对于 dagger2 ,也看了不少文章了,之前也在项目中使用过,但是没有系统总结一遍总觉得似懂非懂的感觉,索信今天来总结一下,打通任督二脉~ dagger2 是在编译时期通过 apt 插件自动注入依赖的框架,主要用处是为了模块间解耦。关于依赖注入,可以先看看我上一篇博文 解耦技巧——依赖注入! ,有利于接下来 dagger2 的研究!
一、Dagger2 配置
- 在工程 gradle 文件中添加 apt 插件
dependencies {
classpath 'com.android.tools.build:gradle:2.2.3'
//添加apt插件
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
- 在 app module 中添加依赖
apply plugin: 'com.android.application'
//添加如下代码,应用apt插件
apply plugin: 'com.neenbedankt.android-apt'
...
dependencies {
...
compile 'com.google.dagger:dagger:2.4'
apt 'com.google.dagger:dagger-compiler:2.4'
//java注解
compile 'org.glassfish:javax.annotation:10.0-b28'
...
}
dagger 2 的配置就完成了。
二、Dagger 2 的简单使用
我们先抛开晦涩难懂的概念,从实际使用为主逐个总结!还是先以制造 car 为例子:
public class Car {
private String carType;
@Inject
public Car() {
carType = "宝马";
}
public String getCarType() {
return carType;
}
}
在依赖类的构造方法中添加 @Inject 注解,在 MainActivity 中定义全局 Car 变量,同时添加 @Inject 注解,表示将 Car 实例注入 MainActivity ,这个时候还没完,需要提供一个类之间依赖的桥梁(component),也类似于注入器。新建 MainComponent 接口,如下:
//用@Component表示这个接口是一个连接器,能用@Component注解的只
//能是interface或者抽象类
@Component
public interface MainComponent {
/**
* 需要用到这个连接器的对象,就是这个对象里面有需要注入的属性
* (被标记为@Inject的属性)
* 这里inject表示注入的意思,这个方法名可以随意更改,但建议就
* 用inject开头。
*/
void injectToMain(MainActivity mainActivity);
}
这时候点击 Rebuild 工程,Dagger2 会帮我们根据刚刚添加的注入器(component),自动生成一个 DaggerMainComponent 类,必须在 MainActivity 中调用方法注入才行。具体原理后面说。
DaggerMainComponent.builder()
.build()
.inject(this);
现在 debug 看看。
一辆 car 已经轻松的制造成功了。
这个时候要是 Car 有多个重载构造呢?而且构造携带参数呢?我们尝试着再添加一个构造,也给新构造添加 @Inject 注解。
@Inject
public Car(String carType) {
this.carType = carType;
}
依然 debug 运行。卧槽,报错了~~
于是我们把原来的空构造删除,只留下带参数的构造,再次 debug,咦?还是报错了
Error:(22, 10) 错误: java.lang.String cannot be provided without an @Inject constructor or from an @Provides-annotated method.
java.lang.String is injected at
com.sunnada.daggerdemo.Car.(carType)
com.sunnada.daggerdemo.Car is injected at
com.sunnada.daggerdemo.MainActivity.mCar
com.sunnada.daggerdemo.MainActivity is injected at
com.sunnada.daggerdemo.component.MainComponent.inject(mainActivity)
由此得出结论:@Inject 只能注解其中一个构造方法,不能注解多个构造,Dagger2 不能确认调用哪一个构造来实例化对象
我们现在给依赖类构造添加 @Inject ,由 MainComponent (注入器)注入生成实例。那么这时考虑以下场景:
- 依赖类的构造存在参数,如果我们手动实例化对象的时候,一般是手动传入实参实例化对象,那么现在由 Dagger 2 自动注入该怎么传参呢?
- 假如我们需要注入第三方框架的实例对象,那么显然是不可能给构造添加 @Inject 注解的。
这时候 @Module 就开始登场了。
@Module——类似一个实例工厂,负责生产需要注入的类的实例。
添加 MainModule 类如下:
/*
@Module注解表示这个类提供生成一些实例用于注入
*/
@Module
public class MainModule {
/**
* 这个方法需要一个String参数,在Dagger2注入中,这些参数也是注入形式的,也就是
* 要有其他对方提供参数poems的生成,不然会造成编译出错
*/
@Provides
public Car provideCar(String carType) {
return new Car(carType);
}
/**
* 这里提供了一个生成String的方法,在这个Module里生成Poetry实例时,会查找到这里
* 可以为上面提供String类型的参数
*/
@Provides
public String provideCarType() {
return "奔驰";
}
/**
* @return 返回注入对象
* @Provides 注解表示这个方法是用来创建某个实例对象的,这里我们创建返回Gson对象
* 方法名随便,一般用provideXXX结构
*/
@Provides
public Gson provideGson() {
return new Gson();
}
}
MainModule (对象实例工厂) 建好了,可以看到生产了 Car 实例,同时为 Car 生产了构造参数 String ,生产了 第三方框架 Gson 实例。工厂建好了,需要把工厂跟MainComponent (注入器)连接起来,让MainComponent (注入器)知道要注入哪些实例。
//用@Component表示这个接口是一个连接器,能用@Component注解的只
//能是interface或者抽象类
// modules 可以添加多个,表示连接到实例生产方
@Component(modules = {MainModule.class})
public interface MainComponent {
/**
* 需要用到这个连接器的对象,就是这个对象里面有需要注入的属性
* (被标记为@Inject的属性)
* 这里inject表示注入的意思,这个方法名可以随意更改,但建议就
* 用inject开头。
*/
void injectToMain(MainActivity mainActivity);
}
最后的依赖需求方(MainActivity)代码:
public class MainActivity extends AppCompatActivity {
@Inject
Car mCar;
@Inject
Gson mGson;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainComponent.builder()
.build()
.injectToMain(this);
String carType = mCar.getCarType();
String json = mGson.toJson(mCar);
}
}
debug 运行效果:
清楚的看出 car ,Gson 实例都已成功注入依赖需求方(MainActivity)。这样就解决了 第三方框架对象实例的注入。值得注意的是:这里注入的 car 实例 调用的是有参数的构造,这势必跟 MainModule 中的 provideCarType 有关 。
最后总结 实例构建的过程:
- 构建实例时,首先从 Module 中查找实例创建方法。
- 若 Module 中存在创建方法,则看此创建方法有无参数
- 若有参数,这些参数也是由 Component 提供的,返回步骤1查找类实例创建方法,逐一生成参数实例,最后再生成最终类实例。
- 若无参数,直接由该方法生成实例。
- 若 Module 中没有创建实例方法,则从构造函数中查找用 @Inject 注解的构造方法。
- 若该构造有参数,则也是返回步骤1逐一生成参数实例,最终调用该构造生成实例*
- 若该构造无参数,则直接调用生成实例。
从以上实例构建过程可以得出结论:Module 提供的实例是优先于 添加 @Inject 注解的构造方法的,Module 类似于一个生产实例的工厂,一旦没有 Module ,@Inject 只适合生成无参数的构造实例,如果有参数,则必须 添加 Module。
最后,该有的思考
有些人或许会觉得这样代码反而变啰嗦了,有啥好处?其实在上面举的例子相对简单,但是假如代码量越来越复杂的时候,一个依赖类在各个地方都实例化,这时候依赖方与依赖类耦合度很强。假如依赖类构造一改变,会造成很大的问题。关于解耦相关,可以看看我之前这篇文章解耦技巧——依赖注入!而我们用 Dagger2 来自动注入所需依赖,所有的实例全部用 Module 来管理,一旦需求改变,也只需修改对应的 Module 即可。
声明:关于 Dagger2 的文章在网上也是一抓一大把了,可是光看不自己总结往往效果不好,所以自己参考一些文章先总结一下 Dagger2 的基本用法,不好之处还请指出。
参考链接:Dagger2图文完全教程,Android:dagger2让你爱不释手-重点概念讲解、融合篇,Android:dagger2让你爱不释手-基础依赖注入框架篇,Android:dagger2让你爱不释手-终结篇
更多原创文章会在公众号第一时间推送,欢迎扫码关注 张少林同学