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;
    }
}

BeanForDaggerBean的区别:

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中(以及BeanComponentBeanForDagger中),我们并没有new BeanForDagger的对象。
也就是说, Dagger帮助我们完成了创建BeanForDagger对象的工作。

2.4. Dagger创建对象的过程

这一节我们通过最简单的描述来简单介绍一下Dagger是如何帮助我们创建BeanForDagger对象的。

首先,在ActivitytestDagger方法中,多了这么一句:

DaggerBeanComponent.create().inject(this);  
  • DaggerBeanComponentDagger根据BeanComponent自动生成的,每一个XXXComponent都会生成对应的DaggerXXXComponent文件。
  • 当调用inject()时,DaggerBeanComponent就会扫描inject方法中的参数(当前就是Activity)中@Inject标记的变量,对于当前的Activity来说,只有mBeanForDagger变量被标记。
  • 找到这个需要被注入的变量后,发现他的类型是BeanForDagger,接下来,Dagger就会去搜索BeanForDagger的类,然后找到该类中@Inject标记的构造方法,并用该构造方法来创建对象。

创建过程的简单步骤:

  1. 通过DaggerXXXComponentInject()触发注入过程
  2. 搜索目标类中用@Inject标识的需要注入的对象
  3. 找到需要注入对象后,寻找该对象中用@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。

上面的例子虽然介绍了有参数和无参数的注入,但他们都有两个局限:

  1. 需要修改构造方法,添加@Inject的标识,但是对于一些jar包中的方法,或者第三方API中提供的方法,我们无法修改其源码;
  2. 虽然构造方法中可以传递参数,但是实际上对这个参数也是有要求的,这个参数虽然会递归创建,但递归的最后一层的构造方法中还是无法传参的;

四. 将对象注入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.1Component改造,发现添加了modules = BeanModule.class,这里面的BeanModule就是我们要添加的文件,文件名随便起,只需要前后对应即可,我们这里就用BeanModule来介绍:

@Module  
public class BeanModule {
    @Provides
    public BeanNeedParam providerBean() {
        BeanNeedParam bean = new BeanNeedParam("BeanWithParam");
        return bean;
    }
}

这就是Module的全部内容,里面有两点需要特别注意:

  1. 该文件引入了@Module@Provides两个新的注释
  2. 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. 注入过程介绍

  1. 通过之前的介绍,在Activity中通过DaggerBeanComponent.create().inject(this)触发了Dagger的注入机制。
  2. 然后Dagger扫描当前Activity有个BeanNeedParam的对象需要被注入:
    //带参数的注入
    @Inject BeanNeedParam mBeanNeedParam;  
    
  3. 然后按照之前的介绍,Dagger将会在BeanNeedParam的类中搜索使用@Inject注释了的构造方法,并用他来创建BeanNeedParam对象。
  4. 但是这里的BeanNeedParam中的构造方法没有使用@Inject来注释,说明无法直接注入。
  5. 接下来Dagger就会在Component中搜索被注册的modules有哪些,结果找到了BeanModule.class
  6. 然后在BeanModule.class中搜索同时具备如下条件的注入类:
    1. 通过@Provides注释的方法
    2. 该方法必须是public属性
    3. 该方法的返回值是BeanNeedParam
  7. 结果就找到了providerBean()方法,然后就调用该方法并顺利拿到了BeanNeedParam对象,然后交给ActivitymBeanNeedParam使用。
  8. 请注意,DaggerModule中搜索目标类时,Module中提供的方法名无关,只和返回值有关,比如下面两种写法是完全等效的,都可以实现BeanNeedParam的注入:
@Provides
public BeanNeedParam providerBean() {
    ......
}
和:
@Provides
public BeanNeedParam dushaofeng() {
    ......
}

整个过程可以归纳为以下步骤:
1. Activity中通过DaggerXXXComponentInject()触发注入过程
2. DaggerActivity中搜索用@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";
    }
}

也就是说,DaggerModule中执行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使用。
下面我们分别获取mBeanNeedParamAAAmBeanNeedParamBBB中的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);
}

这里我们看到其实是和刚才介绍多参数的情况是一样的,只不过我们用TypeStringTypeInt来区分不同的构造方法而已。

2.2. 改造Activity

同理,我们在Activity中也通过TypeStringTypeInt的注释来获取不同的BeanNeedParam对象:

//不同构造方法的注入  
@Named("TypeString")
@Inject
BeanNeedParam mBeanNeedParamString;

//不同构造方法的注入  
@Named("TypeInt")
@Inject
BeanNeedParam mBeanNeedParamInt;

这样一来,mBeanNeedParamString中的mNumber就会使用prividerBeanWithString中传递的"String"值,而mBeanNeedParamInt中的mNumber就会使用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中的mBeanNeedParamCCmBeanNeedParamDD两个变量都需要用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

