基本概念
Dagger2 依赖注入框架,用来解耦类关系。特别是 MVP 分层结构中,方便解耦和分层测试A fast dependency injector for Android and Java.
仓库地址:https://github.com/google/dagger
依赖
- build.gradle
1 | dependencies { |
- app/build.gradle
1 | apply plugin: 'com.neenbedankt.android-apt' |
注解关键字
Inject
- 标注变量
要求Dagger为该变量提供依赖,实例化该变量。不能用private修饰符修饰该变量 - 标注构造函数
@Inject标记了的变量在需要这个类实例的时候,Dagger会找到这个构造函数并将其实例化。也就是@Inject标记的构造函数为@Inject标记的变量提供依赖(实例化)。构造函数的参数不需要使用NonNull标注,Dagger会做强制检测 - 标注方法
标注的方法会自动在构造函数完成实例化后调用
Module
格式:@Module(includes = {*module.class})@Module 注释类,用于标注提供依赖的类。和被 @Inject 标记的构造函数功能类似。但是有时候提供依赖的构造函数是第三方库,无法通过 @Inject 来标注;而且如果构造函数带参数,@Inject 也无法提供参数,所以需要 @Module 来标注。Module 类是一个简单工厂模式,通过 @Provider 提供三个重要功能:
- 给
@Inject变量提供实例 - 给
@Inject构造函数提供参数 - 给其他
@Module中的@Provider方法提供参数
@Inject(构造函数)和 @Module 都可以提供依赖(实例化), Dagger2 会如何选择呢?具体规则如下:
1 | 步骤1:首先查找 @Module 标注的类中是否存在提供依赖的方法 @Provider。 |
所以:创建类实例级别 @Module 维度要高于 @Inject 维度
Provider
注释方法,以 provide 作为前缀,必须出现在有 @Module 注释的类中
它是根据返回值类型来标识和匹配的,方法名并不重要,只需要保证以
provide开头方便阅读即可
这里细化下 @Module 通过 @Provider 提供的三个功能:
- 给
@Inject变量提供实例
也就是将实例化放到了Module中
1 |
|
- 给
@Inject构造函数提供参数
1 | // 构造函数及所需参数 |
对应类构造函数所需参数,可以通过 Module 的构造函数,将参数传递进来
1 | // 对应 module 代码 |
- 给其他
@Module中的@Provider方法提供参数
该方式需要通过后面介绍的@Component将多个@Module类连接起来才能生效。给其他@Module中的@Provider方法提供参数依赖(实例化)
1 | @Module |
Binds
用来简化 @Provider 的注解
- 被注解的方法必须是抽象
- 必须指定返回类型,也就是类似
@Provider的返回值
示例:将 Random 绑定到 SecureRandom 上:
1 |
|
Component
接口或抽象类,是依赖需求方 (@Inject) 和依赖提供方 (@Module) 之间的桥梁。在编译过程中 Dagger 会自动生成对应的实现类 Dagger***Component
如果
@Component标记的类不是顶级类,生成的实现类格式为DaggerA_B_function,类结构之间增加下划线
注解格式
格式:@Component(dependencies = {*component.class}, modules = {*modules.class})
参数 dependencies :表示 Component 和 Component 之间的依赖关系
参数 module :表示依赖提供方
示例:
1 |
|
@Component 注解的接口或抽象类
必须包含 Provision methods 或者 Members-injection methods 其中的一种方法
1 | @Singleton |
Provision methods:该方法没有参数,但是返回值必须为 @Inject 注解变量的类型或者注解过的构造函数的类(也就是依赖需求方),或者为 @Provider 返回值类型:DataRepository getDataRepository();
Members-injection methods:该方法满足下面条件之一:
- 返回值为
void或者 传入的参数类型,此时方法必须且只能包含一个参数,该参数为@Inject注解所在类的类型
1 | // @Inject 在 TaskDetailActivity 中 |
- 返回值为
MembersInjector类型,可以没有参数MembersInjector<SomeType> getSomeTypeMembersInjector();
获取 Component 实例
格式:Dagger***Component.builder().***初始化参数***.build(),举例:
1 | mTasksRepositoryComponent = DaggerTasksRepositoryComponent.builder() |
@Component 在实例化时,会将对应的所有 @Module 全都实例化,此时可以通过 @Module 构造函数传入参数
@Component.Builder
默认是不需要重新定义的。如果需要通过 @Component 将参数传递给 @Module 的 @Provider 中,我们可以在这里定义 @BindsInstance
1 | @Component.Builder |
需要注意 @Component.Builder 需要满足几点条件,具体参考 Dagger 的文档。其中必须有一个 build 返回值为 @Component 类, 每次只能传递一个参数并且返回值为 Builder 类型。
Scope
用于自定义的注解,限定注解作用域,实现局部的单例
Singleton
注解表示为单例模式。但是 Dagger 并没有真正的创建单例,需要我们手动写代码确保只实例化一次。@Singleton 需要同时标记 Provider 和 Component,并且我们把实例化该 Component 放到 Application.onCreate 中,确保只会实例化一次,这样才能体现出单例模式的效果。作用:
- 更好的管理
Component和Module之间的关系,保证它们是匹配的。如果两者的Scope是不一样的,则在编译时报错 - 代码可读性,更好的了解
Module中创建的类实例是单例
Qulifier
用于自定义注解,精确指定 @Module 和 Inject 的依赖对应关系。@Module 来标注提供依赖的方法时,方法名可以随便定义的,但是为了增加可读性,一般以 provide 开头。Dagger2 主要是根据返回值的类型来决定为哪个被 @Inject 标记了的变量赋值。但是如果有多个一样的返回类型,就无法区分了。因此使用 @Qulifier 来自定义一个注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方,这样就能精确匹配了。
一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用@Qulifier 这个注解标示。
常规步骤
引入依赖文件
在 build.gradle 和 app/build.gradle 中添加依赖,并选择 Sync Project 同步并下载这些第三方包
Inject 标注
标注需要依赖的变量,或者提供依赖的构造函数
实现 @Module 和 @Provider
根据 Inject 来设计 Module
Inject标注了构造函数Module可以提供构造函数所需参数Inject没有标注构造函数Module直接实例化并提供返回值
实现 @Component
- 指定范围
Scoped - 选定依赖和指定对应的
Module @Inject标注的依赖变量,如果需要实例化,需要将变量所在类传递给Componentvoid inject(TaskDetailActivity activity);
编译项目
Dagger 自动生成 Module 和 Component 对应的文件
关联注入
Dagger***Component 实现注入并实例化 @Inject 标注的变量
注意
如果觉得 Dagger 生成的不对,可以 clean 整个工程,重新生成一次。有的时候只修改 @Inject 注解,并不会正确的生成对应代码,需要 clean & remake 才行
常见错误
没有提供依赖实例化方法
@Inject 注解成员后,没有注解构造函数,导致如下错误:
1 | C:\Users\xmt\AndroidStudioProjects\xmt\gitlab\02_myTodo\myTodo\app\src\main\java\com\ymzs\todo\mytodo\ToDoApplication.java |
@Component 没有定义范围
没有定义 scoped 的 @Component 不能依赖一个定义了 scoped 的 @Component,否则导致如下报错:
1 | Error:(13, 1) 错误: com.***.mytodo.tasks.TasksPresenterComponent (unscoped) cannot depend on scoped components: |
@Inject 标注私有字段
@Inject 不支持注解私有字段,否则会导致如下错误:
1 | Error:(5, 33) 错误: 找不到符号 |
@Binds 注解的方法,没有提供输入参数
Error:(18, 20) 错误: android.app.Application cannot be provided without an @Inject constructor or from an @Provides-annotated method.
被 @Binds 注解的方法,必须要提供一个 @Inject 构造函数或者一个 @Provides 注解的方法:
1 | @Binds |
@Component.Builder 必须要有一个无参方法返回 @Component 类型
Error:(21, 5) 错误: @Component.Builder types must have exactly one no-args method that returns the @Component type
必须要有一个无参方法返回 @Component 类型,这个可以理解为 build() 提供该 Component 的实例
1 | @Component.Builder |