Dagger2初探

2017-01-14 10:46:22来源:http://www.jianshu.com/p/451de9ad1142作者:r17171709人点击

第七城市

在你逛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框架,个人觉得相对于其他框架来说,这玩意还是有些难度的,所以错误之处请多多指点,我也会在空闲时间外根据自己的理解对文章多多改进,谢谢支持




第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台