我们看到这两个对象的地址不同,这表明,虽然mBeanNeedParamCCmBeanNeedParamDD都是用BeanNeedParam进行注入,但是它们是分别单独注入的,每个对象都是独立的。
如果我们想要实现单例模式该如何办呢?比如我们想要mBeanNeedParamCCmBeanNeedParamDD是同一个对象该怎么办呢?
这时候就可以用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

它们打印出来地址相同,这说明mBeanNeedParamCCmBeanNeedParamDD是同一个对象,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对象,分别是mBeanNeedParamEEmBeanNeedParamFF,按照之前的设计,它们都是使用@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();

运行一下,mBeanNeedParamCCmBeanNeedParamDDmBeanNeedParamEEmBeanNeedParamFF四个对象的地址分别如下:

//输出:
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中的mBeanNeedParamCCmBeanNeedParamDD是同一个对象,而OtherClass中的mBeanNeedParamEEmBeanNeedParamFF是另外的一个对象。
这说明,@Singleton的单例作用,只对同一次的inject()有效。

3. Singleton的单例作用范围

那么再进一步来分析,inject()方法实际上是通过DaggerBeanComponent.create()创建的BeanComponent对象的一个成员方法,也就是说:

DaggerBeanComponent.create().inject(this);  

实际上就是:

BeanComponent beanComponent = DaggerBeanComponent.create();  
beanComponent.inject(this);  

那么这个单例的作用范围究竟是在beanComponent的范围还是inject()的范围呢?
下面我们将MainActivityOtherClass用同一个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范围内的单例模式。

具体实现步骤:

  1. 在应用的Application中创建Component对象,并将其暴露出来:
public class DaggerApplication extends Application {
    private BeanComponent mBeanComponent;
    
    @Override
    public void onCreate() {
        super.onCreate();
        mBeanComponent = DaggerBeanComponent.create();
    }
    
    public BeanComponent getBeanComponent() {
        return mBeanComponent;  
    }
}
  1. 然后就可以在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是连接注入类和目标类的桥梁,那么最简单的结构应该是这样的:

  1. Application负责创建全局的单例或者非单例注入类的Component对象
  2. ActivityFragment在继承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用来连接ApplicationModuleApplication

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    void inject(DaggerApplication application);
    
    //说明将BeanForApplication开放给其他Component使用
    ApplicationBean providerAppBean();
}

在这里请注意两点:

  1. 由于我们设计要将ApplicationBean作为单例注入,因此ApplicationComponent也需要标记@Singleton标识
  2. 我们在ApplicationComponent中提供了一个返回值为ApplicationBean对象的方法声明,它的作用是将该Component中的ApplicationBean对象暴露给其他Component使用,相当于AIDL语言中的方法声明。

1.4. 注入Application

我们需要在Application中完成两个任务:

  1. ApplicationBean注入到Application内部
  2. 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对象

我们再创建一个ActivityBean对象用于观察注入情况:

public class ActivityBean {
    private String name = null;
    
    public ActivityBean() {
    }
    
    public String getAppBeanName() {
        return name;
    }
}

1.6. 准备ActivityModule对象

ActivityModule应该提供ActivityBean的注入方式:

@Module
public class ActivityModule {
    @Provides
    ActivityBean providerActivityBean() {
        return new ActivityBean();
    }
}

1.7. 准备ActivityComponent对象

我们要在ActivityComponent中继承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的写法有三处与之前的写法不同的地方:

  1. 添加了ForActivity的修饰,而这个ForActivity就是我们自定义的Scope的一种,根据之前我们的介绍,他的作用和Singleton是一样的,用于限制该Component的使用范围:
@Scope
@Retention(RUNTIME)
public @interface ForActivity {
}

为什么要添加这个修饰呢?

因为当前Component所继承的ApplicationComponent中包含Singleton的注释,所以ApplicationComponent的子类Component的作用范围不能高于ApplicationComponent的作用范围,因此需要对ActivityComponent也添加Scope的限定。

  1. Component中多了dependencies = ApplicationComponent.class的注释,它的作用就是告诉Dagger当前Component依赖于ApplicationComponent,在查找注入类的时候不仅要在ActivityModule中查找,还需要去ApplicationComponent中的Module中查找。

  2. 我们提供了两个inject()方法,作用是要将该Component同时注入到两个对象中,这在之前的介绍中使用过。

1.8. 设计Activity对象

我们接下来就要在Activity中同时注入ActivityBeanApplicationBean对象了,并且ApplicationBean还是全局单例的模式,为了扩展测试,我们在Activity中还创建了一个OtherClass,也将ActivityBeanApplicationComponent都注入进去进行观察:

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中注入的mAppBean1mAppBean2以及Activity中注入的applicationBean1applicationBean2还有OtherClass中注入的applicationBean1applicationBean2这六个对象的地址都是95c5354

