赞
踩
Dagger2 在模块间解耦、提高代码的健壮性和可维护性方面是当之无愧的“利器”,github star 15k。
文章适合人群:对 Dagger2 感兴趣但还未真正开始学习、对 Dagger2 学习过一些但没有成体系而知识片面、有意向成为 Java/Android 高级开发的程序员。
Chat 主要内容有:
最后:教学相长,欢迎大家一起交流学习。
依赖注入框架 Dagger2 起源于 Dagger,官网地址是 https://google.github.io/dagger/ ,是一款基于 Java 注解来实现的完全在编译阶段完成依赖注入的开源库。Dagger2 应于 Java 和 Android 开发而不单单是 Android,主要用于模块间解耦、提高代码的健壮性和可维护性。
Dagger2 在编译阶段通过 apt 利用 Java 注解自动生成 Java 代码,然后结合手写的代码来自动帮我们完成依赖注入的工作。它需要先编译一次代码,目的是为了生成中间代码,然后在中间代码的基础上按照正常的流程开发。我们知道反射处理相对于正常开发速度而言会慢一点,但 Dagger2 却不会有这样的缺点,它巧妙地把反射处理移动到编译器编译代码时的阶段,而程序运行时并不涉及到反射,而是通过生成代码的方式来达到注入效果的,在 Android 的很多开源框架中都是用到了代码生成工具(APT), 比如 ButterKnife、GreenDao 等三方库等。
Dagger2 是基于 Java 注解来实现依赖注入的,那么在正式使用之前我们需要先了解下 Dagger2 中的注解。Dagger2 使用过程中我们通常接触到的注解主要包括:@Inject, @Module, @Provides, @Component, @Qulifier, @Scope, @Singleten。
@Inject 有两个作用:
一是用来标记需要依赖的变量,以此告诉 Dagger2 为它提供依赖;
二是用来标记构造函数,Dagger2 通过@Inject 注解可以在需要这个类实例的时候来找到这个构造函数并把相关实例构造出来,以此来为被@Inject 标记了的变量提供依赖;
参考 demo:3.2 节案例 A。
@Module 用于标注提供依赖的类。你可能会有点困惑,上面不是提到用@Inject 标记构造函数就可以提供依赖了么,为什么还需要@Module?
比如,很多时候我们需要提供依赖的构造函数是第三方库的,我们没法给它加上@Inject 注解;又比如,需要被注入的依赖类提供的构造函数是带参数的,那么他的参数又怎么来呢?
@Module 正是帮我们解决这些问题的。参考 demo:3.3 节案例 B。
@Provides 用于标注 Module 所标注的类中的方法,该方法在需要提供依赖时被调用,从而把预先提供好的对象当做依赖给标注了@Inject 的变量赋值;
参考 demo:3.3 节案例 B。
@Component 用于标注接口,是依赖需求方和依赖提供方之间的桥梁。被 Component 标注的接口在编译时会生成该接口的实现类(如果@Component 标注的接口为 CarComponent,则编译期生成的实现类为 DaggerCarComponent),我们通过调用这个实现类的方法完成注入;
@Qulifier 用于自定义注解,也就是说@Qulifier 就如同 Java 提供的几种基本元注解一样用来标记注解类。我们在使用@Module 来标注提供依赖的方法时,方法名我们是可以随便定义的(虽然我们定义方法名一般以 provide 开头,但这并不是强制的,只是为了增加可读性而已)。
那么 Dagger2 怎么知道这个方法是为谁提供依赖呢?答案就是返回值的类型,Dagger2 根据返回值的类型来决定为哪个被@Inject 标记了的变量赋值。但是问题来了,一旦有多个一样的返回类型 Dagger2 就懵逼了。@Qulifier 的存在正式为了解决这个问题,我们使用@Qulifier 来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方(也就是被@Inject 标注的变量),这样 Dagger2 就知道为谁提供依赖了。一个更为精简的定义:当类型不足以鉴别一个依赖的时候,我们就可以使用这个注解标示;
demo 参考:3.4 案例 C。
@Scope 同样用于自定义注解,我能可以通过@Scope 自定义的注解来限定注解作用域,实现单例(分局部和全局);
@Scope 需要 Component 和依赖提供者配合才能起作用,对于@Scope 注解的依赖,Component 会持有第一次创建的依赖,后面注入时都会复用这个依赖的实例,实质上@Scope 的目的就是为了让生成的依赖实例的生命周期与 Component 绑定
如果 Component 重建了,持有的@Scope 的依赖也会重建,所以为了维护局部单例需要自己维护 Component 的生命周期。
@Singleton 其实就是一个通过@Scope 定义的注解,我们一般通过它来实现全局单例。但实际上它并不能提前全局单例,是否能提供全局单例还要取决于对应的 Component 是否为一个全局对象。
我们在使用时需要先添加依赖:
dependencies { api 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x'}
添加完依赖后,我们由简入深一个个来介绍具体的使用。
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()); }}
如果创建 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='国产发动机'}
我们提到@Inject 和@Module 都可以提供依赖,那如果我们既在构造函数上通过标记@Inject 提供依赖,又通过@Module 提
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。