文章

Dagger

Dagger

依赖注入概念

依赖注入(Dependency Injection),简称 DI,又叫控制反转(Inversion of Control),简称 IOC。简言之,目标类(需要进行依赖初始化的类)中所依赖的其他的类的初始化过程,不是通过手段编码方式创建,而是通过技术手段,把其他类已经初始化好的实例自动注入到目标类。Dagger2 就是实现依赖注入的一种技术手段。

Dagger2 实质: 就是帮我们写工厂代码,当然比工厂模式更加强大

依赖注入概念

1、依赖概念

一个类的实例需要另一个类的实例进行协助时。传统的设计,通常由调用者来创建被调用者的实例。

如:一个类 ClassA 中,有一个类 ClassB 的实例,则称 ClassA 对 ClassB 有一个依赖。依赖在构造方法中直接 hard init。

1
2
3
4
5
6
public class ClassA {
    ClassB classB;
    public ClassA(){
       classB = new ClassB();
    }
}

2、依赖注入

除了上面的传统依赖方式。创建被调用者不再由调用者创建实例,创建被调用者的实例工作由 IOC 容器来完成,然后注入到调用者中,这个叫做依赖注入。

  • 构造方法注入

将 classB 对象作为 ClassA 构造方法的一个参数传入,在调用 ClassB 的构造方法时已经初始化好了 ClassA 对象了。这种非自己主动初始化依赖,而通过外部传入依赖的方式,称之为依赖注入。

1
2
3
4
5
6
public class ClassA {
    ClassB classB;
    public ClassA(ClassB classB){
       this.classB = classB;
    }
}
  • setter 注入
  • 接口注入

Dagger2

Dagger2 介绍

Dagger2 使用注解来实现依赖注入,但它利用 APT(Annotation Process Tool) 在编译时生成辅助类,这些类继承特定父类或实现特定接口,程序在运行时 Dagger2 加载这些辅助类,调用相应接口完成依赖生成和注入。没有用到运行时反射,对性能影响很小。

取名

Dagger 这个库的取名不仅仅来自它的本意 “ 匕首 “,同时也暗示了它的原理。Jake Wharton 在对 Dagger 的介绍中指出,Dagger 即 DAG-er,这里的 DAG 即数据结构中的 DAG——有向无环图 (Directed Acyclic Graph)。也就是说,Dagger 是一个基于有向无环图结构的依赖注入库。

优点

  1. 将类的初始化以及类之间的依赖关系集中在一处处理
  2. 有大量类的初始化,以及类之间的依赖关系很复杂的时候

Reference

Dagger2 之注解详解

Dagger2 中的注解介绍

对象之间的互相依赖可以想象成一个有向无环图。描述这个图,可以像 Spring 框架一样用一个配置文件来描述依赖关系,Dagger2 用注解来描述依赖关系,更加的直接清晰。

Dagger2 中的注解

1、@Inject 注入标记
  • 说明/作用
  1. @Inject 注解标记在一个属性上,表示该属性需要依赖(表现在目标类中需要依赖被注入一个对象)
  2. @Inject 注解标记在一个构造方法上,表示该构造方法可以提供依赖。
  • 注意
  1. 注解在类的构造方法上,这个类的构造方法只能有一个有 @Inject
  2. 注解在类的属性上,该属性不能 private 修饰
  3. 注解在构造方法上,如果注解的构造方法 AClass() 是带参数 BClass 的,那么会去 Module 类中检查是否有返回 BClass 参数的方法,或者 BClass 是否有被@Inject 注解的构造函数,如果没有找到会在编译期报错(这在编译期保证了对象之间的依赖被满足)
1
2
3
4
5
6
 public class AClass{
    @Inject
    public AClass(BClass bClass){
        //do something
    }
 }
  • @Inject 注入构造方法时弊端(那就需要@Provides 来注解)
  1. 接口是没有构造方法的,不能对构造方法进行注解
  2. 第三方库提供的类,我们无法修改源码,也不能注解它们的构造方法
  3. 有些类需要提供统一的生成方法 (一般会同时私有化构造方法) 或需要动态选择初始化的配置,而不是使用一个单一的构造方法
