神兵利器Dagger2一站式全解(详细总结)

Dagger2 在模块间解耦、提高代码的健壮性和可维护性方面是当之无愧的“利器”,github star 15k。

文章适合人群:对 Dagger2 感兴趣但还未真正开始学习、对 Dagger2 学习过一些但没有成体系而知识片面、有意向成为 Java/Android 高级开发的程序员。

Chat 主要内容有:

  • Dagger2 的简介;
  • Dagger2的注解;
  • Dagger2 的使用;
  • Dagger2 的单例模式;
  • Dagger2 的懒/重加载;
  • Dagger2 中 Component 组织依赖关系;
  • Dagger2 的用途总结和原理分析;

最后:教学相长,欢迎大家一起交流学习。

依赖注入框架 Dagger2 起源于 Dagger,官网地址是 https://google.github.io/dagger/ ,是一款基于 Java 注解来实现的完全在编译阶段完成依赖注入的开源库。Dagger2 应于 Java 和 Android 开发而不单单是 Android,主要用于模块间解耦、提高代码的健壮性和可维护性。

Dagger2 在编译阶段通过 apt 利用 Java 注解自动生成 Java 代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。它需要先编译一次代码,目的是为了生成中间代码,然后在中间代码的基础上按照正常的流程开发。我们知道反射处理相对于正常开发速度而言会慢一点,但 Dagger2 却不会有这样的缺点,它巧妙地把反射处理移动到编译器编译代码时的阶段,而程序运行时并不涉及到反射,而是通过生成代码的方式来达到注入效果的,在 Android 的很多开源框架中都是用到了代码生成工具(APT), 比如 ButterKnife、GreenDao 等三方库等。

二 Dagger2 注解初识

Dagger2 是基于 Java 注解来实现依赖注入的,那么在正式使用之前我们需要先了解下 Dagger2 中的注解。Dagger2 使用过程中我们通常接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。

2.1 @Inject:

@Inject 有两个作用:

一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖;

二是用来标记构造函数,Dagger2 通过@Inject 注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject 标记了的变量提供依赖;

参考 demo:3.2 节案例 A。

2.2 @Module:

@Module 用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject 标记构造函数就可以提供依赖了么,为什么还需要@Module?

比如,很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject 注解;又比如,需要被注入的依赖类提供的构造函数是带参数的,那么他的参数又怎么来呢?

@Module 正是帮我们解决这些问题的。参考 demo:3.3 节案例 B。

2.3 @Provides:

@Provides 用于标注 Module 所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject 的变量赋值;

参考 demo:3.3 节案例 B。

2.4 @Component:

@Component 用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被 Component 标注的接口在编译时会生成该接口的实现类(如果@Component 标注的接口为 CarComponent,则编译期生成的实现类为 DaggerCarComponent),我们通过调用这个实现类的方法完成注入;

2.5 @Qulifier:

@Qulifier 用于自定义注解,也就是说@Qulifier 就如同 Java 提供的几种基本元注解一样用来标记注解类。我们在使用@Module 来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以 provide 开头,但这并不是强制的,只是为了增加可读性而已)。

那么 Dagger2 怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2 根据返回值的类型来决定为哪个被@Inject 标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型 Dagger2 就懵逼了。@Qulifier 的存在正式为了解决这个问题,我们使用@Qulifier 来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject 标注的变量),这样 Dagger2 就知道为谁提供依赖了。一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;

demo 参考:3.4 案例 C。

2.6 @Scope:

@Scope 同样用于自定义注解,我能可以通过@Scope 自定义的注解来限定注解作用域,实现单例(分局部和全局);

@Scope 需要 Component 和依赖提供者配合才能起作用,对于@Scope 注解的依赖,Component 会持有第一次创建的依赖,后面注入时都会复用这个依赖的实例,实质上@Scope 的目的就是为了让生成的依赖实例的生命周期与 Component 绑定

如果 Component 重建了,持有的@Scope 的依赖也会重建,所以为了维护局部单例需要自己维护 Component 的生命周期。

2.7 @Singleton:

@Singleton 其实就是一个通过@Scope 定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的 Component 是否为一个全局对象。

三 Dagger2 使用

3.1 引入 Dagger2

我们在使用时需要先添加依赖:

dependencies {  api 'com.google.dagger:dagger:2.x'  annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}

添加完依赖后,我们由简入深一个个来介绍具体的使用。

3.2 案例 A

Car 类是需求依赖方,依赖了 Engine 类;因此我们需要在类变量 Engine 上添加@Inject 来告诉 Dagger2 来为自己提供依赖。 Engine 类是依赖提供方,因此我们需要在它的构造函数上添加@Inject