分析:

  1. ActivityOtherClass中我们可以获取到ApplicationBean对象,说明我们当前的注入方式完成了ActivityApplication继承Component进行注入的任务
  2. 我们不仅在APP的全局都获取到了ApplicationBean对象,而且得到的都是单例对象,这说明我们在ApplicationModule中对ApplicationBean进行单例注入的方式在全局都是有效的

Activity中的activityBeanOtherClass中的activityBean对象地址不同:

ActivityBean对象在Activity中和OtherClass中分别注入了两次,所以这两次注入是独立的,它们注入的ActivityBean对象是不同的

至此,该注入方式我们就介绍完毕,下面我们来介绍另一种继承的方式。

2. 使用Subcomponent的方式进行继承注入

2.1. 如何注入

该方式和上面的方式区别之处只有三个地方:

  1. 改造ActivityComponent对象
    我们需要先来改造ActivityComponent对象,也就是ActivityComponent,需要将其改写为如下的方式:
@ForActivity
@Subcomponent(modules = ActivityModule.class)
public interface ActivityComponent {
    void inject(MainActivity activity);
    
    void inject(MainActivity.OtherClass otherClass);
}

它与之前的方式的区别:

  1. 不再使用@Component而使用@Subcomponent来注释
  2. 删除了"dependencies = ApplicationComponent.class"语句

2、改造ApplicationComponent对象

然后我们来改造ApplicationComponent对象也就是ApplicationComponent,将其改造成如下方式:

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
    //注入DaggerApplication
    void inject(DaggerApplication application);
    
    //说明将BeanForApplication开放给其他Component使用
    ApplicationBean providerAppBean();
    
    ActivityComponent activityComponent();
}

这里的改造只是多了一句声明:ActivityComponent activityComponent()

3. 改造Activity中的注入方式

我们还需要改造ActivityOtherClass中的注入方式,改造成如下方式(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. dependenciesSubcomponent注入方式的区别

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 {  
    ......  
}  

对比中我们发现,dependenciesComponent强调的是在子类Component依赖于某个Component(子类为主角),而Subcomponent中强调的则是在父类Component中提供某个子类的Component(父类为主角)。

4. 如何选择两种继承方式

那么该如何选择这两种继承方式呢?
Stackoverflow中就有人提出了这样的问题(提问),简单理解就是:

dependencies方式让Component之间更加独立,结构更加清晰,也更利于解耦。

八. Dagger2结构

1. 控制反转和依赖注入

首先,我们就要先介绍控制反转和依赖注入的概念了。
如果A类依赖B类的对象通常我们会怎么做呢?

public class A {
    private B b;
    public A() {
        b = new B();
    }
}

这是最通常的做法:在类Anew一个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的基本结构

Dagger Injections Overview
使用dagger2进行依赖注入时,整个依赖关系如图。在dagger2Component便是注入器,它维护了依赖图并向其他对象注入依赖。依赖图中的对象由Module提供,或是通过构造方法注入来获得,一些具体细节将在稍后介绍。

4. 用Dagger2向类注入依赖

通过Dagger2将依赖注入到某个类中的时候,是用@Inject注解来实现的。@Inject注解为JSR-330标准中的一部分。
注入方式有两种:即@Inject可以标记域也可以标记构造方法。

  1. 域注入
public class MainActivity extends Activity {
   @Inject 
   SharedPreferences mSharedPreferences;

   public void onCreate(Bundle savedInstance) {
       InjectorClass.inject(this);
   } 

注意,这里需要在适当的时机,如在组件的onCreate()中调用Componentinject()方法,此时将依赖注入到该对象中。此外,被@Inject标记的域不可为private,因为它需要有注入器Component进行赋值。

  1. 构造方法注入
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中的ComponentDagger2会在编译阶段生成其实例类:DaggerXXXComponent,以这个为例就是生成DaggerAppComponent,其实现了APPComponent

我们来看这个接口的各个部分:

  1. @Singleton注解:这是一个scope的注释,dagger2scope机制是为单利提供了生命周期的概念。@Singleton表明了依赖的生命长度和Application生命长度一致。另外,@SingletonJSR-330标准中的一部分,另外我们还可以自定义scope。有关scope的概念会在下一篇文章中介绍。
  2. Module:在上面@Component中我们指定了Module类,Module是为依赖图提供具体依赖的对象的,也就是我们在Module中我们告诉Dagger2框架当我们需要某个类的对象时,我们该如何获得。再回到最上面的图,其中AC都是由Module提供的。也就是除了构造方法注入的类以外,其他的依赖需要有Module提供。后面会详细介绍。
  3. inject方法:Component在接口中,我们定义了inject的方法。在前面介绍通过域注入时,我们在组件的onCreate()方法中调用了Componentinject方法。该方法表明了我们可以将依赖图中的依赖注入到什么类中,该类(组件)在适当的时候(如onCreate())调用inject方法,完成依赖注入。inject方法由我们写接口的时候定义,编译阶段框架实现该方法。
  4. 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的注解。
下面是一个实际的例子:
我们首先要定义两个InfoRepositoryRemoteInfoSource都是数据源,都实现了InfoSource接口, 分别表示本地数据源和云端的数据源(这种封装方式在MVP/MVVM架构中非常常见)。为区分二者,我们首先要先定义两个注释,@Remote表示远端数据,@Repository表示本地仓库:

@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {}
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Repository {}

注意,这两个Annotation都需要被@Qualifier修饰,@QualifierJSR-330标准中的一部分。
之后,InfoRepositoryRemoteInfoSource在通过构造方法注入的时候,各自的构造方法在除了@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);
}