2、@Component 注入器
  • 说明/作用
    也叫注入器(Injector),@Component 是目标类中所依赖的其他类与其他类的构造方法之间的一个桥梁;把目标类依赖的实例注入目标类中,来初始化目标类中的依赖;@Component 一端连接目标类(@Inject),另一端连接目标类依赖的实例(@Module),它把目标类依赖实例注入到目标类中,管理好 @Module@Component 中的 modules 属性可以把@Module 加入到@Component,modules 可以加入多个@Module;@Module 提供依赖,@Inject 请求依赖,@Component 就是联系这两者的纽带。Component 主要说明 4 件事:
  1. 谁来提供依赖
  2. 该 Component 依赖哪些其他的 Component
  3. 该 Component 为谁提供依赖注入
  4. 该 Component 可以提供哪些依赖
  • 案例说明
1
2
3
4
5
6
@Singleton
@Component(modules = {AppModule.class}dependencies = {xxxComponent.class})
public interface AppComponent{
  void injectMyActivity myActivity);
  public MyApplication getApplication();
}
  1. modules 参数{}里的类就表示提供依赖的 Module 类(可以有多个),也就是用 @Module 注解的类
  2. dependencies 表示该 Component 提供的注入类的构造函数中,还依赖其他 @Component 提供的一些类
  3. 有参无反的方法指明该参数为 Component 注入的目标,比如本例中,就说明 MyActivity 中需要 AppComponent 提供的依赖
  4. 有反无参的方法指明该 Component 可以提供哪些依赖,或者说暴露哪些依赖。因为这里可能会有疑问,Component 可提供的依赖不就是 Module 里的那些吗?确实没错,但是 Component 也可以有选择的只对外界暴露它的一部分能力,并不一定会声明所有的在 Module 里定义的类。
  5. 这个接口可以理解成一份声明,告诉 Dagger 哪些对象可以被注入,以及谁(Module 类中被@Provides 注解的方法)来提供这些依赖。需要注意,只有在 Component 接口中声明了的类,才会被暴露给依赖它的其他 Component。也就是说,Module 类中提供的依赖,并不一定都会在 Component 中声明。
  • 工作原理
    @Component 需要引用目标类的实例,查找目标类中用 @Inject 注解标注的属性,查找到相应的属性后,接着查找该属性对应的用 @Inject 标注的构造方法,剩下的工作就是初始化该属性的实例并把实例进行赋值。
  • 注意
  1. @Component 修饰的必须是接口或抽象类。Dagger2 框架将自动生成 @Component 的实现类,对应类名 DaggerXXX(XXX 为你的 Component 类)。添加的注入方法一般使用 inject() 方法名,需要一个参数对应为需要注入依赖的容器的实例。
  2. 如果 Component 中的某个对象具有 Scope(Module 中的一个方法具有 Scope),那么该 Component 也具有 Scope。
  • 疑问?
    用@Inject 和@Component 就可以注入了;但如果需要注入第三方的类库,第三方的类库又不能修改,怎么把@Inject 注解加入到这些类中呢?那就是@Module 作用了。
3、@Module 依赖类容器
  • 说明/作用
    @Module 就是一个简单的工厂模式,里面的方法基本都是创建 _ 目标类所依赖的其他类的实例 _ 的方法
    真正提供依赖的类。一个类用@Module 注解修饰,这样 Dagger 在构造类的实例的时候,就知道从哪里去找到需要的依赖。Module 其实就是一个依赖的制造工厂。其实 Module 就是完成各种对象的实例化的一个集合。类里面可以包含很多 @Provides 注解的方法,每个方法对应一种对象的创建。Module 类可以理解成一个用来组织对象创建方法的容器。
  • 注意
    一个完整的 Module 必须拥有@Module 与@Provides 注解。
  • 疑问?
    把@Module 中各种创建类的实例方法与目标类中的@Inject 注解标记的依赖产生关联,那就是@Providers 注解的作用了。
4、@Provides 提供创建依赖的类或参数
  • 说明/作用
    @Module 中创建类的实例方法用@Providers 进行注解,@Component 在搜索到目标类中用@Inject 注解标注的属性后,@Component 就会去@Module 中查找用@Providers 标注的对应的创建类的实例方法
  • 注意
    写在@Module 中某些方法上,表示这个方法用来提供依赖对象的特殊方法;
    为@Provides 方法添加输入参数,Module 中@Provides 方法可以带输入参数,其中参数由 Module 中的其他 Provides 方法提供,或者自动调用构造方法;如果找不到@Provides 方法提供对应参数的对象,自动调用带@Inject 参数的构造方法生成对应对象