public class Engine {    /**     *  Dagger2 通过@Inject 注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,     *  以此来为被@Inject 标记了的变量提供依赖     *         */    @Inject    public Engine() {    }    @NonNull    @Override    public String toString() {        return "Engine{}";    }    public void run() {        Log.i("tag", "引擎转起来了~~~ ");    }

接下来我们需要创建一个用@Component 标注的接口 CarComponent,这个 CarComponent 其实就是一个注入器,这里用来将 Engine 注入到 Car 中。

@Componentpublic interface CarComponent {    void inject(Car car);}

完成这些之后我们需要 Build 下项目,让 Dagger2 帮我们生成相关的 Java 类。接着我们就可以在 Car 的构造函数中调用 Dagger2 生成的 DaggerCarComponent 来实现注入(这其实在前面 Car 类的代码中已经有了体现)

public class Car {    /**     * @Inject:@Inject 有两个作用,一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖     */    @Inject    Engine engine;    public Car() {        DaggerCarComponent.builder().build().inject(this);    }    public Engine getEngine() {        return this.engine;    }    public static void main(String ... args){        Car car = new Car();        System.out.println(car.getEngine());    }}

3.3 案例 B

3.3.1 @Module、@Provide 的使用

如果创建 Engine 的构造函数是带参数的呢?或者 Eggine 类是我们无法修改的呢?这时候就需要@Module 和@Provide 上场了。

可以看到下面 Engine 类的代码和上面的入门 demo 中的 Engine 代码几乎一样,只是多了个带参数的构造方法。

public class Engine {    private String name;    @Inject    Engine(){}    Engine(String name) {        this.name = name;    }    @Override    public String toString() {        return "Engine{" +                "name='" + name + '\'' +                '}';    }    public void run() {        System.out.println("引擎转起来了~~~");    }}

接着我们需要一个 Module 类来生成依赖对象。前面介绍的@Module 就是用来标准这个类的,而@Provide 则是用来标注具体提供依赖对象的方法(这里有个不成文的规定,被@Provide 标注的方法命名我们一般以 provide 开头,这并不是强制的但有益于提升代码的可读性)。

@Modulepublic class MarkCarModule {    String engineType;    public MarkCarModule(String engineType) {        this.engineType = engineType;    }    @Provides    Engine provideEngine() {        return new Engine(engineType);    }}

接下来我们还需要对 CarComponent 进行一点点修改,之前的@Component 注解是不带参数的,现在我们需要加上 modules = {MarkCarModule.class},用来告诉 Dagger2 提供依赖的是 MarkCarModule 这个类。

@Component(modules = MarkCarModule.class)public interface CarComponent {    void inject(Car car);}

Car 类的构造函数我们也需要修改,相比之前多了个 markCarModule(new MarkCarModule())方法,这就相当于告诉了注入器 DaggerCarComponent 把 MarkCarModule 提供的依赖注入到了 Car 类中。

public class Car {    @Inject    Engine engine;    public Car() {        DaggerCarComponent.builder().markCarModule(new MarkCarModule("国产发动机"))                .build().inject(this);    }    public Engine getEngine() {        return this.engine;    }    public static void main(String ... args){        Car car = new Car();        System.out.println(car.getEngine());    }}

这样一个最最基本的依赖注入就完成了,接下来我们测试下我们的代码。 输出:

Engine{name='国产发动机'}
3.3.2 Dagger2 的依赖查找顺序说明

我们提到@Inject 和@Module 都可以提供依赖,那如果我们既在构造函数上通过标记@Inject 提供依赖,又通过@Module 提供依赖 Dagger2 会如何选择呢?具体规则如下:

  1. 步骤 1:首先查找@Module 标注的类中是否存在提供依赖的方法。

  2. 步骤 2:若存在提供依赖的方法,查看该方法是否存在参数。

    a:若存在参数,则按从步骤 1 开始依次初始化每个参数;

    b:若不存在,则直接初始化该类实例,完成一次依赖注入。

  3. 步骤 3:若不存在提供依赖的方法,则查找@Inject 标注的构造函数,看构造函数是否存在参数。

    a:若存在参数,则从步骤 1 开始依次初始化每一个参数