在使用时,如果我们需要RemoteInfoSource时就使用:

@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提供的依赖。

Dagger2Android应用中,最简单的情况是在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的注解具体用法如下:

  1. 首先用其修饰Component
  2. 如果依赖由ModuleProvidesBinds方法提供,且该依赖在此Component中为单例的,则用Scope相关注解修饰ModuleProvidesBinds方法。
  3. 如果依赖通过构造方式注入,且该依赖在此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);
}

也是就是说提供InfoRepositoryInfoRepositoryProvider替换成了DoubleCheck.provider(InfoRepository_Factory.create())。用DoubleCheck包装了原来对象的ProviderDoubleCheck顾名思义,应该是通过双重检查实现单例,我们看源码确实如此:

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()方法,我们通过调用父Componentplus方法或者对应的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/ 提供了一个常用的思路:我们大概需要三个ComponentAppComponentUserComponentActivityComponent,如下:在这里插入图片描述

上文介绍了,各个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;
 }
}

createUserComponentreleaseUserComponent在用户登入和登出时候调用,所以在不同用户中用的是不同UserComponent对象注入,注入的依赖也不同。而AppComponent对象只有一个,所以其依赖图中的依赖为全局单例的。而对于ActivityComponent,则可以在ActivityonCreate()中生成ActivityComponent对象来为之注入依赖。

5.多Component情况下Scope的使用限制

Scope和多个Component在具体使用时候有一下几点限制需要注意:

  1. 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
  1. @SingletonComponent不能依赖其他Component。这从意义和规范上也是说的通的,我们希望SingletonComponent应为全局的Component。这种情况下编译时会报错:
Error:(23, 1) 错误: This @Singleton component cannot depend on scoped components:
@Singleton com.qt.daggerTest.AppComponent
  1. ScopeComponent不能依赖有ScopeComponent,因为这也会导致Scope被破坏。这时候编译时会报错:
Error:(20, 2) 错误: com.qt.daggerTest.ActivityComponent (unscoped) cannot depend on scoped components:
@com.qt.daggerTest.ActivityScope com.qt.daggerTest.AppComponent
  1. 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. 注意事项

  1. Dagger 2中,Scope机制可以使得在scope存在时保持类的单例。

  2. @ApplicationScope的实例与Applicaiton对象的生命周期一致。

  3. @ActivityScope保证引用与Activity的生命周期一致(举个例子我们可以在这个Activity中持有的所有fragment之间分享一个任何类的单例)。

  4. 统一管理依赖于AppComponentModule添加的中间件

@Module
public abstract class ActivityModule { //主意为抽象类
	@ContributesAndroidInjector(modules = MainActivityFragmentModule.class)//主意这个地方,只要这个Activity有Fragment就需要注明它的FragmentModule
	abstract MainActivity contributeMainActivity();
	
	@ContributesAndroidInjector
	abstract RegisterActivity contributeRegisterActivity();
	
	@ContributesAndroidInjector
	abstract LoginActivity contributeLoginActivity();
}
  1. 你可以使用DaggerActivityDaggerFragmentDaggerApplication 来减少 Activity/Fragment/Application类里面的模板代码。

  2. 同样的,在 dagger 的 component 中,你也可以通过 AndroidInjector<T> 去减少模板代码。

  3. 在使用 dagger 的 fragment 或者 activity 中要记得调用 AndroidInjection.inject() 方法。

  4. 同样的,如果你想要在 v4 包里面的 fragment 中使用 Injection,你应该让你的 activity 实现 HasSupportFragmentInject 接口并且重写 fragmentInjector 方法。

  5. 最近,我把这些相关代码移到 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);
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值