5、@Qualifier 限定符
  • 来由
    创建依赖注入类实例有 2 个纬度:
  1. 通过@Inject 注解标注的构造方法来创建
  2. 通过@Module 工厂模式来创建
    这 2 个纬度是有优先级的,@Component 会首先从@Module 维度中查找类实例,若找到就用@Module 维度创建类实例,并停止查找@Inject 维度。否则才是从@Inject 维度查找类实例。所以创建类实例级别@Module 维度要高于@Inject 维度。
  • 依赖注入迷失
    **依赖注入迷失: **基于同一个维度条件下,若一个类的实例有多种方法可以创建出来,那注入器(Component)应该选择哪种方法来创建该类的实例呢?
  • @Qualifier 作用
    给不同的创建依赖类实例的方法用标识进行标注;同时用要使用的创建依赖类实例方法的标识目标类相应的实例属性进行标注。那这样我们的问题就解决了,提到的标识就是 Qualifier 注解,当然这种注解得需要我们自定义。
  • 注意
    dagger2 在发现依赖注入迷失时在编译代码时会报错。
6、@Scope
  • 介绍
    Dagger2 可以通过自定义@Scope 注解,来限定通过@Module 和@Inject 方式创建的依赖类实例的生命周期与目标类的生命周期相同。或者更好的管理创建的依赖类实例的生命周期。
  • 作用
  1. 更好的管理 Component 之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定义的@Scope 注解标注这些 Component,这些注解最好不要一样,这样可以更好的体现出 Component 之间的组织方式。还有编译器检查有依赖关系或包含关系的 Component,若发现有 Component 没有用自定义@Scope 注解标注,会报错。
  2. 更好的管理 Component 与 Module 之间的匹配关系,编译器会检查 Component 管理的 modules,若发现标注 Component 的自定义@Scope 注解与 modules 中的标注创建依赖类实例方法的注解不一样,会报错。
  3. 可读性提高,如用@Singleton 标注全局依赖类,立马能明白这类是全局单例类。
  • 自定义 Scope
    自定义 Scope 名字随便取,Scope 实际上并没有帮助你管理对象的生命周期;无论是@Singleton 还是自定义的 Scope 都是单例的,只不过这个单例是基于 Component 的。自定义的 Scope 标记只是起到一个标识作用,让 Coder 不要忘了按照对象生命周期的不同来组织依赖。
  • 注意
  1. 一个 Module 里只能存在一种 Scope
  2. 如果两个 Component 间有依赖关系 (dependencies),那么它们不能使用相同的 Scope,即不同的 Component 之间的 Scope 必须不同
  3. Singleton 的 Component 不能依赖其他 Scope 的 Component,只能其他 Scope 的组件依赖 Singleton 的 Component
  4. 没有 Scope 的 Component 不能依赖有 Scope 的 Component
  5. 一个 Component 不能同时有多个 Scope,SubComponent 除外
  6. Dagger2 框架不会帮你管理对象的生命周期,需要自己来控制
7、@Singleton
  • 注意
    本身@Singleton 没有创建单例的能力,它的单例范围为 Component 实例内;它是 Scope 的一个默认实现,是 Dagger2 唯一带@Scope 注解的。
  • 如何创建单例
    1· 在@Module 中定义创建全局依赖类实例的方法
  1. ApplicationComponent 管理 Module
  2. 保证 ApplicationComponent 只有一个实例(在 app 的 Application 中实例化 ApplicationComponent)
  • @Singleton 真正作用
  1. 更好的管理 ApplicationComponent 和 Module 之间的关系,保证 ApplicationComponent 和 Module 是匹配的。若 ApplicationComponent 和 Module 的 Scope 是不一样的,则在编译时报错
  2. 代码可读性,让程序员更好的了解 Module 中创建的依赖类实例是单例的
8、@SubComponent

子 Component
和 Component 的 dependencies 区别:
dependencies 不会继承范围(Scope),但 SubComponent 会继承 Scope,这样 SubComponent 同时具备了两种 Scope

9、@Named

是一个 @Qualifier,如果 Module 中需要生成同一个类的两个不同实例,需要用 @Named,根据 @Named 后面字符串来匹配需要注入哪种实例,