    b:若不存在,则直接初始化该类实例,完成一次依赖注入。

总结:Dagger2 依赖查找的顺序是先查找 Module 内所有的 @Provides 提供的依赖,如果查找不到再去查找 @Inject 提供的依赖。

3.4 案例 C :@Qualifiers 和 @Name 的使用

在同一个 Module 中 ,@Provides 提供的依赖是由返回值决定的。这样就会出现问题,同一种类型不同实例,怎么去区别?比如如果一台汽车有两个引擎(也就是说 Car 类中有两个 Engine 变量),3.3.1 案例 B 中的 MarkCarModule 你可能就会这么写:

@Modulepublic class MarkCarModule {    public MarkCarModule(){ }    @Provides    Engine provideEngineA(){        return new Engine("国产发动机");    }    @Provides    Engine provideEngineB(){        return new Engine("德国发动机");    }}

大家应该会这样编码,但是根本编译不过。因为 Dagger2 是根据返回的类型来进行依赖关系确定的。如果存在两个方法返回一样的类型,那么正常情况下 Dagger2 没法知道选哪个(比如上述代码中,不知道选 provideEngineA()方法还是 provideEngineB()方法)。

Dagger2 给出了解决方案: @Name 注解 或者 @Qulifier 注解:

3.4.1 @Qulifier 注解

我们通过@Qulifier 注解来自定义两个注解,用于区分返回类型相同但又是不同对象的两个具体类,在例子里就是国产发动机和德国发动机。

public class Engine {    /**     * 使用@Qulifier 定义两个注解     */    @Qualifier    @Retention(RetentionPolicy.RUNTIME)    public @interface QualifierA { }    @Qualifier    @Retention(RetentionPolicy.RUNTIME)    public @interface QualifierB { }    private String name;    Engine(String name) {        this.name = name;    }    @Override    public String toString() {        return "Engine{" +                "name='" + name + '\'' +                '}';    }    public void run() {        System.out.println("引擎转起来了~~~");    }}

同时我们需要对依赖提供方做出修改,用定义的不同的注解来给不同的 provideEngine 方法贴上标签,这样 Dagger2 在寻找同类型对象时,就知道到底应该找哪一个了。

@Modulepublic class MarkCarModule {    public MarkCarModule(){ }    @Engine.QualifierA    @Provides    Engine provideEngineA(){        return new Engine("国产发动机");    }    @Engine.QualifierB    @Provides    Engine provideEngineB(){        return new Engine("德国发动机");    }}

接下来依赖需求方 Car 类同样需要修改,需要在注入对象前加上自定义的注解便签,以便 Dagger2 知道选择哪个 provide 方法:

public class Car {    @Engine.QualifierA    @Inject    Engine engineA;    @Engine.QualifierB    @Inject    Engine engineB;    public Car() {        DaggerCarComponent.builder().markCarModule(new MarkCarModule())                .build().inject(this);    }    public Engine getEngineA() {        return this.engineA;    }    public Engine getEngineB() {        return this.engineB;    }    public static void main(String... args) {        Car car = new Car();        System.out.println(car.getEngineA());        System.out.println(car.getEngineB());    }}
3.4.2 @Name 注解

看下 Named 标签的源码:

@Qualifier@Documented@Retention(RUNTIME)public @interface Named {    /** The name. */    String value() default "";}

可以看到:@Name 实际上也是 @Qualifier 注解的一个注解。原理还是 @Qualifier,只不过往上封装了一层使用起来可能更加方便。

@Modulepublic class MarkCarModule {    public MarkCarModule(){ }    @Provides    @Named("QualifierA")    Engine provideEngineA(){        return new Engine("国产发动机");    }    @Provides    @Named("QualifierB")    Engine provideEngineB(){        return new Engine("德国发动机");    }}public class Car {    @Named("QualifierA")    @Inject    Engine engineA;    @Named("QualifierB")    @Inject    Engine engineB;    public Car() {        DaggerCarComponent.builder().markCarModule(new MarkCarModule())                .build().inject(this);    }    public Engine getEngineA() {        return this.engineA;    }    public Engine getEngineB() {        return this.engineB;    }    public static void main(String... args) {        Car car = new Car();        System.out.println(car.getEngineA());        System.out.println(car.getEngineB());    }}

四 进阶使用之单例

4.1 @Singleton 实现的局部单例

利用 Dagger2 我们可以舍弃以往双重检查锁等常见的繁琐的单例创建方式。有关单例的学习可以参考这边文章:单例模式学习与进阶。

Dagger2 中提供了@Singleton 注解来实现单例,实现单例需要两步 :

  1. 在 Module 对应的 Provides 方法标明@Singleton,表明提供的是一个单例对象。

  2. 同时在 Component 类标明@Singleton,表明该 Component 中有 Module 使用了@Singleton。

4.1.1 创建 UserBean 对象
public class UserBean {    private String name;    public UserBean(String name) {        this.name = name;    }}
4.1.2 创建 Module

利用@Singleton 来提供一个单例对象:

@Modulepublic class UserModule {    @Singleton    @Provides    UserBean providesUserA() {        return new UserBean("Lucas");    }}
4.1.3 创建 Component

注意:如果 moudule 所依赖的 Comonent 中有被@Singleton 单例的对象,那么 Conponnent 也必须是单例的@Singleton,表明该 Component 中有 Module 使用了@Singleton

@Singleton@Component(modules = UserModule.class)public interface UserComponent {    void join(UserActivity userActivity);}

这里大家可能会有疑问,为什么要用 @Singleton 同时标注 @Provides 和 @Component ?

答:Component 是联系需求与依赖的纽带,对于@Scope(/@Singleton)注解的依赖,Component 会持有第一次创建的依赖,后面注入时都会复用这个依赖的实例,如果 Component 重建了,持有的@Scope(/@Singleton)的依赖也会重建,所以需要@Scope(/@Singleton)来标注 Component。标注@Provides 是为了提供唯一的 userBean 对象。 你甚至可以理解成单例的单例,这么说不严谨,但是有助于理解:我要单例的 userBean,所以 Component 得是个单例,同 activity 拿到的 UserComponent 得是同一个具体实现类。同样对 provide 方法而言,userModule 也是 UserComponent 中的依赖,同一个 UserComponent 中的 userModule 也必须是单例。

4.1.4 UserActivity 中进行测试
public class UserActivity extends AppCompatActivity {    private TextView textView;    @Inject    UserBean userBeanA;    @Inject    UserBean userBeanB;    @Inject    UserBean userBeanC;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_monkey);        textView = findViewById(R.id.textView);        DaggerUserComponent.create().join(this);    }    public void checkSingleton(View view) {        textView.setText("userBeanA:" + userBeanA.hashCode() + "\n"                + "userBeanB:" + userBeanB.hashCode() + "\n"                + "userBeanC:" + userBeanC.hashCode() + "\n");    }}

