- 一. 配置
- 二. 使用
- 三. 带参数的构造方法
- 四. 将对象注入`Activity`
- 五. 注入多参和多构造函数的对象注入Activity
- 六. 使用Dagger2的单例模式
- 七. `Component`的组织方法
- 八. `Dagger2`结构
- 九. 关键字
- 十. 总结
demo:下载
一. 配置
打开模块的(不是全工程的)build.gradle
文件:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
}
buildTypes {
release {
}
}
}
dependencies {
}
Dagger2
只需要在dependencies
中添加两行说明即可:
dependencies {
......
compile 'com.google.dagger:dagger:2.10'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
}
在gradle
版本低于2.2
,在工程的build.gradle
中添加:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
在module
中添加:
apply plugin: 'com.neenbedankt.android-apt'
而在2.2
及以后的版本中,gradle
官方添加了对Annotation Processors
的支持(https://bitbucket.org/hvisser/android-apt
),使用annotationProcessor
代替了apt
,因此无需再配置apt
。
二. 使用
1. 原始方式
我们先来看一下如果不使用Dagger
的情况,我们在Activity
中创建一个Bean
的对象,其构造方法无需传递任何参数,但是内部会初始化其成员name
,然后在Activity
中使用该对象。
Bean的内容为:
public class Bean {
private String mName = null;
public Bean() {
this.mName = "原始方式";
}
public String getName() {
return mName;
}
}
在Activity中创建并使用该对象:
private void testOrignal() {
Bean bean = new Bean();
Log.d(TAG, "不使用Dagger时 Name:" + bean.getName());
}
//输出:D/Dagger2: 不使用Dagger时 Name:原始方式
2. 最简单的Dagger使用方式
用例子来介绍如何通过Dagger
来实现上面的代码。
2.1. 改造Bean
和Bean
类似,我们新建一个BeanForDagger
的对象,其内容为:
public class BeanForDagger {
private String mName = null;
@Inject
public BeanForDagger() {
this.mName = "Dagger方式";
}
public String getName() {
return mName;
}
}
BeanForDagger
和Bean
的区别:
1、
mName
的初始化名称不同,这是为了区分两个对象
2、BeanForDagger
的构造方法多了@Inject
的注释,它会告诉Dagger
用这个方法来注入目标类
2.2. 创建Component
文件
然后我们我们新建一个BeanComponent
的接口(文件),其内容如下:
@Component //接口使用了`@Component`的注释
public interface BeanComponent {
void inject(MainActivity activity);
}
请注意,这个接口使用了@Component
的注释。
2.3. 使用Dagger
经过上面修改之后,我们就可以使用Dagger
来自动创建BeanForDagger
对象。
首先在Activity
中声明该变量:
@Inject
BeanForDagger mBeanForDagger;
// 然后我们可以这样使用该变量:
private void testDagger() {
// 触发Dagger机制
DaggerBeanComponent.create().inject(this);
if (mBeanForDagger != null) {
Log.d(TAG, "使用Dagger注入变量,mBeanForDagger Name:" + mBeanForDagger.getName());
}
}
//输出:D/Dagger2: 使用Dagger注入变量,mBeanForDagger Name:Dagger方式
这说明我们已经可以使用mBeanForDagger
了,但是请注意,在Activity
中(以及BeanComponent
或BeanForDagger
中),我们并没有new BeanForDagger
的对象。
也就是说, Dagger
帮助我们完成了创建BeanForDagger
对象的工作。
2.4. Dagger
创建对象的过程
这一节我们通过最简单的描述来简单介绍一下Dagger是如何帮助我们创建BeanForDagger对象的。
首先,在Activity
的testDagger
方法中,多了这么一句:
DaggerBeanComponent.create().inject(this);
DaggerBeanComponent
是Dagger
根据BeanComponent
自动生成的,每一个XXXComponent
都会生成对应的DaggerXXXComponent
文件。- 当调用
inject()
时,DaggerBeanComponent
就会扫描inject
方法中的参数(当前就是Activity)中用@Inject
标记的变量,对于当前的Activity
来说,只有mBeanForDagger
变量被标记。 - 找到这个需要被注入的变量后,发现他的类型是
BeanForDagger
,接下来,Dagger
就会去搜索BeanForDagger
的类,然后找到该类中用@Inject
标记的构造方法,并用该构造方法来创建对象。
创建过程的简单步骤:
- 通过
DaggerXXXComponent
的Inject()
触发注入过程 - 搜索目标类中用
@Inject
标识的需要注入的对象 - 找到需要注入对象后,寻找该对象中用
@Inject
标识的构造方法
2.5. 用到的注释
已经用了两个注释:@Inject
和@Component
,其中的Inject
用来注释需要注入的目标对象,以及用来注入的构造方法,而Component
用来触发注入机制。
三. 带参数的构造方法
- 上面的使用过程虽然简单,但是他的特点很明显:需要被注入的对象的构造方法无需传参数!
- 因为无需传参,所以
Dagger
找到该构造方法后可以使用new BeanForDagger()
完成创建。 - 那么疑问就来了,如果这个构造方法需要参数时,怎么办?
Dagger
是否具有创建参数的能力?
比如BeanForDagger
对象的构造方法是这样的:
@Inject
public BeanForDagger(ParamForDagger param) {
this.mName = param.mParamName;
}
那么Dagger
如何才能获得ParamForDagger
的参数呢?
解决的方法很简单,只需要在ParamForDagger
的构造方法中标记@Inject
即可,如这样:
public class ParamForDagger {
String mParamName = null;
@Inject
public ParamForDagger() {
mParamName = "ParamName";
}
}
//输出:D/Dagger2: 使用Dagger注入变量,mBeanForDagger Name:ParamName
- 当
Dagger
在创建BeanForDagger
时发现其构造方法需要ParamForDagger
的对象,那么Dagger
就会递归查找ParamForDagger
类中使用Inject
标记的构造方法,找到后,先创建ParamForDagger
,然后再用他来创建BeanForDagger
。 - 也就是说,
Dagger具备递归创建对象的能力
。 - 这个例子执行结果就是,将
BeanForDagger
中的mName
初始化为ParamForDagger
中的值:ParamName。
上面的例子虽然介绍了有参数和无参数的注入,但他们都有两个局限:
- 需要修改构造方法,添加
@Inject
的标识,但是对于一些jar
包中的方法,或者第三方API
中提供的方法,我们无法修改其源码; - 虽然构造方法中可以传递参数,但是实际上对这个参数也是有要求的,这个参数虽然会递归创建,但递归的最后一层的构造方法中还是无法传参的;
四. 将对象注入Activity
前面一节介绍了Dagger2
最简单的使用场景,但是在结尾我们遇到两个困难,即如何在不修改注入类代码的情况下实现注入?如何解决注入类构造方法需要参数的情况?
比如我们有一个BeanNeedParam
的对象,其构造方法中需要传递一个String类
型的变量用来初始化mName
public class BeanNeedParam {
private String mName = null;
public BeanNeedParam(String name) {
this.mName = name;
}
public String getName() {
return mName;
}
}
接下来我们介绍如何在不修改这个类的前提下,如何将其注入到Activity中。
1. 有参数的注入类使用方式
为了解决有参数的问题,我们需要对之前的结构进行两点改造,下面分别来进行。
1.1. 改造Component
改造Component
,使用上一篇中的demo
,将BeanComponent.java
改造:
@Component(modules = BeanModule.class)
public interface BeanComponent {
void inject(MainActivity activity);
}
为了对比明显,我们再贴出旧的BeanComponent
:
@Component
public interface BeanComponent {
void inject(MainActivity activity);
}
这个时候就能看出来,改造的唯一地方就是在Component
的声明后面多了一句(modules = BeanModule.class)
,具体作用稍后介绍,接下来我们再继续进行第二个改造内容。
1.2. 创建Dagger
中的Module
创建一个新的
Module
文件。
在上面1.1
的Component
改造,发现添加了modules = BeanModule.class
,这里面的BeanModule
就是我们要添加的文件,文件名随便起,只需要前后对应即可,我们这里就用BeanModule
来介绍:
@Module
public class BeanModule {
@Provides
public BeanNeedParam providerBean() {
BeanNeedParam bean = new BeanNeedParam("BeanWithParam");
return bean;
}
}
这就是Module
的全部内容,里面有两点需要特别注意:
- 该文件引入了
@Module
和@Provides
两个新的注释 - 在
providerBean
中完成了BeanNeedParam
对象的创建工作,并传递了合适的参数
1.3、注入对象的使用
现在可以在Activity
中直接使用BeanNeedParam
对象,继续之前的使用即可:
public class MainActivity extends AppCompatActivity {
//带参数的注入
@Inject
BeanNeedParam mBeanNeedParam;
......
/*
使用Dagger方式创建BeanForDagger对象
*/
private void testDagger() {
// 触发Dagger机制
DaggerBeanComponent.create().inject(this);
if (mBeanNeedParam != null) {
Log.d(TAG, "mBeanNeedParam Name:" + mBeanNeedParam.getName());
}
}
}
这里的使用方式和之前无需参数的注入方式完全相同。
2. 注入过程介绍
- 通过之前的介绍,在
Activity
中通过DaggerBeanComponent.create().inject(this)
触发了Dagger
的注入机制。 - 然后
Dagger
扫描当前Activity
有个BeanNeedParam
的对象需要被注入://带参数的注入 @Inject BeanNeedParam mBeanNeedParam;
- 然后按照之前的介绍,
Dagger
将会在BeanNeedParam
的类中搜索使用@Inject
注释了的构造方法,并用他来创建BeanNeedParam
对象。 - 但是这里的
BeanNeedParam
中的构造方法没有使用@Inject
来注释,说明无法直接注入。 - 接下来
Dagger
就会在Component
中搜索被注册的modules
有哪些,结果找到了BeanModule.class
。 - 然后在
BeanModule.class
中搜索同时具备如下条件的注入类:- 通过
@Provides
注释的方法 - 该方法必须是
public
属性 - 该方法的返回值是
BeanNeedParam
- 通过
- 结果就找到了
providerBean()
方法,然后就调用该方法并顺利拿到了BeanNeedParam
对象,然后交给Activity
的mBeanNeedParam
使用。 - 请注意,
Dagger
在Module
中搜索目标类时,与Module
中提供的方法名无关,只和返回值有关,比如下面两种写法是完全等效的,都可以实现BeanNeedParam
的注入:
@Provides
public BeanNeedParam providerBean() {
......
}
和:
@Provides
public BeanNeedParam dushaofeng() {
......
}
整个过程可以归纳为以下步骤:
1. Activity
中通过DaggerXXXComponent
的Inject()
触发注入过程
2. Dagger
在Activity
中搜索用@Inject
标注的变量,说明改对象需要被注入
3. 去Component
中注册的Module
中搜索注入类
4. 在Module
中搜索返回值为注入类的方法,执行并拿到注入类对象,从而完成注入过程
5. 如果在Module
中没有搜索到提供目标类注入的方法,则在工程中搜索目标类
6. 找到需要注入对象后,寻找该对象中用@Inject
标识的构造方法,完成自动创建过程
3. Module
中的方法需要参数的情况
如果遇到Module
中的方法需要参数的情况该如何处理呢?
比如Module
中提供BeanNeedParam
的方法是这样的:
@Provides
public BeanNeedParam providerBean(String name) {
BeanNeedParam bean = new BeanNeedParam(name);
return bean;
}
那么Dagger
在调用该方法时,就需要一个String
类型的参数,如何将这个参数传递给Dagger
呢?
可以这样实现,在Module中提供一个返回值为String的方法:
@Module
public class BeanModule {
@Provides
public BeanNeedParam providerBean(String name) {
BeanNeedParam bean = new BeanNeedParam(name);
return bean;
}
@Provides
public String providerString() {
return "param from other function";
}
}
也就是说,Dagger
在Module
中执行providerBean
时,发现需要一个String
类型的参数,接着Dagger
就会在Module
中搜索返回值为String
的方法并执行。
这说明Dagger在Module中的执行也具备递归性。
4. 疑问
通过上面的介绍我们找到了不修改注入类代码的情况下将其注入的方法。但这种方法还是有个局限:那就是每次注入只能传递给BeanNeedParam
相同的参数,当我们需要给他传递不同的参数时,如何实现呢?
比如对于BeanNeedParam
的注入类来说,我们需要在不同场合传递给他不同的name
值,该如何处理?
五. 注入多参和多构造函数的对象注入Activity
1. 包含可变参数的构造方法的情况
这里的"可变参数"指的是注入类构造方法传递的参数可能每个都不同,比如对于BeanNeedParam的注入类的构造方法:
public BeanNeedParam(String name) {
this.mName = name;
}
我们希望拿到两个BeanNeedParam
对象,其中一个对象的mName="AAA"
,而另一个对象的mName="BBB"
,如下:
1.1. 改造BeanModule
在BeanModule
中创建两个方法来分别返回不同的BeanNeedParam
对象,并使用@Named
注释来区分这两个方法:
@Named("TypeA")
@Provides
public BeanNeedParam providerBeanA() {
return new BeanNeedParam("AAA");
}
@Named("TypeB")
@Provides
public BeanNeedParam providerBeanB() {
return new BeanNeedParam("BBB");
}
这里引入了Dagger
的一个新的注释标记:@Named
,他的作用是给方法添加了一个新的属性,来区分当前注入类的不同注入方式。
更通俗的理解是,告诉Dagger
,在BeanModule
中存在两种方法来获得BeanNeedParam
对象,其中一个叫做TypeA
,另一个叫做TypeB
,具体使用哪个请按照需求来选择。
1.2. 改造Activity中使用方式
既然已经给Dagger
准备好了不同的选择,那么接下来只需要告诉Dagger
当前需要使用哪个就可以了。
比如对于Activity
来说,我们创建当前就需要两个BeanNeedParam
对象,那么可以这么定义:
@Named("TypeA")
@Inject
BeanNeedParam mBeanNeedParamAAA;
@Named("TypeB")
@Inject
BeanNeedParam mBeanNeedParamBBB;
这就很明确的告诉Dagger
,请用TypeA
的构造方法创建BeanNeedParam
并交给mBeanNeedParamAAA
使用,用TypeB
的构造方法创建BeanNeedParam
并交给mBeanNeedParamBBB
使用。
下面我们分别获取mBeanNeedParamAAA
和mBeanNeedParamBBB
中的Named
值来验证一下结果:
//输出:
D/Dagger2: mBeanNeedParamAAA Name:AAA
D/Dagger2: mBeanNeedParamBBB Name:BBB
2. 注入类拥有多个构造方法的情况
其实知道了如何处理多参数的情况,就知道如何来处理多构造方法的情况,我们来看具体如何实现。
我们先在BeanNeedParam
的注入类中添加另外一个构造方法,其需要传入的不再是String
,而是一个int
型的数据,这样他就拥有了两个构造方法:
// 需要String类型的参数
public BeanNeedParam(String name) {
this.mName = name;
}
// 需要int类型的参数
public BeanNeedParam(int number) {
this.mNumber = number;
}
接下来我们看如何实现该情况下的注入。
2.1. 改造BeanModule
我们还是先来改造BeanModule
,我们需要提供两个不同的方法来创建不同的BeanNeedParam
:
@Named("TypeString")
@Provides
public BeanNeedParam prividerBeanWithString() {
//使用string类型构造方法,并传递"String"的参数
return new BeanNeedParam("String");
}
@Named("TypeInt")
@Provides
public BeanNeedParam prividerBeanWithInt() {
//使用int类型构造方法,传递"2017"的参数
return new BeanNeedParam(2017);
}
这里我们看到其实是和刚才介绍多参数的情况是一样的,只不过我们用TypeString
和TypeInt
来区分不同的构造方法而已。
2.2. 改造Activity
同理,我们在Activity
中也通过TypeString
和TypeInt
的注释来获取不同的BeanNeedParam
对象:
//不同构造方法的注入
@Named("TypeString")
@Inject
BeanNeedParam mBeanNeedParamString;
//不同构造方法的注入
@Named("TypeInt")
@Inject
BeanNeedParam mBeanNeedParamInt;
这样一来,mBeanNeedParamString
中的mNumber
就会使用prividerBeanWithString
中传递的"String"值,而mBeanNeedParamInt
中的mNumbe
r就会使用prividerBeanWithInt
中传递的2017
的值。
将他们显示出来:
//测试不同构造方法的情况
if (mBeanNeedParamString != null) {
Log.d(TAG, "mBeanNeedParamString Name:" + mBeanNeedParamString.getName() + ",Number:" + mBeanNeedParamString.getNumber());
}
if (mBeanNeedParamInt != null) {
Log.d(TAG, "mBeanNeedParamInt Name:" + mBeanNeedParamInt.getName() + ",Number:" + mBeanNeedParamInt.getNumber());
}
//输出:
D/Dagger2: mBeanNeedParamString Name:String,Number:-1
D/Dagger2: mBeanNeedParamString Name:DefaultName,Number:2017
3. 更换其他注释符
如果觉得@Named
这个标识符有歧义或者使用字符串的形式容易写错,那么可以利用Dagger
自定义一个全新的注释符。下面我们就来创建一个全新的SelfType
名称的注释符。
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface SelfType {
int value() default 1;// 注释符的类型是int,默认值为1。
}
然后我们就可以在BeanModule
中如此使用他来区分不同的构造方法:
// 对于SelfType为1的变量,使用"A"作为构造方法参数
@SelfType(1)
@Provides
public BeanNeedParam prividerBeanWithTypeAnnotationA() {
return new BeanNeedParam("A");
}
// 对于SelfType为2的变量,使用"B"作为构造方法参数
@SelfType(2)
@Provides
public BeanNeedParam prividerBeanWithTypeAnnotationB() {
return new BeanNeedParam("B");
}
然后在Activity
中使用:
@SelfType(1)
@Inject
BeanNeedParam mBeanNeedParamSelfTypeA;
@SelfType(2)
@Inject
BeanNeedParam mBeanNeedParamSelfTypeB;
这样一来,mBeanNeedParamSelfTypeA
对象就会使用BeanNeedParam("A")
来创建,而mBeanNeedParamSelfTypeB
就会使用BeanNeedParam("B")
来创建。
同时,由于我们定义SelfType
的时候其默认值就是1,因此下面两种写法是完全等效的:
//使用默认值:1
@SelfType
@Inject
BeanNeedParam mBeanNeedParamSelfTypeA;
//手动写入当前值:1
@SelfType(1)
@Inject
BeanNeedParam mBeanNeedParamSelfTypeA;
至此,Dagger2
中常用(必备)的注释就介绍完了,接下来我们来介绍一些高阶注释的使用。
六. 使用Dagger2的单例模式
Dagger2
中有一个极其具有迷惑性的注释@Singleton
,字面意思感觉可以达到单例模式的作用,但实际上他的这个单例
有点假。
为什么说有点假呢?因为他的确可以在一些情况下达到单例的作用,但是又和我们通常意义上的单例模式相差很远,用错了,可能会适得其反。
1.Singleton
注释的使用
如果我们在目标类中存在两个同样的注入类,它们实际上是两个不同的对象,请看这个定义:
@Inject
BeanNeedParam mBeanNeedParamCC;
@Inject
BeanNeedParam mBeanNeedParamDD;
这说明Activity
中的mBeanNeedParamCC
和mBeanNeedParamDD
两个变量都需要用BeanNeedParam
来注入,接下来我们看它们是分别被注入,还是用同一个对象进行注入:
//测试注入对象的地址
if (mBeanNeedParamCC != null) {
Log.d(TAG, "mBeanNeedParamCC 地址:" + mBeanNeedParamCC);
}
if (mBeanNeedParamDD != null) {
Log.d(TAG, "mBeanNeedParamDD 地址:" + mBeanNeedParamDD);
}
//输出:
D/Dagger2: mBeanNeedParamCC 地址:com.example.able.daggerdemo.BeanNeedParam@d4455b2
D/Dagger2: mBeanNeedParamDD 地址:com.example.able.daggerdemo.BeanNeedParam@1e0e903
我们看到这两个对象的地址不同,这表明,虽然mBeanNeedParamCC
和mBeanNeedParamDD
都是用BeanNeedParam
进行注入,但是它们是分别单独注入的,每个对象都是独立的。
如果我们想要实现单例模式该如何办呢?比如我们想要mBeanNeedParamCC
和mBeanNeedParamDD
是同一个对象该怎么办呢?
这时候就可以用Singleton
的注释了,进行如下改造:
1.1. 改造BeanModule中的方法
在提供BeanNeedParam
的方法上面添加@Singleton
注释:
@Singleton
@Provides
public BeanNeedParam providerBean(String name) {
BeanNeedParam bean = new BeanNeedParam(name);
return bean;
}
1.2、改造BeanComponent
需要在BeanComponent
中添加注释:
@Singleton
@Component(modules = BeanModule.class)
public interface BeanComponent {
void inject(MainActivity activity);
}
//输出:
D/Dagger2: mBeanNeedParamCC 地址:com.example.able.daggerdemo.BeanNeedParam@d4455b2
D/Dagger2: mBeanNeedParamDD 地址:com.example.able.daggerdemo.BeanNeedParam@d4455b2
它们打印出来地址相同,这说明mBeanNeedParamCC
和mBeanNeedParamDD
是同一个对象,Singleton
完成了单例模式的注入。
2. Singleton
的假单例行为
看了上面的例子,我们发现Singleton
的确实现了BeanNeedParam
的单例注入,但是当我们把同样的"单例类"注入到其他类中时,再来看一下效果。
我们现在创建一个新的类,为了方便测试,我们就在Activity
中创建一个子类OtherClass
:
class OtherClass {
@Inject
BeanNeedParam mBeanNeedParamEE;
@Inject
BeanNeedParam mBeanNeedParamFF;
public OtherClass() {
//重新注入
DaggerBeanComponent.create().inject(this);
this.testDagger();
}
private void testDagger() {
//测试注入对象的地址
if (mBeanNeedParamEE != null) {
Log.d(TAG, "mBeanNeedParamEE 地址:" + mBeanNeedParamEE);
}
if (mBeanNeedParamDD != null) {
Log.d(TAG, "mBeanNeedParamFF 地址:" + mBeanNeedParamFF);
}
}
}
这个类中我们注入了两个BeanNeedParam
对象,分别是mBeanNeedParamEE
和mBeanNeedParamFF
,按照之前的设计,它们都是使用@Singleton
标注的"单例"。
这里由于我们需要对OtherClass
进行注入,因此需要在BeanComponent
中添加新的inject()
方法,如下:
@Singleton
@Component(modules = BeanModule.class)
public interface BeanComponent {
//将BeanModule注入MainActivity中
void inject(MainActivity activity);
//将BeanModule注入OtherClass
void inject(MainActivity.OtherClass otherClass);
}
然后我们在MainActivity
中实例化OtherClass
:
OtherClass otherClass = new OtherClass();
运行一下,mBeanNeedParamCC
、mBeanNeedParamDD
、mBeanNeedParamEE
、mBeanNeedParamFF
四个对象的地址分别如下:
//输出:
D/Dagger2: mBeanNeedParamCC 地址:com.example.able.daggerdemo.BeanNeedParam@96072f
D/Dagger2: mBeanNeedParamDD 地址:com.example.able.daggerdemo.BeanNeedParam@96072f
D/Dagger2: mBeanNeedParamEE 地址:com.example.able.daggerdemo.BeanNeedParam@96b913c
D/Dagger2: mBeanNeedParamFF 地址:com.example.able.daggerdemo.BeanNeedParam@96b913c
这时我们发现,MainActivity
中的mBeanNeedParamCC
和mBeanNeedParamDD
是同一个对象,而OtherClass
中的mBeanNeedParamEE
和mBeanNeedParamFF
是另外的一个对象。
这说明,@Singleton
的单例作用,只对同一次的inject()
有效。
3. Singleton的单例作用范围
那么再进一步来分析,inject()
方法实际上是通过DaggerBeanComponent.create()
创建的BeanComponent
对象的一个成员方法,也就是说:
DaggerBeanComponent.create().inject(this);
实际上就是:
BeanComponent beanComponent = DaggerBeanComponent.create();
beanComponent.inject(this);
那么这个单例的作用范围究竟是在beanComponent
的范围还是inject()
的范围呢?
下面我们将MainActivity
和OtherClass
用同一个BeanComponent
进行注入来看一下效果。
在MainActivity
中,我们将beanComponent
对象传递给OtherClass
:
private void testDagger() {
BeanComponent beanComponent = DaggerBeanComponent.create();
beanComponent.inject(this);
if (mBeanNeedParamCC != null) {
Log.d(TAG, "mBeanNeedParamCC 地址:" + mBeanNeedParamCC);
}
if (mBeanNeedParamDD != null) {
Log.d(TAG, "mBeanNeedParamDD 地址:" + mBeanNeedParamDD);
}
OtherClass otherClass = new OtherClass(beanComponent);
}
在OtherClass中我们使用传递进来的beanComponent直接注入:
class OtherClass {
@Inject
BeanNeedParam mBeanNeedParamEE;
@Inject
BeanNeedParam mBeanNeedParamFF;
public OtherClass(BeanComponent bc) {
//使用Activity创建的BeanComponent进行注入
bc.inject(this);
this.testDagger();
}
private void testDagger() {
//测试注入对象的地址
if (mBeanNeedParamEE != null) {
Log.d(TAG, "mBeanNeedParamEE 地址:" + mBeanNeedParamEE);
}
if (mBeanNeedParamDD != null) {
Log.d(TAG, "mBeanNeedParamFF 地址:" + mBeanNeedParamFF);
}
}
}
//输出:
D/Dagger2: mBeanNeedParamCC 地址:com.example.able.daggerdemo.BeanNeedParam@ff03081
D/Dagger2: mBeanNeedParamDD 地址:com.example.able.daggerdemo.BeanNeedParam@ff03081
D/Dagger2: mBeanNeedParamEE 地址:com.example.able.daggerdemo.BeanNeedParam@ff03081
D/Dagger2: mBeanNeedParamFF 地址:com.example.able.daggerdemo.BeanNeedParam@ff03081
我们发现此时四个BeanNeedParam
变量实际上是指向了同一个对象。
这说明,在同一个BeanComponent
对象内,单例是有效的。
4. 让@Singleton成为全局单例
我们知道了@Singleton
的作用范围之后,就可以根据不同需求来使用它,当我们需要全局的单例时,就可以在Application
中创建Component
对象,然后将其提供给需要的类,从而实现App
范围内的单例模式。
具体实现步骤:
- 在应用的
Application
中创建Component
对象,并将其暴露出来:
public class DaggerApplication extends Application {
private BeanComponent mBeanComponent;
@Override
public void onCreate() {
super.onCreate();
mBeanComponent = DaggerBeanComponent.create();
}
public BeanComponent getBeanComponent() {
return mBeanComponent;
}
}
- 然后就可以在Activity和OtherClass中使用该Component:
BeanComponent beanComponent = ((DaggerApplication)getApplication()).getBeanComponent();
beanComponent.inject(this);
实现了全局的单例模式。
5. @Scope注释
Dagger2
中的@Scope
注释的作用描述如下:
/**
* Identifies scope annotations. A scope annotation applies to a class
* containing an injectable constructor and governs how the injector reuses
* instances of the type. By default, if no scope annotation is present, the
* injector creates an instance (by injecting the type's constructor), uses
* the instance for one injection, and then forgets it. If a scope annotation
* is present, the injector may retain the instance for possible reuse in a
* later injection. If multiple threads can access a scoped instance, its
* implementation should be thread safe. The implementation of the scope
* itself is left up to the injector.
*/
简单来说就是没有
Scope
时,每次注入都创建新的对象(By default, if no scope annotation is present, the injector creates an instance, uses the instance for one injection, and then forgets it.)
,而使用了Scope
的注释以后,创建的对象会被复用,从而实现单例模式(If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection.)
。
看这个描述,他与@Singleton
的作用非常相似,而实际上,@Singleton
注释就是@Scope
的一个默认实现。
因此,我们完全可以自定义一个全新的注释来实现和@Singleton
相同的作用。
比如我们可以定义一个叫做MySingleton
的注释来实现单例模式:
@Scope
@Documented
@Retention(RUNTIME)
public @interface MySingleton {
}
而他的使用方法和作用@Singleton
完全相同,这里就不再介绍了。
6. Dagger
流程总结
现在我们回头来看一下Dagger2
中两大组件所起到的作用:
Module
: 它用来提供各个注入类的集合,Dagger2
在需要注入对象时,将会先从Module中寻找要注入的类,找不到的话再去寻找标记了Inject的构造方法。Component
: 它是目标类和注入类之间的桥梁和接口,告诉Dagger2
需要向哪里注入哪些类。
我们用一个图示来说明它们之间的关系:
七. Component
的组织方法
所谓Component
组织方法,也就是我们工程中的Component
该如何分布和结合。
对于一款APP
来说,一些基础的服务类比如全局Log
、图片加载器
、网络请求器
、缓存器
等应该做到全局单例,而对某个Activity
或者Fragment
来说又有自己的单例或者非单例的对象,那么这种情况下该如何组织我们的注入结构呢?
我们现在知道Component
是连接注入类和目标类的桥梁,那么最简单的结构应该是这样的:
Application
负责创建全局的单例或者非单例注入类的Component对象Activity
或Fragment
在继承Application
提供的Component
基础上扩展自己的Component
接口
具体该如何操作呢?Dagger2
给我们提供两种方法来实现注入继承。
1. 使用dependencies
属性实现继承注入
如果对比源码看的话,请将源码分支切换到UseDependencies
分支。
1.1. 准备ApplicationBean
对象
我们创建一个ApplicationBean
对象用来作为目标类,准备将其注入到应用中:
public class ApplicationBean {
private String name = null;
public ApplicationBean() {
name = "AppBean";
}
public String getAppBeanName() {
return name;
}
}
1.2、准备APP
级别的Module
对象
然后创建ApplicationModule
用来将其注入到目标类,并且我们标记了Singleton
准备将其作为单例模式注入:
@Module
public class ApplicationModule {
//作为单例模式注入app
@Singleton
@Provides
ApplicationBean privoderAppBean() {
return new ApplicationBean();
}
}
1.3. 准备APP
级别的Component
对象
相应的,我们创建ApplicationComponent
用来连接ApplicationModule
和Application
:
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(DaggerApplication application);
//说明将BeanForApplication开放给其他Component使用
ApplicationBean providerAppBean();
}
在这里请注意两点:
- 由于我们设计要将
ApplicationBean
作为单例注入,因此ApplicationComponent
也需要标记@Singleton
标识 - 我们在
ApplicationComponent
中提供了一个返回值为ApplicationBean
对象的方法声明,它的作用是将该Component
中的ApplicationBean
对象暴露给其他Component
使用,相当于AIDL
语言中的方法声明。
1.4. 注入Application
我们需要在Application
中完成两个任务:
- 将
ApplicationBean
注入到Application
内部 - 将
ApplicationComponent
对象共享给Activity
或者其他类
具体实现如下:
public class DaggerApplication extends Application {
private ApplicationComponent mAppComponent;
@Inject
ApplicationBean mAppBean1;
@Inject
ApplicationBean mAppBean2;
@Override
public void onCreate() {
super.onCreate();
if (mAppComponent == null) {
mAppComponent = DaggerApplicationComponent.create();
}
mAppComponent.inject(this);
Log.d("Dagger", "Application mAppBean1:" + mAppBean1);
Log.d("Dagger", "Application mAppBean2:" + mAppBean2);
}
public ApplicationComponent getAppComponent() {
return mAppComponent;
}
}
在这里我们注入了两次ApplicationBean
对象,并在注入完成后打印出它们的地址用于观察是否实现了单例的功能。
1.5. 准备ActivityBean
对象
我们再创建一个Activity
的Bean
对象用于观察注入情况:
public class ActivityBean {
private String name = null;
public ActivityBean() {
}
public String getAppBeanName() {
return name;
}
}
1.6. 准备Activity
的Module
对象
Activity
的Module
应该提供ActivityBean
的注入方式:
@Module
public class ActivityModule {
@Provides
ActivityBean providerActivityBean() {
return new ActivityBean();
}
}
1.7. 准备Activity
的Component
对象
我们要在Activity
的Component
中继承ApplicationComponent
,也就是要让Activity的Component
不仅可以从ActivityModule
中查找注入类,还要能从ApplicationModule
中查找到注入类:
@ForActivity
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);
void inject(MainActivity.OtherClass otherClass);
}
这个Component
的写法有三处与之前的写法不同的地方:
- 添加了
ForActivity
的修饰,而这个ForActivity
就是我们自定义的Scope
的一种,根据之前我们的介绍,他的作用和Singleton
是一样的,用于限制该Component
的使用范围:
@Scope
@Retention(RUNTIME)
public @interface ForActivity {
}
为什么要添加这个修饰呢?
因为当前
Component
所继承的ApplicationComponent
中包含Singleton
的注释,所以ApplicationComponent
的子类Component
的作用范围不能高于ApplicationComponent
的作用范围,因此需要对ActivityComponent
也添加Scope
的限定。
-
Component
中多了dependencies = ApplicationComponent.class
的注释,它的作用就是告诉Dagger
,当前Component
依赖于ApplicationComponent
,在查找注入类的时候不仅要在ActivityModule
中查找,还需要去ApplicationComponent
中的Module
中查找。 -
我们提供了两个inject()方法,作用是要将该Component同时注入到两个对象中,这在之前的介绍中使用过。
1.8. 设计Activity
对象
我们接下来就要在Activity
中同时注入ActivityBean
和ApplicationBean
对象了,并且ApplicationBean
还是全局单例的模式,为了扩展测试,我们在Activity
中还创建了一个OtherClass
,也将ActivityBean
和ApplicationComponent
都注入进去进行观察:
public class MainActivity extends AppCompatActivity {
@Inject
ApplicationBean applicationBean1;
@Inject
ApplicationBean applicationBean2;
@Inject
ActivityBean activityBean;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerApplication application = (DaggerApplication) getApplication();
ApplicationComponent applicationComponent = application.getAppComponent();
ActivityComponent activityComponent = DaggerActivityComponent.builder().applicationComponent(applicationComponent).build();
activityComponent.inject(this);
Log.d("Dagger", "Activity activityBean:" + activityBean);
Log.d("Dagger", "Activity applicationBean1:" + applicationBean1);
Log.d("Dagger", "Activity applicationBean2:" + applicationBean2);
OtherClass otherClass = new OtherClass();
}
class OtherClass {
@Inject
ApplicationBean applicationBean1;
@Inject
ApplicationBean applicationBean2;
@Inject
ActivityBean activityBean;
public OtherClass() {
DaggerApplication application = (DaggerApplication) getApplication();
ApplicationComponent applicationComponent = application.getAppComponent();
ActivityComponent activityComponent = DaggerActivityComponent.builder().applicationComponent(applicationComponent).build();
activityComponent.inject(this);
Log.d("Dagger", "OtherClass activityBean:" + this.activityBean);
Log.d("Dagger", "OtherClass applicationBean1:" + this.applicationBean1);
Log.d("Dagger", "OtherClass applicationBean2:" + this.applicationBean2);
}
}
}
1.9. 结果分析
我们运行之后打印出来的Log如下:
//输出:
D/Dagger: Application mAppBean1:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Application mAppBean2:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Activity activityBean:com.example.able.dagger2demo.ActivityBean@57a6b69
D/Dagger: Activity ApplicationBean1:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Activity ApplicationBean2:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: OtherClass activityBean:com.example.able.dagger2demo.ActivityBean@e0fe9ee
D/Dagger: OtherClass ApplicationBean1:com.example.able.dagger2demo.ActivityBean@95c5354
D/Dagger: OtherClass ApplicationBean2:com.example.able.dagger2demo.ActivityBean@95c5354
我们来分析Log
的表现:
1、Application
中注入的mAppBean1
和mAppBean2
以及Activity
中注入的applicationBean1
、applicationBean2
还有OtherClass
中注入的applicationBean1
、applicationBean2
这六个对象的地址都是95c5354
分析:
- 在
Activity
和OtherClass
中我们可以获取到ApplicationBean
对象,说明我们当前的注入方式完成了Activity
从Application
继承Component
进行注入的任务 - 我们不仅在
APP
的全局都获取到了ApplicationBean
对象,而且得到的都是单例对象,这说明我们在ApplicationModule
中对ApplicationBean
进行单例注入的方式在全局都是有效的
Activity
中的activityBean
和OtherClass
中的activityBean
对象地址不同:
ActivityBean
对象在Activity
中和OtherClass
中分别注入了两次,所以这两次注入是独立的,它们注入的ActivityBean
对象是不同的
至此,该注入方式我们就介绍完毕,下面我们来介绍另一种继承的方式。
2. 使用Subcomponent
的方式进行继承注入
2.1. 如何注入
该方式和上面的方式区别之处只有三个地方:
- 改造
Activity
的Component
对象
我们需要先来改造Activity
的Component
对象,也就是ActivityComponent
,需要将其改写为如下的方式:
@ForActivity
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity activity);
void inject(MainActivity.OtherClass otherClass);
}
它与之前的方式的区别:
- 不再使用
@Component
而使用@Subcomponent
来注释 - 删除了
"dependencies = ApplicationComponent.class"
语句
2、改造Application
的Component
对象
然后我们来改造Application
的Component
对象也就是ApplicationComponent
,将其改造成如下方式:
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
//注入DaggerApplication
void inject(DaggerApplication application);
//说明将BeanForApplication开放给其他Component使用
ApplicationBean providerAppBean();
ActivityComponent activityComponent();
}
这里的改造只是多了一句声明:ActivityComponent activityComponent()
3. 改造Activity
中的注入方式
我们还需要改造Activity
和OtherClass
中的注入方式,改造成如下方式(Activity和OtherClass的注入方式相同):
DaggerApplication application = (DaggerApplication) getApplication();
ApplicationComponent applicationComponent = application.getAppComponent();
applicationComponent.activityComponent().inject(this);
然后就完成了所有改造,运行结果如下:
//输出:
D/Dagger: Application mAppBean1:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Application mAppBean2:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Activity activityBean:com.example.able.dagger2demo.ActivityBean@57a6b69
D/Dagger: Activity ApplicationBean1:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: Activity ApplicationBean2:com.example.able.dagger2demo.ApplicationBean@95c5354
D/Dagger: OtherClass activityBean:com.example.able.dagger2demo.ActivityBean@e0fe9ee
D/Dagger: OtherClass ApplicationBean1:com.example.able.dagger2demo.ActivityBean@95c5354
D/Dagger: OtherClass ApplicationBean2:com.example.able.dagger2demo.ActivityBean@95c5354
这个结果与dependencies
的方式结果是一致的,说明两种注入方式都达到了Component
继承的目的。
3. dependencies
与Subcomponent
注入方式的区别
在Activity
注入时就可以看出来,我们再次贴出它们的对比:
dependencies
方式:
DaggerApplication application = (DaggerApplication) getApplication();
//获取ApplicationComponent对象
ApplicationComponent applicationComponent = application.getAppComponent();
//用ActivityComponent对象进行注入
ActivityComponent activityComponent = DaggerActivityComponent.builder().applicationComponent(applicationComponent).build();
activityComponent.inject(this);
Subcomponent
方式:
DaggerApplication application = (DaggerApplication) getApplication();
ApplicationComponent applicationComponent = application.getAppComponent();
//用ApplicationComponent对象进行注入
applicationComponent.activityComponent().inject(this);
结果发现,dependencies
方式中,我们最终调用的是ActivityComponent
对象中的inject()
方法,而Subcomponent
方式中,我们最终调用的是ApplicationComponent.inject()
方法。
从Component
的注释上我们也可以看到这个区别:
dependencies
方式:
//ApplicationComponent
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
......
}
//ActivityComponent
@Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
......
}
Subcomponent
方式:
//ApplicationComponent
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
......
ActivityComponent activityComponent();
}
//ActivityComponent
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
......
}
对比中我们发现,dependencies
中Component
强调的是在子类Component
依赖于某个Component
(子类为主角),而Subcomponent
中强调的则是在父类Component
中提供某个子类的Component
(父类为主角)。
4. 如何选择两种继承方式
那么该如何选择这两种继承方式呢?
在Stackoverflow
中就有人提出了这样的问题(提问),简单理解就是:
dependencies
方式让Component
之间更加独立,结构更加清晰,也更利于解耦。
八. Dagger2
结构
1. 控制反转和依赖注入
首先,我们就要先介绍控制反转和依赖注入的概念了。
如果A类依赖B类的对象通常我们会怎么做呢?
public class A {
private B b;
public A() {
b = new B();
}
}
这是最通常的做法:在类A
里new
一个B
的对象作为成员变量,也就是A
控制了B
实例的生成,控制依赖。这样做的不足在于A
需要了解B
的细节,如果要替换B
模块还需要相应的修改A
。更严重的是,如果B
的实例化依赖了其他类C/D/F...
,则A
类还需要管理C/D/F
类的实例化过程,这样模块间的耦合度会很高,修改和替换难度都很大。
而控制反转(IoC)是一种设计思想,即设计好的对象的依赖由容器来控制,而非之前用对象来直接控制对象依赖。程序架构发生了“主从换位”的变化,应用程序被动的等待IoC
容器来创建并注入它所需要的资源。
依赖注入(Dependency Injection)是控制反转的一种具体实现。组件之间依赖关系由注入器(容器)在运行期决定,即由容器动态的将某个依赖关系注入到组件之中。依赖注入将依赖统一管理,使得组件间耦合度降低,更容易拓展和修改,也能更好的控制组件的生命周期。如果某组件需要修改其依赖,如果新增加的依赖存在与依赖图中,那么只需修改该类就可以,注入器可以将新的依赖注入。
2. java
依赖注入标准:JSR-330
java
有一套依赖注入规范——JSR-330
。该规范里定义了一些依赖注入相关的注解(javax.inject
包下),dagger
就是基于了这个规范,也就是dagger对外使用的annotation许多都是JSR-330
下的。而其他的注入框架如Spring
也支持JSR-330
。上文的例子中,如果用支持JSR-330
的依赖注入框架的话,最后实现的代码可以类似于:
public class A {
@inject
private B b;
}
在dagger2
中用的JSR-330
标准注释有:@Inject
@Qualifier
@Scope
@Named
等。
值得注意的是,JSR-330
并没有规范注入器,也就是说用不同的注入框架时,上述代码可以做到基本一致,都是符合JSR-330
规范的,而不同框架注入器实现方式不同。
3. dagger2
的基本结构
使用dagger2
进行依赖注入时,整个依赖关系如图。在dagger2
中Component
便是注入器,它维护了依赖图并向其他对象注入依赖。依赖图中的对象由Module
提供,或是通过构造方法注入来获得,一些具体细节将在稍后介绍。
4. 用Dagger2
向类注入依赖
通过Dagger2
将依赖注入到某个类中的时候,是用@Inject
注解来实现的。@Inject
注解为JSR-330
标准中的一部分。
注入方式有两种:即@Inject
可以标记域也可以标记构造方法。
- 域注入
public class MainActivity extends Activity {
@Inject
SharedPreferences mSharedPreferences;
public void onCreate(Bundle savedInstance) {
InjectorClass.inject(this);
}
注意,这里需要在适当的时机,如在组件的onCreate()
中调用Component
的inject()
方法,此时将依赖注入到该对象中。此外,被@Inject
标记的域不可为private
,因为它需要有注入器Component
进行赋值。
- 构造方法注入
public class ProviderHandler {
private ContentResolver mContentResolver;
@Inject
ProviderHandler(@NonNull Context context) {
mContentResolver = context.getContentResolver();
}
注意:在构造方法注入的时候这个类成为依赖图表的一部分。当其他类需要该这个类的对象时,dagger2
会调用该构造方法创造其实例对象,该类的对象可以被注入其他对象中,如上部分的图所示,B
就是通过构造器注入的方式生成,Component
也可以根据需要将其注入到其他类中。
这两种注入方法中,需要的被注入的对象都从依赖图中获取,而不需要手工控制。
而对于Android
中的一些组件,如Activity/Service/Fragment
等等,由于其构造方法不由我们控制,只能通过域注入来完成。
5.Component
Component
就是我们前文提到的注入器(容器),它定义了依赖图,并负责向其他类中注入依赖。
和Android
的许多框架一样,对于Component
部分,是有开发者以接口形式写Component
,框架通过gradle
插件自动生成Component
的实例。形式有些像retrofit
,但retrofit
是通过动态代理,通过代理对象来完成接口任务的。
在实际开发中,我们定义的Component代码类似如下:
@Singleton
@Component(modules = {ApplicationModule.class, NumberInfoModule.class})
public interface AppComponent {
void inject(MyService service);
void inject(MyContentProvider p rovider);
@Component.Builder
interface Builder {
@BindsInstance
Builder application(Application application);
AppComponent build();
}
}
上面例子中,我们定义了一个接口AppComponent
,该接口被@Component
注释,表明这是一个dagger2
中的Component
,Dagger2
会在编译阶段生成其实例类:DaggerXXXComponent
,以这个为例就是生成DaggerAppComponent
,其实现了APPComponent
。
我们来看这个接口的各个部分:
@Singleton
注解:这是一个scope
的注释,dagger2
的scope
机制是为单利提供了生命周期的概念。@Singleton
表明了依赖的生命长度和Application
生命长度一致。另外,@Singleton
是JSR-330
标准中的一部分,另外我们还可以自定义scope
。有关scope
的概念会在下一篇文章中介绍。Module
:在上面@Component
中我们指定了Module
类,Module
是为依赖图提供具体依赖的对象的,也就是我们在Module
中我们告诉Dagger2
框架当我们需要某个类的对象时,我们该如何获得。再回到最上面的图,其中A
和C
都是由Module
提供的。也就是除了构造方法注入的类以外,其他的依赖需要有Module
提供。后面会详细介绍。inject
方法:Component
在接口中,我们定义了inject
的方法。在前面介绍通过域注入时,我们在组件的onCreate()
方法中调用了Component
的inject
方法。该方法表明了我们可以将依赖图中的依赖注入到什么类中,该类(组件)在适当的时候(如onCreate()
)调用inject
方法,完成依赖注入。inject
方法由我们写接口的时候定义,编译阶段框架实现该方法。Builder
: 在Component
接口中定义Component.Builder
接口,顾名思义是在定义Component
的建造者。上例中,用@BindsInstance
在定义Builder
时候我们可以在允许Component
初始化的时候设置一些对象,如上面例子,可以给Component
设置Application
对象,从而将Application
对象纳入到依赖图中。
另外,如果其Module
的构造函数需要传入参数的话,会自动生成Component.Builder
设置该Module
的方法,可以参考如下例子(也是一个添加Apllication
依赖的实例):
@Module
public class AppModule {
Application mApplication;
public AppModule(Application application) {
mApplication = application;
}
@Provides
@Singleton
Application providesApplication() {
return mApplication;
}
}
@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface NetComponent {
void inject(MainActivity activity);
// void inject(MyFragment fragment);
// void inject(MyService service);
}
实例化Component
时可调用:
mNetComponent = DaggerNetComponent.builder()
.appModule(new AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
.build();
至此,我们可见Component
维护了依赖图,而其中的依赖来源有:构造方法注入的对象,Component.Builder
的@BindsInstance
,以及Module
类中提供的依赖。
6. Module
上文提到了,Module
类是为依赖图中提供依赖的。一般从构造方法提供的依赖都有明确可调用的构造器才能够注入,而没有构造方法,例如从一些静态方法等方式获取的依赖就需要在Module
中定义了。另外还有一种情况,如果我们想注入的是接口或者是父类,而注入的是接口的实际实现或其子类,也需要在Module
中定义。下面通过Module
中使用的一些注解来进行解释。
@Provides
@Provides注
解允许我们在Module
里定义方法,方法传入的参数是依赖图中已存在的依赖对象,返回将是该方法提供给依赖图的依赖。@Provides
适用于需要由静态方法提供的依赖的情况,如:
@Module
class AppModule {
@Singleton @Provides
GithubService provideGithubService() {
return new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(new LiveDataCallAdapterFactory())
.build()
.create(GithubService.class);
}
...
}
另外需要通过调用依赖图中对象的某些方法才能获得的依赖
比如依赖图中有Context
对象,例如我们希望在依赖图中或者ContentResolver
对象,就可以定义方法:
@Module
class AppModule {
@Provides
ContentProvider provideUserDao(Context context) {
return context.getContentResolver();
}
当然,还有其他情况下需要用到Provides
注解,可以根据项目实际情况判断,就不举例了。
@Binds
@Binds
和@Provides
最大区别就是@Binds
只能修饰抽象方法,比如说当A1
类继承自A
,而在当前的依赖图中可以提供A1
的对象(如A1
已经可以通过构造方法注入到Component
中),而在被注入类中需要A的对象,那么就可以定义Bind
的抽象方法来将A1
作为A
的对象注入。再以上面AppComponent
为例,Component
实例化中通过Builder
可以获得Application
的对象,而如果依赖图中需要context
,就可以提供给他们这个Application
对象:
public abstract class ApplicationModule {
//expose Application as an injectable context
@Binds
abstract Context bindContext(Application application);
}
所以@Binds
解决了我们面向接口编程的需求。
当然这种情况也可以用@Provides
的有实体方法(方法实体是类型的强转),但@Binds
明显更加清晰。
@Qualifier
而更进一步,如果依赖图中有两个子类都实现了某一接口,而我们在注入时在不同的场景下需要用这两个的某个,该怎么做呢?这时候我们需要@Qualifier
的注解。
下面是一个实际的例子:
我们首先要定义两个InfoRepository
和RemoteInfoSource
都是数据源,都实现了InfoSource
接口, 分别表示本地数据源和云端的数据源(这种封装方式在MVP/MVVM
架构中非常常见)。为区分二者,我们首先要先定义两个注释,@Remote
表示远端数据,@Repository
表示本地仓库:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {}
注意,这两个Annotation都需要被@Qualifier
修饰,@Qualifier
为JSR-330
标准中的一部分。
之后,InfoRepository
和RemoteInfoSource
在通过构造方法注入的时候,各自的构造方法在除了@Inject
注释外还要加上刚刚定义的对应的Annotation
:
public class InfoRepository {
Context mContext;
@Repository
@Inject
InfoRepository(@NonNull Context context) {
mContext = context;
}
}
public class RemoteInfoSource {
Context mContext;
@Remote
@Inject
RemoteInfoSource(@NonNull Context context) {
mContext = context;
}
}
接下来在Module
中定义对应的@bind
的抽象方法,对应方法也需要加对应Annotation
:
@Module
abstract public class NumberInfoModule {
@Binds @Repository
abstract InfoSource provideLocalDataSource(InfoRepository dataSource);
@Binds @Remote
abstract InfoSource provideRemoteDataSource(RemoteInfoSource dataSource);
}
在使用时,如果我们需要Remote
的InfoSource
时就使用:
@Inject
@Remote
InfoSource mRemoteInfoSource;
或在构造方法注入时:
public Class TestClass {
@Inject
public TestClass(@Remote InfoSource cloudSource) {
this.mRemoteInfoSource= remoteInfoSource;
}
}
另外,@Qualifier
定义的注解可以设置参数,来标记不同的对象,如:
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Source {
String source() default "local";
}
就可以用@Source(soucrce = "remote")
和@@Source(soucrce = "repository")
代替上例中的@Remote
和@Repository
标签。
另外,@Named
标签是JSR-330
自带的一个@Qualifier
实现,我们可以直接用@Named
来起到和自定义@Qualifie
注解r相同的效果,对应上面例子,@Remote
和@Repository
的位置替换成@Named("Remote")
和@Named("Repository")
,通过参数来区分同样类型的不同对象。
而需要本地仓库时,对应注解换成@Repository
。
注意,在@Provides
注解的方法中,同样可以用@Qualifier
的标签。
小结:
这篇文章首先介绍了何为控制反转和依赖注入,并介绍java
依赖注入标准:JSR-330
。随后,本文介绍了通过Dagger2
构建依赖图的具体结构,着重介绍了如何将依赖注入到各个模块中,将对象增加到依赖图中的方法。介绍了Component
的作用以及接口的定义方式。最后介绍了Module
同@Provides
和@Binds
向依赖图提供依赖,并利用@Qualifier
限定注入的对象。
我们可以总结出有三种方式想依赖图提供依赖:通过Component.Builder()
的@BindInstance
方式,通过构造方法注入的对象以及有Module
的@Provides
和@Binds
提供的依赖。
Dagger2
在Android
应用中,最简单的情况是在Application
完成Component
的初始化,并Application
中用静态方法向外提供Component
实例,以让其他组件通过Component
完成依赖注入。
但更复杂的情况下,还需要不同生命周期的Component
来控制不同依赖图的生命,这就需要用到Scope
以及Component
间的依赖,以及Subcomponents
,这些也都是Dagger2
的重要概念,这将在下篇文章进行介绍。
九. 关键字
1. Scope
Scope
注解是JSR-330
标准中的,该注解是用来修饰其他注解的。 前篇文章提到的@Singleton
就是一个被Scope
标注的注解,是Scope
的一个默认实现。Scope
的注解的作用,是在一个Component
的作用域中,依赖为单例的。也就是说同一个Component
对象向各个类中注入依赖时候,注入的是同一个对象,而并非每次都new
一个对象。
在这里,我们再介绍自定义的Scope
注解,如:
@Scope
public @interface ActivityScope {
}
如上,ActivityScope
就是一个我们自己定义的Scope
注解,其使用方式与上篇文章中我们用Singleton
的用法类似的。顾名思义,在实际应用中Singleton
代表了全局的单例,而我们定义ActivityScope
代表了在Activity
生命周期中相关依赖是单例的。
Scope
的注解具体用法如下:
- 首先用其修饰
Component
- 如果依赖由
Module
的Provides
或Binds
方法提供,且该依赖在此Component
中为单例的,则用Scope
相关注解修饰Module
的Provides
和Binds
方法。 - 如果依赖通过构造方式注入,且该依赖在此
Component
中为单例的,则要用Scope
修饰其类。
我们通过如下例子详细说明,并进行测试和分析其原理:
以Singleton
这个Scope
是实现为例:
首先用它来修饰Component
类:
@Singleton
@Component
public interface AppComponent {
void inject(MainActivity mainActivity);
}
用通过构造方法注入依赖图的,用@Singleton
修饰其类:
@Singleton
public class InfoRepository {
private static final String TAG = "InfoRepository";
@Inject
InfoRepository() {
}
public void test() {
Log.d(TAG, "test");
}
}
实际注入到Activity
类中如下:
public class MainActivity extends Activity {
@Inject
InfoRepository infoRepositoryA;
@Inject
InfoRepository infoRepositoryB;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.getComponent().inject(this);
setContentView(R.layout.activity_main);
Log.d("test", "a:"+infoRepositoryA.hashCode());
Log.d("test", "b:"+infoRepositoryB.hashCode());
}
}
运行结果:
10-19 19:25:08.253 26699-26699/com.qt.daggerTest D/test: a:442589
10-19 19:25:08.254 26699-26699/com.qt.daggerTest D/test: b:442589
如上可见,两次注入的InfoRepository
对象为同一个。
如果将上面修饰InfoRepository
的@Singleton
注解去掉,结果是什么呢?经过测试如下:
10-19 19:23:00.092 23014-23014/com.qt.daggerTest D/test: a:442589
10-19 19:23:00.092 23014-23014/com.qt.daggerTest D/test: b:160539730
也就是说在不加@Singleton
注解时候,每次注入的时候都是new
一个新的对象。
注意,如果我们将上面所有的@Singleton
替换成我们自己的Scope
注解其结果也是一致的,如替换成上文的ActivityScope
。
下面,我们通过分析Dagger
自动生成的代码来分析如何实现单例的:
在注入类不加@Singleton
注解时,生成的DaggerAppComponent
类的initialize()
方法如下:
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.mainActivityMembersInjector =
MainActivity_MembersInjector.create(InfoRepository_Factory.create());
}
而加@Singleton
注解后的initialize()
方法变成了:
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.infoRepositoryProvider = DoubleCheck.provider(InfoRepository_Factory.create());
this.mainActivityMembersInjector =
MainActivity_MembersInjector.create(infoRepositoryProvider);
}
也是就是说提供InfoRepository
的InfoRepositoryProvider
替换成了DoubleCheck.provider(InfoRepository_Factory.create())
。用DoubleCheck
包装了原来对象的Provider
。DoubleCheck
顾名思义,应该是通过双重检查实现单例,我们看源码确实如此:
public final class DoubleCheck<T> implements Provider<T>, Lazy<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private DoubleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the provider
@Override
public T get() {
Object result = instance;
if (result == UNINITIALIZED) {
synchronized (this) {
result = instance;
if (result == UNINITIALIZED) {
result = provider.get();
/* Get the current instance and test to see if the call to provider.get() has resulted
* in a recursive call. If it returns the same instance, we'll allow it, but if the
* instances differ, throw. */
Object currentInstance = instance;
if (currentInstance != UNINITIALIZED && currentInstance != result) {
throw new IllegalStateException("Scoped provider was invoked recursively returning "
+ "different results: " + currentInstance + " & " + result + ". This is likely "
+ "due to a circular dependency.");
}
instance = result;
/* Null out the reference to the provider. We are never going to need it again, so we
* can make it eligible for GC. */
provider = null;
}
}
}
return (T) result;
...
}
其get
方法就用了DoubleCheck
方式保证了单例,其中还判断如果存在循环依赖的情况下抛出异常。
注意,Scope
只的单例是在Component
的生命周期中相关依赖是单例的,也就是同一个Component
对象注入的同类型的依赖是相同的。按上面例子,如果我们又创建了个AppComponent
,它注入的InfoRepository
对象与之前的肯定不是一个。
2. Component
间依赖
在Android
应用中,如果我们需要不止一个Component
,而Component
的依赖图中有需要其他Component
依赖图中的某些依赖时候,利用Component
间依赖(Component Dependency
)方式是个很好的选择。在新建Component
类时候可以在@Component
注解里设置dependencies
属性,确定其依赖的Component
。在被依赖的Component
中,如果要暴露其依赖图中的某个依赖给其他Component
,要显示的在其中定义方法,使该依赖对其他Component
可见如:
@Singleton
@Component(modules = AppModule.class)
public interface AppComponent {
Application application();
}
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
void inject(MainActivity mainActivity);
}
在ActivityComponent
中,就可以使用AppComponent
依赖图中暴露出的Application
依赖了。
非常清晰,Component
依赖(Component Dependency
)的方式适用于Component
只将某个或某几个依赖暴露给其他Component
的情况下。
3. subComponent
定义subComponent
是另一种方式使Component
将依赖暴露给其他Component
。当我们需要一个Component
,它需要继承另外一个Component
的所有依赖时,我们可以定义其为subComponent
。
具体用法如下:首先在父Component
的接口中定义方法,来获得可以继承它的subComponent
:
@Singleton
@Component(
modules = {AppModule.class}
)
public interface AppComponent {
UserComponent plus(UserModule userModule);
}
其次,其subComponent
被@SubComponent
注解修饰,如下:
@UserScope
@Subcomponent(
modules = {UserModule.class}
)
public interface UserComponent {
...
}
Dagger
会实现上面在接口中定义的plus()
方法,我们通过调用父Component
的plus
方法或者对应的subComponent
实例,具体如下:
userComponent = appComponent.plus(new UserModule(user));
这样,我们就获得了userComponent
对象,可以利用他为其他类注入依赖了。
注意,subComponent
继承了其父Component
的所有依赖图,也就是说被subComponent
可以向其他类注入其父Component
的所有依赖。
4. 用多个Component
组织你的Android
应用
前面几部分中,我们介绍了如何创建多个Component/subComponent
并使其获得其他Component
的依赖。这就为我们在应用中用多个Component
组织组织应用提供了条件。文章http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/ 提供了一个常用的思路:我们大概需要三个Component
:AppComponent
,UserComponent
,ActivityComponent
,如下:
上文介绍了,各个Component
要有自己的Scope
且不能相同,所以这几个Component
对应的Scope
分别为@Singleton
,@UserScop
,@ActivityScope
。也就是说,依赖在对应的Component
生命周期(同个Component
中)中都是单例的。而三个Component
的生命周期都不同:AppComponent
为应用全局单例的,UserComponent
的生命周期对应了用户登录的生命周期(从用户登录一个账户到用户退出登录),ActivityComponent
对应了每个Activity
的生命周期,如下:
Scopes lifecycle 摘自http://frogermcs.github.io/dependency-injection-with-dagger-2-custom-scopes/
在具体代码中,我们通过控制Component
对象的生命周期来控制依赖图的周期,以UserComponent
为例,在用户登录时候创建UserComponent
实例,期间一直用该实例为相关类注入依赖,当其退出时候将UserComponent
实例设为空,下次登录时候重新创建个UserComponent
,大致如下:
public class MyApplication extends Application {
...
private void initAppComponent() {
appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.build();
}
public UserComponent createUserComponent(User user) {
userComponent = appComponent.plus(new UserModule(user));
return userComponent;
}
public void releaseUserComponent() {
userComponent = null;
}
public AppComponent getAppComponent() {
return appComponent;
}
public UserComponent getUserComponent() {
return userComponent;
}
}
createUserComponent
和releaseUserComponent
在用户登入和登出时候调用,所以在不同用户中用的是不同UserComponent
对象注入,注入的依赖也不同。而AppComponent
对象只有一个,所以其依赖图中的依赖为全局单例的。而对于ActivityComponent
,则可以在Activity
的onCreate()
中生成ActivityComponent
对象来为之注入依赖。
5.多Component
情况下Scope
的使用限制
Scope
和多个Component
在具体使用时候有一下几点限制需要注意:
Component
和他所依赖的Component
不能公用相同的Scope
,每个Component
都要有自己的Scope
,编译时会报错,因为这有可能破坏Scope
的范围。这种情况下编译会报错:
Error:(21, 1) 错误:com.qt.daggerTest.AppComponent depends on scoped components in a non-hierarchical scope ordering:
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.AppComponent
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.ActivityComponent
@Singleton
的Component
不能依赖其他Component
。这从意义和规范上也是说的通的,我们希望Singleton
的Component
应为全局的Component
。这种情况下编译时会报错:
Error:(23, 1) 错误: This @Singleton component cannot depend on scoped components:
@Singleton com.qt.daggerTest.AppComponent
- 无
Scope
的Component
不能依赖有Scope
的Component
,因为这也会导致Scope
被破坏。这时候编译时会报错:
Error:(20, 2) 错误: com.qt.daggerTest.ActivityComponent (unscoped) cannot depend on scoped components:
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.AppComponent
Module
以及通过构造函数注入依赖图的类和其Component
不可有不相同的Scope
,这种情况下编译时会报:
Error:(6, 1) 错误: com.qt.daggerTest.AppComponent scoped with @com.qt.daggerTest.ActivityScope may not reference bindings with different scopes:
@Singleton class com.qt.daggerTest.InfoRepository
十. 总结
1. 注意事项
-
在
Dagger 2
中,Scope
机制可以使得在scope
存在时保持类的单例。 -
@ApplicationScope
的实例与Applicaiton
对象的生命周期一致。 -
@ActivityScope
保证引用与Activity
的生命周期一致(举个例子我们可以在这个Activity
中持有的所有fragment
之间分享一个任何类的单例)。 -
统一管理依赖于
AppComponent
的Module
添加的中间件
@Module
public abstract class ActivityModule { //主意为抽象类
@ContributesAndroidInjector(modules = MainActivityFragmentModule.class)//主意这个地方,只要这个Activity有Fragment就需要注明它的FragmentModule
abstract MainActivity contributeMainActivity();
@ContributesAndroidInjector
abstract RegisterActivity contributeRegisterActivity();
@ContributesAndroidInjector
abstract LoginActivity contributeLoginActivity();
}
-
你可以使用
DaggerActivity
,DaggerFragment
,DaggerApplication
来减少Activity/Fragment/Application
类里面的模板代码。 -
同样的,在
dagger
的component
中,你也可以通过AndroidInjector<T>
去减少模板代码。 -
在使用
dagger
的fragment
或者activity
中要记得调用AndroidInjection.inject()
方法。 -
同样的,如果你想要在
v4
包里面的fragment
中使用Injection
,你应该让你的activity
实现HasSupportFragmentInject
接口并且重写fragmentInjector
方法。 -
最近,我把这些相关代码移到
BaseActivity
和BaseFragment
。因为与其在每个activity
中声明这些,还不如把共同的代码放到基类里面。
2. 例子
DaggerAppCompatActivity、DaggerFragment
@Beta
public abstract class DaggerAppCompatActivity extends AppCompatActivity implements HasFragmentInjector, HasSupportFragmentInjector {
@Inject DispatchingAndroidInjector<Fragment> supportFragmentInjector;
@Inject DispatchingAndroidInjector<android.app.Fragment> frameworkFragmentInjector;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return supportFragmentInjector;
}
@Override
public AndroidInjector<android.app.Fragment> fragmentInjector() {
return frameworkFragmentInjector;
}
}
从上面的代码可以看出 DaggerAppCompatActivity
跟我们自己写的 Activity
并没有多大的区别,所以可以让我们的 Activity
继承 DaggerAppCompatActivity
的方式来减少模板代码:
public class DetailActivity extends AppCompatActivity implements HasSupportFragmentInjector, DetailView {
@Inject
DispatchingAndroidInjector<Fragment> fragmentDispatchingAndroidInjector;
@Inject
DetailPresenter detailPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
AndroidInjection.inject(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
}
@Override
public void onDetailLoaded() {}
@Override
public AndroidInjector<Fragment> supportFragmentInjector() {
return fragmentDispatchingAndroidInjector;
}
}
让我们的 DetailActivity
继承 DaggerAppCompatActivity
类,这样我们就不用让 DetailActivity
类实现 HasSupportFragmentInjector
接口以及重写方法:
public class DetailActivity extends DaggerAppCompatActivity implements DetailView {
@Inject
DetailPresenter detailPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_detail);
}
@Override
public void onDetailLoaded() {}
}
AndroidInjector
@Component(modules = {
AndroidInjectionModule.class,
AppModule.class,
ActivityBuilder.class})
public interface AppComponent {
@Component.Builder
interface Builder {
@BindsInstance Builder application(Application application);
AppComponent build();
}
void inject(AndroidSampleApp app);
}
build()
和 seedInstance()
方法已经在 AndroidInjector.Builder
抽象类中定义了,所以我们的 Builder
类可以通过继承 AndroidInjection.Builder<Application>
来去掉上面代码中 application()
和 build()
这两个方法。
同样的,AndroidInjector
接口中已经有 inject()
方法了。所以我们可以通过继承 AndroidInjector<Application>
接口(接口是可以继承接口的)来删除 inject()
方法。
AppComponent
@Component(modules = {
AndroidSupportInjectionModule.class,
AppModule.class,
ActivityBuilder.class})
interface AppComponent extends AndroidInjector<AndroidSampleApp> {
@Component.Builder
abstract class Builder extends AndroidInjector.Builder<AndroidSampleApp> {}
}
你有没有意识到我们的 modules
属性也改变了?
我从
@Component
注解的modules
属性中移除了AndroidInjectionModule.class
并且添加了AndroidSupportInjectionModule.class
。这是因为我们使用的是支持库(v4库)的Fragment
。而AndroidInjectionModule
是用来绑定app
包的Fragment
到dagger
。所以如果你想在v4.fragment
中使用注入,那么你应该在你的AppComponent modules
中添加AndroidSupportInjectionModule.class
。
Application
我们改变了AppComponent
的注入方式。那么 Application
类需要做什么改变?
跟
DaggerActivity
和DaggerFragment
一样,我们也让Application
类继承DaggerApplication
类。
之前的 Application
类的代码如下:
public class AndroidSampleApp extends Application implements HasActivityInjector {
@Inject
DispatchingAndroidInjector<Activity> activityDispatchingAndroidInjector;
@Override
public void onCreate() {
super.onCreate();
DaggerAppComponent
.builder()
.application(this)
.build()
.inject(this);
}
@Override
public DispatchingAndroidInjector<Activity> activityInjector() {
return activityDispatchingAndroidInjector;
}
}
修改后代码如下:
public class AndroidSampleApp extends DaggerApplication {
@Override
protected AndroidInjector<? extends AndroidSampleApp> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}