1
2
3
4
5
6
7
@Provides
@Named("default")
SharedPreferences provideDefaultSharedPrefs() { /*…*/ }

@Provides
@Named("secret")
SharedPreferences provideSecretSharedPrefs() {/*…*/ }

提供两种不同的 SharedPreferences 依赖,在需要注入的地方这样标记:

1
2
3
4
@Inject @Named("default")
SharedPreferences mDefaultSharedPrefs;
@Inject @Named("secret")
SharedPreferences mSecretSharedPrefs;
10、Lazy 接口和 Provider 接口

见 Dagger2 之 Lazy 和 Provider.md

11、Multibindings

Multibindings 的应用场景比较少,主要用于插件化的实现,Multibindings 分成 Set 与 Map,Map 的 Multibindings 目前还是 Beta 版本,也就是说还是在试验阶段,所以只介绍简单的 Set 的 Multibindings。
通过 Multibindings,Dagger 可以将不同 Module 产生的元素整合到同一个集合。更深层的意义是,Module 在此充当的插件的角色,客户端通过这些插件的不同而获取到不同的集合。

其他

1、@Inject 和@Provides 依赖生成方式的区别
  • @Inject 用于注入可实例化的类;@Providers 可用于注入所有类(接口,抽象类等)
  • @Inject 可用于修饰属性和构造方法,可用于任何非@Module 类在目标类中可作用于属性,在依赖类中作用于构造方法;@Providers 只可用于修饰非构造方法,且该方法必须在某个 Module 内部
  • @Inject 修饰的方法只能是构造方法;@Providers 修饰的方法必须以 provide 开头

Dagger2 之 Component

一、Component 的组织方式(重点)

1、按什么粒度划分 @Component
  • 一个 Component?
    如果一个 Android App 中只有一个 Component,那这个 Component 是很难维护的、变化率很高和很庞大的,因为这个 Component 的职责太多了导致。
2、划分规则
  • 有一个全局的 Component(可以叫 ApplicationComponent),负责管理整个 App 的全局类实例(全局类实例是整个 App 都要用到的类的实例,这些类基本上都是单例的)
  • 每个页面对应一个 Component,比如一个 Activity 页面定义一个 Component,一个 Fragment 定义一个 Component;当然这不是必须,有些页面之间的依赖类是一样的,可以共用一个 Component。
3、划分粒度不能过小

假如使用 mvp 架构搭建 app,划分粒度是基于每个页面的 m、v、p 各自定义 Component 的,那 Component 的粒度就太小了,定义这么多的 Component,管理、维护就很非常困难
所以以页面划分 Component 在管理、维护上面相对来说更合理。

二、组织 Component

问题:其他的 Component 想要全局的依赖类实例,涉及到了依赖类实例共享问题。因为 Component 有管理创建类实例的能力。因此只要能很好的组织 Component 之间的关系,问题就好办了,具体的组织方式分为以下 3 种:

依赖方式

一个 Component 是依赖于一个或多个 Component,Component 的 dependencies 属性就是依赖方式的具体体现。

包含方式

一个 Component 是包含一个或多个 Component 的,被包含的 Component 还可以继续包含其他的 Component。这种方式特别像 Activity 与 Fragment 的关系。SubComponent 就是包含方式的具体体现。

继承方式

官网没有提到该方式,具体没有提到的原因我觉得应该是,该方式不是解决类实例共享的问题,而是从更好的管理、维护 Component 的角度,把一些 Component 共有的方法抽象到一个父类中,然后子 Component 继承。

三、SubComponent

如果一个 Component 的功能不能满足你的需求,你需要对它进行拓展,一种办法是使用 Component(dependencies=××.classs)。另外一种是使用 @Subcomponent,Subcomponent 用于拓展原有 component。同时,这将产生一种副作用——子 component 同时具备两种不同生命周期的 scope。子 Component 具备了父 Component 拥有的 Scope,也具备了自己的 Scope。
Subcomponent 其功能效果优点类似 component 的 dependencies。但是使用@Subcomponent 不需要在父 component 中显式添加子 component 需要用到的对象,只需要添加返回子 Component 的方法即可,子 Component 能自动在父 Component 中查找缺失的依赖。
父 AppComponent:

1
2
3
4
5
@PerApp
@Component(modules = {AppModule.class})
public interface AppComponent { //父Component
    ActivitySubComponent subAppComponent();  //1.只需要在父Component添加返回子Component的方法即可
}

