引言
在正式开始见解dagger2之前,我们先看一个例子。
第一个版本:
public class Engine { //车的引擎
public Engine(){}
public void run(){
System.out.println(" Engine ------------> run : "+hashCode());
}
}
public class Car{ //每台车都需要一个引擎
Engine engine;
public Car(){
engine = new Engine();
}
public void run(){
engine.run();
}
}
我们用下图表示两者关系
我们要制造一台汽车,每台汽车里面包含了一个发动机。但我们发现汽车和引擎的结合太紧密了,比如想汽车从汽油机改为柴油机,那就重新造一台汽车,太费事了。于是,我们把汽车类稍为改一下。
第二个版本:
public class Car {
Engine mEngine;
public Car(Engine engine){
mEngine = engine;
}
public void run(){
mEngine.run();
}
}
现在不一样,我们的发动机不是和汽车一起制造的,而变成组装了。是在外面造好之后,再安装进来,他们逻辑关系图就成了以下这样:
这就是最简单的依赖注入(DI)了,Engine是Car的依赖类,现在, 我们改变依赖的实现方式时,我们不需要修改Car源代码。所有它的依赖都是从外面提供的,所以我们只需修改依赖对象。
使用依赖注入的优势是什么呢?
1. 构造/使用 的分离
当我们构造类的实例,通常这些对象会在其它的地方被使用到,使用依赖注入可以让我们的代码更加模块化,并且所有的依赖都可以被很简单地替换掉(只要他们实现了相同的接口),并且不会与我们应用的逻辑产生冲突。
2. 单元测试(Unit testing)
使用依赖注入可以让一个类是完全被隔离,不需要了解它的相关依赖,方便我们编写的单元测试的例子。
3. 独立/并行开发
模块化的代码,它可以非常方便在程序员间进行代码的分离,独立或并行的开发。
当然,除了这些优点之外还有一些缺点。其中一个缺点是会产生很大的模版代码。使用依赖注入框架时,也会增加学习成本。
下面我们开始真正介绍dagger2这个在java和android上的依赖注入框架。
Dagger2
Dagger2是Dagger1的分支,dagger1由Square公司开发,dagger2由谷歌公司接手开发,目前的版本是2.7。Dagger2解决问题的基本思想是:利用编译器apt工具自动生成部分代码和自己手写代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。值得一提的是,dagger2没有用到java反射技术,所以性能相比dagger1有大大的提高。
依赖注入(Dependency Injection简称ID): 就是目标类(目标类需要进行依赖初始化的类)中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建,而是通过技术手段可以把其他的类的已经初始化好的实例自动注入到目标类中。
dagger2注解:
要学习Dagger2,就必须要知道下面这些注解和这其中的每一个概念:
@Inject: 通常在需要依赖的地方使用这个注解。换句话说,你用它告诉Dagger这个类或者字段需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。
@Module: Modules类里面的方法用来专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的依赖。
@Provide: 在modules中可以定义很多方法,只有使用了这个注解,Dagger才知道这个方法是提供依赖类的。
@Component: Components可以说是一个注入器,也可以说是@Inject和@Module的桥梁,它的主要作用就是连接这两个部分。 如果没有以@Components注解的接口,dagger是无法找到相应的@module类的,也就无法实现@Inject对象注入。
@Scope: Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域,即可以限定依赖类实例的生命周期,比如有的跟activity生命周期一样,有的是app生命周期等等。
@Qualifier: 当两个依赖类型一样,无法鉴别他们的时候,我们就可以使用这个注解标示。相当如给依赖打上不同的“tag”,例如:在Android中,我们会需要不同类型的context,所以我们就可以定义 @qualifier注解“@ForApplication”和“@ForActivity”,这样当注入一个context的时候,我们就可以告诉 Dagger我们想要哪种类型的context。
dagger2使用
gradle添加依赖:
项目gradle里添加apt工具依赖:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
app的gradle里添加dagger2依赖:
apply plugin: 'com.neenbedankt.android-apt'//添加插件
//添加依赖
compile 'com.google.dagger:dagger:2.7'
apt 'com.google.dagger:dagger-compiler:2.7'
使用:
@Inject注解,在Dagger 2中有3种不同的方式来提供依赖
1. 构造器注入:@Inject使用在类的构造器上作注解;
2. 属性注入: 给指定的属性作注解;
3. 方法注入:是在类的public方法中作注解。
自定义依赖类:
public class Engine {
//注意这里放在构造器上,如果这个构造器有参数,则它的参数需要被注入,
//同时意味着这个类可以被注入到别的类里
@Inject
public Engine(){
}
public void run(){
System.out.println(" Engine ------------> run : "+hashCode());
}
}
自定义需要注入依赖的类:
public class Car {
@Inject //放在属性成员上,代表这个成员需要被依赖注入
public Engine mEngine;
public Car(){
}
public void run(){
mEngine.run();
}
}
有了上面这两步后,dagger2就可以开始工作了么?,当然不行,dagger2无法聪明到自己去找到Car中正确的Engine类,我们还需要有一个类来告诉dagger2到什么地方去找到需要的依赖。那就是@Component注解。
定义依赖注入的桥梁Component类:
那我们看看这桥梁是怎么工作的:
Component需要引用到目标类的实例,Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。因此我们也可以给Component叫另外一个名字注入器(Injector)
代码如下:
/** * 注意这里Component()参数里没有用到@Module类,这样也是可行的, * 因为上面Engine类里面在构造器上定义@inject注解,相当于已 * 经告诉了dagger2从哪里去找依赖,否则必须制定@Module类。 */
@Component()
public interface CarComponent {//这个类名不固定,但这样更易读
// 注入具体的类中(方法名可随意取,重要的是参数)
void inject(Car car);
}
有了Component桥梁后,就是使用这个component,进行依赖注入了,修改上面Car类,如下:
public class Car {
@Inject
public Engine mEngine;
public Car(){
//特别注意DaggerCarComponent是Dagger2自动生成的
CarComponent carComponent = DaggerCarComponent.builder().build();
//进行注入
carComponent.inject(this);
}
public void run(){
mEngine.run();
}
}
通过以上步骤,一个简单的dagger2依赖注入就完成了。我们可以随便new一个Car出来使用,而不用担心Engine为空了。但上面只是一个特例,Engine类是我们自己写的,我们能在其构造函数上标注@Inject注解,如果我们的依赖是第三方库中的类呢,比如OkHttpClient,这里我们就要用到@Module注解了,下面看一个完整例子:
先建立一个HttpHelper类,用于封装OkHttp操作:
public class HttpHelper {
@Inject //需要依赖注入
OkHttpClient client;
private HttpHelper(){
//这里进行注入
DaggerHttpHelperComponent.builder().build().inject(this);
}
public static HttpHelper getInstance(){
return Holder.httpHelper;
}
private static class Holder{
public static HttpHelper httpHelper = new HttpHelper();
}
//get请求
public void get(String url,final Listener listener){
final Request request = new Request.Builder()
.url(url)
.build();
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call,IOException e) {
listener.onFailed();
}
@Override
public void onResponse(Call call,Response response) throws IOException {
final String s = response.body().string();
listener.onSuccess(s);
}
});
}
//请求回调
public interface Listener{
void onSuccess(String response);
void onFailed();
}
}
再添加一个module类提供上面需要被注入的依赖
@Module
public class HttpHelperModule {
@Provides //必须用这个标注
@Singleton //注意这里代表是单例
OkHttpClient provideOkHttpClient(){
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(60 * 1000,TimeUnit.MILLISECONDS)
.readTimeout(60 * 1000,TimeUnit.MILLISECONDS);
return builder.build();
}
}
添加一个component类,将上面的module和inject联系起来:
@Component(modules = HttpHelperModule.class)
@Singleton //module里面添加了Singleton,则component里面必须也要添加
public interface HttpHelperComponent {
void inject(HttpHelper helper);
}
有了module和component后,dagger2就会自动帮我们生成DaggerHttpHelperComponent类,所以就可以调用 :
DaggerHttpHelperComponent.builder().build().inject(this);
完成依赖注入。其原理就是使用apt在编译时,在build目录下产生相应的java源代码,如上面的DaggerHttpHelperComponent.java代码,大家可以自行分析下。
总结
Inject,Component,Module,Provides是dagger2中的最基础最核心的知识点。奠定了dagger2的整个依赖注入框架:
- Inject主要是用来标注目标类的依赖和依赖的构造函数;
- Component它是一个桥梁,一端是目标类,另一端是目标类所依赖类的实例,它也是注入器(Injector)负责把目标类所依赖类的实例注入到目标类中,同时它也管理Module。
- Module和Provides是为解决第三方类库而生的,Module是一个简单工厂模式,Module可以包含创建类实例的方法,这些方法用Provides来标注
dagger2中Scope(作用域),Qualifier(限定符),Singleton(单例),SubComponent等都是对dagger2中整个注入依赖注入框架进行的补充,我们后面会继续讲解,敬请期待……
github代码:https://github.com/nickyangjun/Dagger2Test
更多精彩Android技术可以关注我们的微信公众号,扫一扫下方的二维码或搜索关注公共号:
Android老鸟