运行后可以看到,userBeanA、userBeanB、userBeanC 三者的 hashCode 值都是一样的,证明了在此 activity 中,我们刚刚所写的单例,成功!

值得注意的是:这里的单利对象只能在同一个 Activity 中有效。

解释一下为什么这里的单利对象只能在同一个 Activity 中有效。比如上面的这个例子,关键在于 UserActivity 中的 DaggerUserComponent.create().join(this) 这一行,它创建了桥梁 UserComponent,所以在这个 activity 中,所有的 userBean 对象都是通过这个 UserComponent 去注入的,而 UserComponent 已经知道了 UserModule 是个被@Singeton 注解修饰的单例。如果再新建另外一个 UserActivity2,并在这个新建的 activity2 中再 DaggerUserComponent.create().join(this) 一下,那么就会产生新的桥梁:UserComponent2(UserComponent 是个接口,在不同的 activity 中传入不同的 activity 实例,由此也就产生了不同的 UserComponent 具体实现),这时的 userBean 对象就不是刚刚那个 UserActivity 中的 userBean 了!

不同的 Activity 持有的对象不同,使用@Singeton 注解或者定自定义的@Scope 的, 只能在同一个 activity(或者 fragment)的一个生命周期中保持单例。

4.2 自定义@Scope 注解实现的局部单例

我们看下@Singeton 注解的具体实现:

@Scope@Documented@Retention(RUNTIME)public @interface Singleton {}

可以看到, @Singleton 只是 @Scope 一个默认的实现而已,但是因为它更具可读性,能够让开发者一眼就明白它的作用是为了单例。那么我们现在用@Scope 来实现一个单例,首先我们需要通过@Scope 定义一个 CarScope 注解:

public class Engine {    /**     * 1. @Scope 定义一个 CarScope 注解     */    @Scope    @Retention(RetentionPolicy.RUNTIME)    public @interface CarScope {    }    private String name;    Engine(String name) {        this.name = name;        System.out.println("Engine create: " + name);    }    @Override    public String toString() {        return "Engine{" +                "name='" + name + '\'' +                '}';    }    public void run() {        System.out.println("引擎转起来了~~~");    }}

接着我们需要用这个@CarScope 去标记依赖提供方 MarkCarModule,表明这个 provide 方法提供的对象是单例。

@Modulepublic class MarkCarModule {    public MarkCarModule(){ }    /**     * 2. @CarScope 去标记依赖提供方 MarkCarModule     * @return     */    @Engine.CarScope    @Provides    Engine provideEngine(){        return new Engine("国产发动机");    }}

同时还需要使用@Scope 去标注注入器 Compoent,表明该 Component 中有 Module 使用了这个自定义的@Scope:

/** * 3. 同时还需要使用@Scope 去标注注入器 Compoent */@Engine.CarScope@Component(modules = MarkCarModule.class)public interface CarComponent {    void inject(Car car);}public class Car {    @Inject    Engine engineA;    @Inject    Engine engineB;    public Car() {        DaggerCarComponent.builder().markCarModule(new MarkCarModule())                .build().inject(this);    }    public Engine getEngineA() {        return this.engineA;    }    public Engine getEngineB() {        return this.engineB;    }    public static void main(String... args) {        Car car = new Car();        System.out.println(car.getEngineA());        System.out.println(car.getEngineB());    }}

如果我们不使用@Scope,上面的代码会实例化两次 Engine 类,因此会有两次"Create Engine"输出。现在我们在有@Scope 的情况测试下劳动成果: 输出

Engine create: 国产发动机Engine{name='国产发动机'}Engine{name='国产发动机'}

4.3 全局单例

上面的局部单例例子中,DaggerUserComponent 在两个 activity 中被各自被实例化一次, 因此产生了两个不同的对象, 我们需要做到让 Component 能够实现单例,Android 中, 我们知道在整个 App 生命周期中都只有一个 Appclication 实例,所以在 Application 中获得一个唯一的 component 实例,用它来提供我们需要的单例。