子 ActivitySubComponent:

1
2
3
4
5
@PerActivity //2.注意子Component的Scope范围小于父Component
@Subcomponent(modules = {ActivityModule.class})//3.使用@Subcomponent
public interface ActivitySubComponent {  //子Component
    void inject(SubcomponentDemoActivity subcomponentDemoActivity);
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
// 4.调用subComponent方法创建出子Component
DaggerAppComponent
        .builder()
        .appModule(new AppModule())
        .build()
        .subAppComponent()
        .inject(this);

// 通过调用父Component
String runningActivityName = mActivityInfo.runningActivityName();
// 通过ActivitySubComponent
AppInfo appInfo = mActivityInfo.mAppInfo();

通过 Subcomponent,子 Component 就好像同时拥有两种 Scope,当注入的元素来自父 Component 的 Module,则这些元素会缓存在父 Component;当注入的元素来自子 Component 的 Module,则这些元素会缓存在子 Component 中。

Dagger2 之 Lazy 和 Provider 接口

Lazy 和 Provider 都是用于包装目标类中需要被注入的类型

Lazy

Lazy 用于延迟加载,在 get() 的时候,才会创建对象,后面每次 get() 都是一样的对象。

Provider

Provider 用于强制重新加载,Provider 保证每次重新加载,但并不意味着每次返回的对象都是不同的,只有 Module 的@Provide 方法每次都创建新的实例时,Provider 每次 get() 的对象才相同。

案例

  • CounterComponent
1
2
3
4
5
6
7
8
@Component(modules = {CounterModule.class})
public interface CounterComponent {
//    不能用父类或父接口
//    void inject(Counter counter);
    void inject(DirectCounter counter);
    void inject(LazyCounter counter);
    void inject(ProviderCounter counter);
}
  • CounterModule
1
2
3
4
5
6
7
8
9
10
11
12
13
 @Module
 public class CounterModule {
    int next = 100;
    private final String type;
    public CounterModule(String type) {
        this.type = type;
    }
    @Provides
    public Integer provideInteger() {
        Log.i(Counter.TAG, type + " computing...");
        return next++;
    }
 }
  • DirectCounter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public class DirectCounter extends Counter {
    @Inject
    Integer value;
    @Override
    public void init() {
        DaggerCounterComponent.builder().counterModule(new CounterModule(this.getClass().getSimpleName())).build().inject(this);
    }
    @Override
    public void log() {
        super.log();
        logi(value.toString());
        logi(value.toString());
        logi(value.toString());
    }
 }
1
2
3
4
5
// DirectCounter computing
// DirectCounter printing...
// 100
// 100
// 100
  • LazyCounter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public class LazyCounter extends Counter {
    @Inject
    Lazy<Integer> lazy;
    @Override
    public void init() {
        DaggerCounterComponent.builder().counterModule(new CounterModule(this.getClass().getSimpleName())).build().inject(this);
    }
    @Override
    public void log() {
        super.log();
        logi(lazy.get().toString()); //在这时才创建对象,以后每次调用get会得到同一个对象
        logi(lazy.get().toString());
        logi(lazy.get().toString());
    }
 }
1
2
3
4
5
// LazyCounter printing...
// LazyCounter computing...
// 100
// 100
// 100
  • ProviderCounter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public class ProviderCounter extends Counter {
    @Inject
    Provider<Integer> provider;
    @Override
    public void init() {
        DaggerCounterComponent.builder().counterModule(new CounterModule(this.getClass().getSimpleName())).build().inject(this);
    }
    @Override
    public void log() {
        super.log();
        logi(provider.get().toString()); //在这时创建对象,以后每次调用get会再强制调用Module的Provides方法一次,根据Provides方法具体实现的不同,可能返回跟前面的是同一个对象,也可能不是。
        logi(provider.get().toString());
        logi(provider.get().toString());
    }
 }
1
2
3
4
5
6
7
// ProviderCounter printing...
// ProviderCounter computing...
// 100
// ProviderCounter computing...
// 101
// ProviderCounter computing...
// 102

Dagger2 之注意点及易错点

一、Dagger2 使用注意点

1、为@Provides 方法添加输入参数

Module 中@Provides 方法可以带输入参数,其参数由 Module 集合中的其他@Provides 方法提供,或者自动调用@Inject 的构造方法;如果找不到@Provides 方法提供对应参数的对象,自动调用带@Inject 参数的构造方法生成相应对象

2、添加多个 Module

一个 Component 可以包含多个 Module,这样 Component 获取依赖时候会自动从多个 Module 中查找获取,Module 间不能有重复方法。添加多个 Module 有两种方法,一种是在 Component 的注解@Component(modules={××××,×××}) 添加多个 modules,

1
2
3
4
@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) //添加多个Module
public interface FruitComponent{
    // ...
}

