基本概念
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
标注的依赖变量,如果需要实例化,需要将变量所在类传递给Component
void 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 |