4.3.1 创建 UserBean 类
public class UserBean {}
4.3.2 创建 BaseModule

和局部单例是一样的。

@Modulepublic class BaseModule {    @Singleton    @Provides    UserBean providesUser() {        return new UserBean();    }}
4.3.3 创建 BaseComponent

这个 component 是用来让别的 component 来依赖的, 只需要告诉别的 component 他可以提供哪些类型的依赖即可。

@Singleton@Component(modules = BaseModule.class)public interface BaseComponent {    UserBean getSingletonUser();}

4.3.4 在 application 中提供唯一的 baseComponent 类

public class MyApplication extends Application {    private BaseComponent baseComponent;    @Override    public void onCreate() {        super.onCreate();        baseComponent = DaggerBaseComponent.create();    }    public BaseComponent getBaseComponent() {        return baseComponent;    }}
4.3.5 自定义一个 Scope

4.3.7 中需要创建的 UserComponet 是需要依赖 BaseComponent 的,而 BaseComponent 又是被@singleton 标注的,所以 UserComponet 也需要被@singleton 标注,但是已经被@singleton 标注过的依赖,不能被被依赖方(下面的 UserComponet)使用了,只能自定义一个。

/** * Scope 标注是 Scope * @Retention(RUNTIME) 运行时级别 */@Scope@Retention(RetentionPolicy.RUNTIME)public @interface BaseScope {}
4.3.6 创建 UserModule

这里写的是自定义业务。

@Modulepublic class UserModule {}
4.3.7 创建 UserComponet

在我们自己的 Component, 使用 dependencies 依赖于 baseComponent, (在@Component 注解参数中, 可以依赖多个 module 和 component, 根据自己的业务需求定义即可)

一个 Component 可以依赖一个或多个 Component,并拿到被依赖 Component 暴露出来的实例。

建议:如果 modules 有多个,可以用大括号括起来:@Component(modules = {AModule.class,BModule.class})

@BaseScope@Component(modules = UserModule.class, dependencies = BaseComponent.class)public interface UserComponet {    void join(UserActivity userActivity);    void join(User2Activity user2Activity);}
4.3.8 activity 中测试
public class UserActivity extends AppCompatActivity {    private TextView textView;    @Inject    UserBean userBeanA;    @Inject    UserBean userBeanB;    @Inject    UserBean userBeanC;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_student);        ((TextView) findViewById(R.id.text)).setText("第一个界面");        textView = findViewById(R.id.textView);        DaggerUserComponet.builder().baseComponent(((MyApplication) getApplication()).getBaseComponent()).build().join(this);    }    public void 点击全局单例(View view) {        textView.setText("userA:" + userBeanA.hashCode() + "\n"                + "userB:" + userBeanB.hashCode() + "\n"                + "userC:" + userBeanC.hashCode() + "\n");    }    public void 下一个全局单例(View view) {        startActivity(new Intent(this, User2Activity.class));    }}

一开始看上去有点绕有点多,实际上可以这样理解:用了两个局部单例:不管什么 activity,在使用 DaggerUserComponet 注入的时候,因为 UserComponet 依赖了 baseComponent,而 baseComponent 是由 application 创建的,只会有一个。对 baseComponent 而言,它有的 userbean 又是@Singleton 标注的,也是唯一。以此实现了 app 整个生命周期的全局单例。

五:懒/重加载

5.1 Dagger2 中的懒加载

智能懒加载,是 Dagger2 实现高性能的重要举措之一:在需要的时候才对成员变量进行初始化,可以大幅缩短应用初始化的时间。

使用方法:用 Lazy<T>修饰变量即可。Lazy 是泛型类,接受任何类型的参数。

    @Inject    Lazy<Object> object;

拿 3.2 小节中的 demo 为例,只要用 Lazy<T>修饰需要被注入的对象即可。

public class Car {    /**     * @Inject:@Inject 有两个作用,一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖     */    @Inject    Lazy<Engine> engine;    public Car() {        DaggerCarComponent.builder().build().inject(this);    }    public Engine getEngine() {        return this.engine;    }    public static void main(String ... args){        Car car = new Car();        System.out.println(car.getEngine());    }}

5.2 Provider 强制重新加载

@Singleton 标注实现的单例可以让我们每次获取的都是同一个对象(暂不细究全局/局部单例),但有时,我们希望每次都创建一个新的实例,这种情况与 @Singleton 完全相反。Dagger2 通过 Provider 就可以实现。它的使用方法和 Lazy 很类似。

使用方法:用 Provider<T>修饰变量即可。Provider 是泛型类,接受任何类型的参数。

    @Inject    Provider<Object> object;

还是拿 3.2 小节中的 demo 为例,只要用 Provider<T>修饰需要被注入的对象即可。

public class Car {    /**     * @Inject:@Inject 有两个作用,一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖     */    @Inject    Provider<Engine> engine;    public Car() {        DaggerCarComponent.builder().build().inject(this);    }    public Engine getEngine() {        return this.engine;    }    public static void main(String ... args){        Car car = new Car();        System.out.println(car.getEngine());    }}