另外一种添加多个 Module 的方法可以被使用 Module 中的@Module(includes={××××,×××}),这种使用 Module 的 includes 的方法一般用于构建更高层的 Module 时候使用。

1
2
3
4
5
6
7
8
@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class})
public class FruitModule{
    // ...
}
@Component(modules={FruitModule.class}) //添加多个Module
public interface FruitComponent{
    // ...
}
3、DaggerXXXComponent.create()–Module 实例的创建
1
2
3
DaggerFruitComponent.create().inject(this); //7 使用FruitComponent的实现类注入
// 等价于
DaggerFruitComponent.builder().appleModule(new AppleModule()).bananaModule(new BananaModule()).build().inject(this);

调用 DaggerFruitComponent.create() 实际上等价于 DaggerFruitComponent.builder().build()。DaggerFruitComponent 使用了构建者模式,在构建的过程中,默认使用了 Module 无参的构造方法产生实例。

1
2
3
4
5
6
7
8
9
public FruitComponent build() {
  if (appleModule == null) {
    this.appleModule = new AppleModule();
  }
  if (bananaModule == null) {
    this.bananaModule = new BananaModule();
  }
  return new DaggerFruitComponent(this);
}

**Note: **如果需要传入特定的 Module;使用 Module 的有参构造器,那么必须显示传入 Module 实例。

4、区分返回类型相同的@Providers 修饰的方法

当有 Fruit 需要注入时,Dagger2 就会在 Module 中查找返回类型为 Fruit 的方法,也就是说,Dagger2 是按照 Provide 方法返回类型查找对应的依赖。但是,当 Container 需要依赖两种不同的 Fruit 时,你就需要写两个@Provides 方法,而且这两个@Provides 方法都是返回 Fruit 类型,靠判别返回值的做法就行不通了,编译时报如下错误:

1
2
3
4
Error:(20, 10) 错误: me.hacket.thirdpart.dagger2.demo3.bean.Fruit is bound multiple times:
@Provides me.hacket.thirdpart.dagger2.demo3.bean.Fruit me.hacket.thirdpart.dagger2.demo3.module.FruitModule.provideApple()
@Provides me.hacket.thirdpart.dagger2.demo3.bean.Fruit me.hacket.thirdpart.dagger2.demo3.module.FruitModule.provideBanana()
@Provides me.hacket.thirdpart.dagger2.demo3.bean.Fruit me.hacket.thirdpart.dagger2.demo3.module.AppleModule.provideFruit(int)

这就需要使用@Named 来区分,只有具有相同 @Named 注解标记的目标类中@Inject 和生成依赖类中 Module 的@Provides 对应起来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//定义Module
@Module
public class FruitModule{
    @Named("typeA")
    @Provides
    public Fruit provideApple(){  //提供Apple给对应的mFruitA
        return new Apple();
    }
    @Named("typeB")
    @Provides
    public Fruit provdeBanana(){ //提供Banana给对应的mFruitB
        return new Banana()
    }
}
//定义Component
@Component(modules={FruitModule.class})
interface FruitComponent{    //Dagger根据接口自动生成FruitComponent
    void inject(Container container);
}
//定义Container
class Container{
    @Named("typeA") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的元依赖
    @Inject
    Fruit mFruitA;
    @Named("typeB") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖
    @Inject
    Fruit mFruitB;
    // ...
    public void init(){
         DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的实现类注入
     }

}

如果觉得@Named 只能用字符串区分不满足需求,你也可以自定义类似@Named 的注解,使用元注解@Qualifier 可以实现这种注解,比如实现一个用 int 类型区分的@IntNamed,用法同@Named 注解一样:

