在你逛Github的时候,如果遇到一些大型的开源项目,恐怕最眼熟的几个关键字就是RxJava、Retrofit、MVP、Dagger2、RetroLambda等等。在之前的文章中,我们先后学习过Rxjava、Retrofit以及Lambda表达式的使用,那么今天我们就来看看Dagger2是什么玩意
Dagger2是一款使用在Java和Android上的依赖注入的一个类库,目前Dagger有两个分支,一个由Square维护,一个为Google在前者的基础上开出的分支,即Dagger2
那么问题来了,你说了这么多基本概念,我还是一头雾水,这个到底能给我带来什么好处呢?
回顾一下我们之前是怎么初始化一个对象的
public class ModelD {
int a;
int b;
public ModelD() {
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
}
这是一个非常标准的对象。使用也是直接声明后初始化,进行相应set/get操作
ModelD modelD;
modelD=new ModelD();
modelD.setA(10);
modelD.setB(20);
Log.d("MainActivity", modelD.getA() + " " + modelD.getB());
这样就是一个典型的用到哪里new到哪里的用例。有没有觉得好心疼?每次检查代码的时候,都得翻遍代码去找哪里new的?为了解决这个问题,统一化管理各种Model,从现在开始,我们就来学习Dagger2
环境搭建
- 在你项目的build.gradle下添加
buildscript {
......
dependencies {
......
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
- 在你主工程的build.gradle下添加
apply plugin: 'com.neenbedankt.android-apt'
......
dependencies {
......
compile 'com.google.dagger:dagger:2.5'
apt "com.google.dagger:dagger-compiler:2.5"
provided 'javax.annotation:javax.annotation-api:1.2'
}
这样就完成了环境搭建了
@Inject
我们先简单接触一下Dagger2,看看他到底有什么功能
Inject即javax.inject.Inject。当你的类需要自动实例化的功能,那么就得在你的构造方法中通过@Inject注解来告诉Dagger2如何实例化。在完成构造方法的注解之后,后续就需要对成员变量进行注解以告诉Dagger2哪个对象需要实例化
那么我们如何使用Dagger2去对之前初始化过程进行改造呢?
public class ModelD {
......
@Inject
public ModelD() {
}
......
}
@Inject
ModelD modelD;
注意这边modelD对象不可以是private
初步改造完成,但是这样还是无法运行的,我们还需要一个桥梁去连接这两部分,谁去做这个连接器呢?它就是Component
@Component
刚说了Component是一个连接器,他通过查找目标类中用Inject注解标注的成员变量,查找到相应的成员变量后,接着查找该成员变量所在的类对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该变量的实例并把实例进行赋值。
Component注解的类只能是接口或抽象类,每个@Component必须至少有一个抽象方法,他们的名字可以是任意的,但是格式必须符合provision或者members-injection,后面还有一种@Subcomponent时需要的一种接口方法,总共就这三种。provision在下期Subcomponent的介绍中会提及,members-injection就是我们之前写的inject方法
@Component
public interface MyComponent {
public void inject(MainActivity activity);
}
这里强调说明下,inject中的内容代表真正消耗依赖的类型,这里是MainActivity。如果你在这边写其他对象,或者重复某个对象,在编译过程中都会出错的
编译之后Dagger2会按照上面接口规定的协议生成一个实现类,通过编译我们可以生成以Dagger为前缀的类,提供builder()来生成实例
public final class DaggerMyComponent implements MyComponent {
private MembersInjector<MainActivity> mainActivityMembersInjector;
private DaggerMyComponent(Builder builder) {
assert builder != null;
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static MyComponent create() {
return builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.mainActivityMembersInjector = MainActivity_MembersInjector.create(ModelD_Factory.create());
}
@Override
public void inject(MainActivity activity) {
mainActivityMembersInjector.injectMembers(activity);
}
public static final class Builder {
private Builder() {}
public MyComponent build() {
return new DaggerMyComponent(this);
}
}
}
这里的injectMembers就是将成员变量注入。
这样就可以直接通过无参构造方法去实例化一个对象了
DaggerMyComponent.builder().build().inject(this);
但是请注意,这种写法不适用于有参构造方法,那么有参构造方法需要怎么实现呢?我们来看看@Module与@Provides
@Modules
Modules相当于一个管理集合,他负责集中创建欲初始化的类的对象,简单的说它就是一个依赖提供者。
直接在构造方法中写Inject注解也是很麻烦的一件事:
- 有时候我们不能使用Inject去注解一个构造方法,比如jar包里面的
- 只可以在一个构造方法中添加Inject注解,不然Dagger2也不知道你初始化的到底是哪个构造方法
- 接口不能使用,谁知道你接口是怎么实现的
所以我们这里就需要使用Provides去注解一个方法来满足依赖。方法的返回类型就是依赖要满足的类型。
@Provides
这是添加该注解的方法,表明用来初始化某个实例对象。
我们将刚才ModelD的初始化用Modules跟Provides来实现以下
public class ModelD {
int a;
int b;
public ModelD(int a, int b) {
this.a=a;
this.b=b;
}
}
@Module
public class TotalModule {
int a;
int b;
public TotalModule(int a, int b) {
this.a=a;
this.b=b;
}
@Provides
public ModelD providesModelD() {
return new ModelD(a, b);
}
}
通过对TotalModule的初始化,将变量用于ModelD的初始化。注意这边Module的类名以及Provides注解的方名的命名规则,尽量做的规范起来
请注意,如果Provides所注解的方法有入参,这个参数一定要被Dagger得到(通过其他的provider方法或者@Inject注解的构造方法获得)。并且方法的返回值类型必须唯一,不能重复
剩下就是用Component去关联起来,不然谁知道这个Module?
@Component(modules = TotalModule.class)
public interface TotalComponent {
void inject(MainActivity activity);
}
有个地方要注意,我们这边TotalModule显示声明了一个带2个参数的构造方法,所以在编译时注解生成的DaggerTotalComponent对象的内部类Builder跟之前就有所区别了,它强制要求我们实现TotalModule
public static final class Builder {
private TotalModule totalModule;
private Builder() {}
public TotalComponent build() {
if (totalModule == null) {
throw new IllegalStateException(TotalModule.class.getCanonicalName() + " must be set");
}
return new DaggerTotalComponent(this);
}
public Builder totalModule(TotalModule totalModule) {
this.totalModule = Preconditions.checkNotNull(totalModule);
return this;
}
}
使用时候跟之前类似
DaggerTotalComponent.builder().totalModule(new TotalModule(2, 3)).build().inject(this);
其实还有一种写法。可能你觉得刚才的写法实在是麻烦,每次TotalModule都初始化相同的数据,不需要这么麻烦
@Module
public class TotalModule2 {
@Provides
public ModelD providesModelD(int a, int b) {
return new ModelD(a, b);
}
@Provides
public int providesInteger() {
return 1;
}
}
@Module的优先级高于@Inject。优先从provides里面获取构造方法,如果找不到,再从Inject里面获取,如果什么都找不到,就报错了
到底为止,就是Dagger2的典型使用方式了
参考文章
Dagger2使用,详细解读和从Dagger1迁移的方法介绍
Dagger2使用详解
Android常用开源工具(2)-Dagger2进阶
本文演示示例已经上传到Github
首次接触Dagger2框架,个人觉得相对于其他框架来说,这玩意还是有些难度的,所以错误之处请多多指点,我也会在空闲时间外根据自己的理解对文章多多改进,谢谢支持
网友评论