但是,需要注意的是 Provider 所表达的重新加载是说每次重新执行 Module 相应的 @Provides 方法,如果这个方法本身每次返回同一个对象,那么每次调用 get() 的时候,对象也会是同一个。

六:Component 的组织依赖关系

6.1 前言

Component 的组织依赖关系主要参考了Dagger 2 完全解析(三),Component 的组织关系与 SubComponent这篇文章,我觉得写的很好,例子很棒,自己写下来应该差不多,所以这一小节主要以该篇文章内容结构为主体来写,建议大家也看看。进入正文。

在实际项目中,有多个需要注入依赖的对象,也就是说会有多个 Component,它们之间会有相同的依赖,那么该如何处理它们之间的关系呢?以下面这个场景为例子:

public class Man {    @Inject    Car car;    public void goWork() {        ...        car.go();        ...    }}public class Friend {    @Inject    Car car;    // 车是向 Man 借的    public void goSightseeing() {        ...        car.go();        ...    }}

项目场景如下:Man 有一辆车,Friend 没有车,但是他可以借 Man 的车出去玩,但提供 Car 实例的,是 CarModule 不变。

这种情况下我们该怎么设计 Component 呢?很多人第一时间会这么设计:

// @ManScope 和 @FriendScope 都是自定义的作用域@ManScope@Component(modules = CarModule.class)public interface ManComponent {    ...}@FriendScope@Component(modules = CarModule.class)public interface FriendComponent {    ...}

这种做法最简单,ManComponent 和 FriendComponent 需要的 car 都在各自 modules 中提供了,也就是说:Man 和 Friends 都有 CarModule 去提供 car。所以这时,发现问题了嘛?这个 car 已经不是 Man 他的 car 了!

问题:

(1)有时依赖实例需要共享,例如上面场景中,Friend 的 car 是向 Man 借的,所以 FriendComponent 应该使用 ManComponent 中的 car 实例。

(2)Scope 作用域容易失效,例如 CarModule 的 provideCar()使用 @Singleton 作用域,FriendComponent 和 ManComponent 也要用 Singleton 标注,但它们都会持有一个 car 实例。

所以 FriendComponent 需要依赖 ManComponent 提供的 car 实例,这就是 Component 组织关系中的一种:依赖关系。

6.2 Component 的组织关系

在 Dagger 2 中 Component 的组织关系分为两种:

依赖关系:一个 Component 依赖其他 Compoent ,以获得其中公开的依赖实例,用 Component 中的 dependencies 声明。

继承关系:一个 Component 继承(扩展)其他的 Component, 以获得其他的 Component 中的依赖,SubComponent 就是继承关系的体现。

6.2.1 依赖关系

Friend 与 Man 场景中的依赖关系图:在这里插入图片描述具体的实现代码:

@ManScope@Component(modules = CarModule.class)public interface ManComponent {    void inject(Man man);    Car car();  //必须向外提供 car 依赖实例的接口,表明 Man 可以借 car 给别人}@FriendScope@Component(dependencies = ManComponent.class)public interface FriendComponent {    void inject(Friend friend);}

注:因为 FriendComponent 和 ManComponent 是依赖关系,所以其中一个声明了作用域的话,另外一个也必须声明。而且它们的 Scope 不能相同,ManComponent 的生命周期 >= FriendComponent 的。FriendComponent 的 Scope 不能是 @Singleton,因为 Dagger 2 中 @Singleton 的 Component 不能依赖其他的 Component。

编译时生成的代码中 DaggerFriendComponent 的 Provider<Car>实现中会用到 manComponent.car()来提供 car 实例,如果 ManComponent 没有向外提供 car 实例的接口的话,DaggerFriendComponent 就会注入失败。

依赖注入:

ManComponent manComponent = DaggerManComponent.builder()    .build();FriendComponent friendComponent = DaggerFriendComponent.builder()    .manComponent(manComponent)    .build();friendComponent.inject(friend);

依赖关系就跟生活中的朋友关系相当,注意事项如下:

被依赖的 Component 需要把暴露的依赖实例用显式的接口声明,如上面的 Car car(),我们只能使用朋友愿意分享的东西。

依赖关系中的 Component 的 Scope 不能相同,因为它们的生命周期不同。

6.2.3 继承关系

继承关系跟面向对象中的继承的概念有点像,SubComponent 称为子 Component,类似于平常说的子类。下面先看看下面这个场景:

public class Man {    @Inject    Car car;    ...}public class Son {    @Inject    Car car;    @Inject    Bike bike;}

Son 可以开他爸爸 Man 的车 car,也可以骑自己的自行车 bike。依赖关系图:在这里插入图片描述上图中 SonComponent 在 ManComponent 之中,SonComponent 子承父业,可以访问 parent Component 的依赖,而 ManComponent 只知道 SonComponent 是它的 child Component,可以访问 SubComponent.Builder,却无法访问 SubComponent 中的依赖。

@ManScope@Component(modules = CarModule.class)public interface ManComponent {    void inject(Man man);   // 继承关系中不用显式地提供暴露依赖实例的接口}@SonScope@SubComponent(modules = BikeModule.class)public interface SonComponent {    void inject(Son son);    @Subcomponent.Builder    interface Builder { // SubComponent 必须显式地声明 Subcomponent.Builder,parent Component 需要用 Builder 来创建 SubComponent        SonComponent build();    }}

@SubComponent 的写法与@Component 一样,只能标注接口或抽象类。与依赖关系一样,SubComponent 与 parent Component 的 Scope 不能相同,只是 SubComponent 表明它是继承扩展某 Component 的。

那怎么表明一个 SubComponent 是属于哪个 parent Component 的呢?只需要在 parent Component 依赖的 Module 中的 subcomponents 加上 SubComponent 的 class,然后就可以在 parent Component 中请求 SubComponent.Builder。

@Module(subcomponents = SonComponent.class)public class CarModule {    @Provides    @ManScope    static Car provideCar() {        return new Car();    }}@ManScope@Component(modules = CarModule.class)public interface ManComponent {    void injectMan(Man man);    SonComponent.Builder sonComponent();    // 用来创建 Subcomponent}SubComponent 编译时不会生成 DaggerXXComponent,需要通过 parent Component 的获取 SubComponent.Builder 方法获取 SubComponent 实例。ManComponent manComponent = DaggerManComponent.builder()    .build();SonComponent sonComponent = manComponent.sonComponent()    .build();sonComponent.inject(son);

继承关系和依赖关系最大的区别就是:继承关系中不用显式地提供依赖实例的接口,SubComponent 继承 parent Component 的所有依赖。

6.3 依赖关系 vs 继承关系

相同点:

  1. 两者都能复用其他 Component 的依赖

  2. 有依赖关系和继承关系的 Component 不能有相同的 Scope

区别:

  1. 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖。

  2. 依赖关系会生成两个独立的 DaggerXXComponent 类,而 SubComponent 不会生成 独立的 DaggerXXComponent 类。

在 Android 开发中,Activity 是 App 运行中组件,Fragment 又是 Activity 一部分,这种组件化思想适合继承关系,所以在 Android 中一般使用 SubComponent。

6.4 SubComponent 的其他问题

6.4.1 抽象工厂方法定义继承关系

除了使用 Module 的 subcomponents 属性定义继承关系,还可以在 parent Component 中声明返回 SubComponent 的抽象工厂方法来定义:

@ManScope@Component(modules = CarModule.class)public interface ManComponent {    void injectMan(Man man);    SonComponent sonComponent();    // 这个抽象工厂方法表明 SonComponent 继承 ManComponent}

这种定义方式不能很明显地表明继承关系,一般推荐使用 Module 的 subcomponents 属性定义。

6.4.2 重复的 Module

当相同的 Module 注入到 parent Component 和它的 SubComponent 中时,则每个 Component 都将自动使用这个 Module 的同一实例。也就是如果在 SubComponent.Builder 中调用相同的 Module 或者在返回 SubComponent 的抽象工厂方法中以重复 Module 作为参数时,会出现错误。(前者在编译时不能检测出,是运行时错误)

@Component(modules = {RepeatedModule.class, ...})interface ComponentOne {  ComponentTwo componentTwo(RepeatedModule repeatedModule); // 编译时报错  ComponentThree.Builder componentThreeBuilder();}@Subcomponent(modules = {RepeatedModule.class, ...})interface ComponentTwo { ... }@Subcomponent(modules = {RepeatedModule.class, ...})interface ComponentThree {  @Subcomponent.Builder  interface Builder {    Builder repeatedModule(RepeatedModule repeatedModule);    ComponentThree build();  }}DaggerComponentOne.create().componentThreeBuilder()    .repeatedModule(new RepeatedModule()) // 运行时报错 UnsupportedOperationException!    .build();

6.5 总结

Component 之间共用相同依赖时,可以有两种组织关系:依赖关系与继承关系。在 Android 开发中,一般使用继承关系,以 AppComponent 作为 root Component,AppComponent 一般还会使用 @Singleton 作用域,而 ActivityComponent 为 SubComponent。

七:dagger2 的好处 or 用途

7.1 一切都是为了解耦

一切都是为了解耦。一个类的 new 代码是非常可能充斥在 app 的多个类中的,假如该类的构造函数发生变化,那这些涉及到的类都得进行修改。设计模式中提倡把容易变化的部分封装起来。

我们用了 dagger2 后,假如是通过用 Inject 注解标注的构造函数创建类实例,则即使构造函数变的天花乱坠,我们基本上都不需要修改任何代码。假如是通过工厂模式 Module 创建类实例,Module 其实就是把 new 类实例的代码封装起来,这样即使类的构造函数发生变化,只需要修改 Module 即可。

我认为如果是小 app 一般没必要用到 Daggger2(为了学习还是好的),解耦也是要付出相应的空间和时间代价的,工作量和复杂度都可能会提高(多人协作大项目会提高你的效率),编译时也容易出错,但在解耦数据和业务逻辑方面,Dagger2 无愧利器之名。

7.2 增加开发效率

这里的开发效率着重于:省去重复而又无意义 new 实例对象代码。当然这也会增加我们一些依赖包括 module、component 等代码的铺垫,但做完这些铺垫,dagger2 就可以把 new 一个实例的工作做了,从而让开发者把精力集中在业务上。

一个很棒的例子就是 Dagger2 中单例的写法,开发者不需要担心自己写的单例方法是否线程安全,懒汉 or 饿汉模式,这些 Dagger2 都会帮你搞定。

7.3 更好的管理类实例

每个 app 中的 ApplicationComponent 管理整个 app 的全局类实例,所有的全局类实例都统一交给 ApplicationComponent 管理,并且它们的生命周期与 app 的生命周期一样。每个页面对应自己的 Component,页面 Component 管理着自己页面所依赖的所有类实例。因为 Component,Module,整个 app 的类实例结构变的很清晰。

八 原理分析

在大体介绍完 Dagger2 后,我们分析一下实现原理。

以最简单的 案例 A:入门 demo 为例,Dagger2 在编译时根据注解生成一些辅助类,我们具体分析下生成的辅助类。辅助类可以通过 DaggerXXXComponent 来快速定位。上面两个例子对应生成辅助类如下:在这里插入图片描述

我们分析下具体的注入过程,首先 Car 中会调用 DaggerCarComponent.builder().build().inject(this);,我们来看下 DaggerCarComponent 的源码:

public final class DaggerCarComponent implements CarComponent {  private DaggerCarComponent() {  }  public static Builder builder() {    return new Builder();  }  public static CarComponent create() {    return new Builder().build();  }  @Override  public void inject(Car car) {    injectCar(car);}  private Car injectCar(Car instance) {    Car_MembersInjector.injectEngine(instance, new Engine());    return instance;  }  public static final class Builder {    private Builder() {    }    public CarComponent build() {      return new DaggerCarComponent();    }  }}

可以看到,DaggerCarComponent 是 CarComponent 的实现类,最终调用到了 inject 方法里的 injectCar 方法,然后调用了 injectCar 方法里的 Car_MembersInjector.injectEngine(instance, new Engine());

我们来看下 Car_MembersInjector 的源码:

public final class Car_MembersInjector implements MembersInjector<Car> {  private final Provider<Engine> engineProvider;  public Car_MembersInjector(Provider<Engine> engineProvider) {    this.engineProvider = engineProvider;  }  public static MembersInjector<Car> create(Provider<Engine> engineProvider) {    return new Car_MembersInjector(engineProvider);}  @Override  public void injectMembers(Car instance) {    injectEngine(instance, engineProvider.get());  }  @InjectedFieldSignature("com.ysalliance.getfan.daggertest.Car.engine")  public static void injectEngine(Car instance, Engine engine) {    instance.engine = engine;  }}

可以看到,这是 Car 对应的@Inject。

Engine_Factory 对应的源码:

public final class Engine_Factory implements Factory<Engine> {  @Override  public Engine get() {    return newInstance();  }  public static Engine_Factory create() {    return InstanceHolder.INSTANCE;  }  public static Engine newInstance() {    return new Engine();  }  private static final class InstanceHolder {    private static final Engine_Factory INSTANCE = new Engine_Factory();  }}

可以看到,Engine_Factory,是 Engine 构造方法的@Inject。

上面是最简单的 Dagger2 的注入原理的分析,如果想看带@Module 和 @Provides 的稍微复杂一点的 Dagger2 的注入原理,推荐看这一篇博文:《Dagger2 使用及原理分析》,相信在这篇的基础上,学习带@Module 和 @Provides 的注入原理,更加水到渠成。

参考文章:

Dagger2 源码分析(三)从源码角度分析注解在 Dagger2 中的使用

轻松学,听说你还没有搞懂 Dagger2

https://docs.oracle.com/javaee/7/api/javax/inject/package-summary.html

https://jcp.org/en/jsr/detail?id=330

https://dagger.dev/dev-guide/

Dagger 2 基本使用,局部单例,全局单例

@Scope 看这一篇就够了——Dagger2 (二)

dagger2 从入门到放弃-Component 的继承体系、局部单例

Dagger 2 完全解析(三),Component 的组织关系与 SubComponent

dagger2 到底有哪些好处?

关于 Android Dagger2 应用的疑惑?

阅读全文: http://gitbook.cn/gitchat/activity/5ec344f8ef4eff0c0bf71207

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

  • 6
    点赞
  • 0
    评论
  • 19
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值