1
2
3
4
5
6
@Qualifier // 必须,表示IntNamed是用来做区分用途
@Documented // 规范要求是Documented,当然不写也问题不大,但是建议写,做提示作用
@Retention(RetentionPolicy.RUNTIME) // 规范要求是Runtime级别
public @interface IntNamed{
    int value();
}
5、Component 定义方法的规则
  • 1、Component 的方法输入参数一般只有一个,对应了需要注入的类 Container。有输入参数返回值类型就是 void
1
2
3
4
@Component(modules = {FruitModule.class, AppleModule.class, BananaModule.class})
public interface FruitComponent {
    void inject(Container container);
}
  • 2、Component 的方法可以没有输入参数,但是就必须有返回值:返回的实例会先从定义的 Module 中查找,没有找到就用该实例对应的类带@Inject 的构造器来生成返回实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
//定义ComponentB
@Component(modules={××××××××})//1.假设Module中没有provideApp()方法,但有provideInfo()
interface ComponentB{
    Apple apple(); //2.实现类自动返回由Apple(info)构建的实现类
}
public class Apple{
    @Inject
    Apple(Info info){//被@Inject标记,使用这个构造器生成实例
        // ...
    }
    Apple(){   //不会使用这个构造器,没有被@Inject标记
    }
}

代码会生成 ComponentB 的实现类 DaggerComponetB,调用其 apple() 方法会自动使用 Apple(info) 构造器生成实例返回。

6、目标类(如 Container)中的@Inject 规则
  • @Inject 可以标记目标类(Container)中的成员变量,这些成员变量要求至少是包级可见的,不可以标记为 private
  • 当@Inject 标记目标类的成员变量时,会按照以下规则来查找对应依赖:
1
2
1.该成员变量的依赖会从Module的@Provides方法集合中查找;
2.如果查找不到,则查找成员变量对应的类是否有@Inject构造方法,并注入构造方法且递归注入该类型的成员变量
7、Dagger2 中的单例

Java 中的单例通常保存在一个静态域中,这样的单例往往要等到虚拟机关闭的时候,该单例所占用的资源才释放。
但 Dagger2 创建出来的单例并不保持在静态域中,而是保留在 Component 实例中。

二、易错点

遇到的一些常见错误

1、Module 中存在多个返回相同类的@Provides 方法,导致 Component 依赖注入迷失

一个 Module 中,存在多个可以提供相同类的@Providers,编译时出错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Module
public class ActivityModule {

    @Provides
    public UserModel provideUserModel() {
        UserModel userModel = new UserModel();
        userModel.name = "default hacket";
        userModel.gender = "default gender male";
        return userModel;
    }

    @Provides
    public UserModel provideUserModel2() {
        UserModel userModel = new UserModel("hacket_2", "hacket_male_2");
        return userModel;
    }
}

错误如下:

1
2
3
4
Error:(17, 10) 错误: me.hacket.thirdpart.dagger2.demo2.ui.bean.UserModel is bound multiple times:
@Provides me.hacket.thirdpart.dagger2.demo2.ui.bean.UserModel me.hacket.thirdpart.dagger2.demo2.di.module.ActivityModule.provideUserModel()
@Provides me.hacket.thirdpart.dagger2.demo2.ui.bean.UserModel me.hacket.thirdpart.dagger2.demo2.di.module.ActivityModule.provideUserModel2()
@Provides me.hacket.thirdpart.dagger2.demo2.ui.bean.UserModel me.hacket.thirdpart.dagger2.demo2.di.module.ActivityModule.provideUserModel3()

解决,用@Qualifier 注解,在@Inject 和@Provides 标记一一对应起来。

2、Component 注入的对象不对(如为目标对象类的父类)

Component 中注入的对象不对,如注入 MainActivity,但是写了父类 Activity,编译不会出错,运行时注入的对象为 null。

1
2
3
4
@Component(modules = {ActivityModule.class})
public interface ActivityComponent {
    void injectUserModel(Activity activity);
}

注意: 这里必须是真正消耗依赖的类型 MainActivity,而不可以写成其父类,比如 Activity。因为 Dagger2 在编译时生成依赖注入的代码,会到 inject 方法的参数类型中寻找可以注入的对象,但是实际上这些对象存在于 MainActivity,而不是 Activity 中。如果函数声明参数为 Activity,Dagger2 会认为没有需要注入的对象。当真正在 MainActivity 中创建 Component 实例进行注入时,会直接执行按照 Activity 作为参数生成的 inject 方法,导致所有注入都失败。(是的,我是掉进这个坑了。)

3、命名@Provider 修饰的方法
1
Error:(32, 16) 错误: Cannot have more than one @Provides method with the same name in a single module
4、Scope 没有标记 Scope
1
2
Error:(14, 1) 错误: me.hacket.thirdpart.dagger2.demo4.component.JuiceComponent (unscoped) may not reference scoped bindings:
@Singleton @Provides me.hacket.thirdpart.dagger2.demo4.bean.JuiceMachine me.hacket.thirdpart.dagger2.demo4.module.MachineModule.provideJuiceMachine()

ListView 展示数据

UserAdapter

在构造方法前添加 @Inject 注解,这个 Dagger2 会在获取 UserAdapter 对象时,调用这个标记的构造方法,从而生成一个 UserAdapter 对象。

1
2
3
4
5
6
7
8
9
10
11
12
 public class UserAdapter extends BaseAdapter {
     private LayoutInflater inflater;
     private List<String> users;

     @Inject
     public UserAdapter(Context ctx, List<String> users) {
         this.inflater = LayoutInflater.from(ctx);
         this.users = users;
     }

     // ...
 }

如果构造方法含有参数,Dagger2 会在调用构造对象的时候先去获取这些参数,所以要保证它的参数也提供可被 Dagger 调用的生成函数。
Dagger2 调用生成对象的两种函数:

  1. 用@Inject 修饰的构造方法
  2. @Module 里用@Provides 修饰的方法

构建 Module

@Provides 提供给 UserAdapter 的两个参数 ContextList<String>,这样生成 UserAdapter 时需要的依赖从这里获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Module
public class UserModule {
    private static final int COUNT = 30;
    private final Context mContext;
    @Inject
    public UserModule(Context context) {
        this.mContext = context;
    }
    @ActivityScope
    @Provides
    public Context provideActivityContext() {
        return mContext;
    }
    @ActivityScope
    @Provides
    public List<String> providerUsers() {
        List<String> users = new ArrayList<>(COUNT);

        for (int i = 0; i < COUNT; i++) {
            users.add("item " + i);
        }

        return users;
    }
}

构建 Component

负责注入依赖,@Component 生成实现并命名为 Dagger$${YouComponentClassName}

1
2
3
4
5
@ActivityScope
@Component(modules = {UserModule.class})
public interface UserComponent {
    void inject(Dagger2MainActivity activity);
}

注意:这里必须是真正消耗依赖的类型 MainActivity,而不可以写成其父类,比如 Activity ,否则会导致注入失败。

@ActivityScope

是一个自定义的范围注解,作用是允许对象被记录在正确的组件中,当然这些对象的生命周期应该遵循 Activity 的生命周期

1
2
3
4
5
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScope {

}

完成注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 public class Dagger2MainActivity extends AppCompatActivity {
     // ...
     @Inject
     UserAdapter adapter;
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         // ...
         // 完成注入
         DaggerUserComponent.builder()
             .userModule(new UserModule(this))
             .build()
             .inject(this);
         listView.setAdapter(adapter);
     }
 }

Reference

Dagger 2: Step To Step
http://www.jianshu.com/p/7505d92d7748

Dagger2 资源

Dagger2 基础

  1. Dagger 2从浅到深(一)
  2. Dagger 2从浅到深(二)
  3. Dagger 2从浅到深(三)
  4. Dagger 2从浅到深(四)
  5. Dagger 2从浅到深(五)
  6. Dagger 2从浅到深(六)
  7. Dagger 2从浅到深(七)
  8. Dagger 2应用于Android的完美扩展库-dagger.android

Dagger2 进阶

Android:dagger2 让你爱不释手 - 基础依赖注入框架篇
http://www.jianshu.com/p/cd2c1c9f68d4
Android:dagger2 让你爱不释手 - 重点概念讲解、融合篇
http://www.jianshu.com/p/1d42d2e6f4a5
Android:dagger2 让你爱不释手 - 终结篇
http://www.jianshu.com/p/65737ac39c44

Dagger2 原理

Dagger2 工作流程分析
http://ifarseer.github.io/2016/05/09/dagger2/

Dagger1 源码解析
http://a.codekk.com/detail/Android/扔物线/Dagger 源码解析

Dagger2 项目

https://github.com/saulmm/Avengers

本文由作者按照 CC BY 4.0 进行授权