前言
Dagger2 当前Android流行的依赖注入框架。未来Android应用的趋势也许是MVP+Retrofit+Dagger+RxJava结合其他高效简便的库来开发。Dagger2更好的解耦了代码。出于兴趣,前段时间开始学习Dagger2依赖注入框架。通过学习前人的文章以及自己写Demo,成功地敲开了Dagger2的大门并了解了Dagger2实现依赖注入的原理。感谢各位前辈的分享,在这里抛砖引玉的讲讲我理解的Dagger2。 一来希望可以帮助像我一样刚刚开始接触Dagger2的小菜鸟更好的入门,二来,以后自己需要用到的时候也可以翻来看看。
好啦,闲话不多扯,上文。
Dagger2 配置
配置Dagger2开发环境很简单。只需两步。
- 在工程的
build.gradle
加入的dependencies
中加入classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
.apt是用于自动生成代码的,Dagger2需要依赖它自动生成代码,下面你就知道了。
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0'
//引入APT,用于自动生成代码
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
注意: 是在工程项目的build.gradle中添加,别添加错了_。
- 在项目即modlue中的
build.gradle
添加四句话:- apply plugin: 'com.neenbedankt.android-apt'(添加在顶部)
- apt 'com.google.dagger:dagger-compiler:2.0'
- compile 'com.google.dagger:dagger:2.0'
- compile 'javax.annotation:javax.annotation-api:1.2'
- compile 'javax.inject:javax.inject:1'
后面三句添加在dependencies
中。
apply plugin: 'com.android.application'
//引入APT
apply plugin: 'com.neenbedankt.android-apt'
.
.
.
dependencies {
//引入Dagger2以及依赖的一些包,因为用到注释所以还需要引用annotation包
apt 'com.google.dagger:dagger-compiler:2.0'
compile 'com.google.dagger:dagger:2.0'
compile 'javax.inject:javax.inject:1'
compile 'javax.annotation:javax.annotation-api:1.2'
}
好啦,在上述文件,添加了依赖和相关配置之后,你就可以开始使用Dagger2了。
。。。。等等,还没告诉我们怎么用呢,怎么就开始了。小编,疏忽。接下来,先给大家讲讲Dagger2的基本概念和几个关键注释。最后,在用简单的例子告诉大家怎么用吧。
依赖注入
刚开始接触Dagger2的各位看官应该很好奇,为什么Dagger2能够成为主流框架之一,得万千开发者的独宠。心想肯定是有什么强大的功能。是的,Dagger2的强大功能就在于依赖注入,解耦,减少new 实例化对象的重复劳动,并且更加适应开发过程中需求的变动(也就是解耦啦!)。先用代码来解释一下什么叫做依赖注入吧。
public class A {
B b;
public A(){
b = new B();
}
}
上面是一个简单反映依赖现象的类A,类A需要(依赖于)B的实例,B被需要(依赖)。A通过new创建了一个B的实例。但是如果B的构造函数改变了,变成了带参数的构造函数。例如:
public class B {
public B(String name) {
}
}
那么A的代码就要修改:
public class A {
B b;
String name = "name";
public A(){
b = new B(name);
}
}
现在只是简单的一个A,如果你编写一个复杂的业务逻辑,许多个类都像A一样依赖于B,那么其他类也要修改,给创建B的代码中添加一个String参数,这将增加了维护和测试成本。需求总是有可能在未来的某个时刻变,每次这样的重复修改,相信你改的欲仙欲死的。
那么怎么解决耦合来更好的应变未来的改变呢,设计模式主张把容易变化抽离出来,我们可以把B的创建抽离出来。通过依赖注入的方式来传递给A。
举个栗子:School里面肯定有Teacher,没有Teacher的School就称不上学校了吧。所以School依赖于Teacher,在这个情景下呢,School就是将要被注入依赖的目标类,而Teacher呢,就是要被注入到目标类的被依赖实例。这个时候我们就可以用依赖注入把Teacher的实例给School。
依赖注入的方式有三种:
public class School implements InjectTeacher {
Teacher mTeacher;
// 1.在构造函数中,直接传入一个teacher对象
public School(Teacher teacher) {
this.mTeacher = teacher;
}
// 2. set Teacher实例
public void setTeacher(Teacher teacher ) {
this.mTeacher = teacher;
}
// 3. 通过接口,传入一个Teacher对象。
@Override
public void injectTeacher(Teacher teacher) {
this.mTeacher = teacher;
}
}
这三种都是依赖注入的方法,就是把Teacher的实例对象提供给School的一个中间桥梁,而在Dagger2里面也有一个这样的桥梁,他就是Componet,是一个接口。待会我们再揭开Component的面纱。其实也就是避免在Shcool里面直接new一个teacher对象。我们常用的有第一种和第二种。而Dagger2呢就是通过接口的方式来实现依赖注入的一种框架。不需要new来创建实例了,直接通过添加@Inject
等注释就可以,创建一个实例对象给要注入依赖的类了。
依赖注入总结 依赖注入涉及三个对象。
- 目标类 :需要依赖其他类。
- 被依赖的类 : 被目标类依赖的类。
- 实现把被依赖的类 注入到目标类的工具(中间桥梁)。
不知道这样讲,大家有没有对依赖注入有那么点了解了。还不理解看官,在查看相关的资料了解了解,我就讲到这里啦。讲的不好,勿喷,不过欢迎指正。
Dagger2 基本概念
在Dagger2 中实现依赖注入,有六个关键的注释。接下来分别给大家讲解一下这七个注释。
@injcet
用于注释目标类里面要被依赖的其他类以及其他类的构造函数,告知Dagger2,这个类要被依赖注入。
PS :@Inject注解的字段不能是private
和protected
的.
原因: 在实现依赖的过程中,需要直接引用成员变量,eg.instance.mDaggerTest
,instance
是目标类,mDaggerTest
是目标类里面一个被依赖的其他类的实例对象,因为需要直接引用,所以不可以是private
或protected
,否则报错。
- 代码范例:
//目标类
public class MainActivity extends Activity {
@Inject
DaggerTest mDaggerTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
//被依赖的类
public class DaggerTest {
@Inject
public DaggerTest() {
Log.e("Dagger", "Dagger1 Test");
}
public void log(){
Log.e("Dagger", "Dagger2 Test");
}
}
@module
@module用于注释自定义的产生依赖实例的类,可以看成是一个简单的工厂类,用于创建类的实例。Dagger2在实现依赖注入时,比如要实例化一个A类的对象,通过查找@moudle注释的类中返回A类对象的方法,来实例化一个A类的对象,从而实现依赖注入。
-
为什么要有module?不是已经有了inject吗?
存在的就是合理的嘛。在介绍inject使用的方法时,我们提到inject是在目标类的 成员变量定义的时候注释,以及在被依赖的类的构造方法上注释。但是有时候我们需要依赖的是第三方库的时候,inject就不适用了,因为我们不能在第三方库的类的构造函数上面添加inject。那怎么办呢?这个时候就需要用到module了,module就是给第三方库提供依赖的一个工厂类。当然不是第三方库的类,也可以通过module注释的类来提供。
代码范例:
@Module
public class DaggerModules {
private DemoApplication mDemoApplication;
public DaggerModules(DemoApplication demoApplication) {
mDemoApplication = demoApplication;
}
@Provides
@Singleton
Context getApplicationContext() {
return mDemoApplication;
}
@Provides
@Singleton
LocationManager provideLocationManager() {
return (LocationManager) mDemoApplication.getSystemService(Context.LOCATION_SERVICE);
}
}
关于module注解的类应该如何编写,在下一篇分析Dagger2的依赖注入实现的代码分析将作详细的分析。敬请期待。。。
@Provider
在@moudle注释的类里面,标记方法的注释。与Component建立连接,实现依赖注入。
@Component
@Component 标记的类相当于依赖注入里面的注入器,是实现依赖注入的中间桥梁,将被依赖类的实例注入到目标类中。
- Component在将被依赖的类注入到目标类的时候,是怎么判断需要的是哪个类型的实例对象呢?
Component是通过@inject 或者 @Provider注释的构造函数或者方法的返回值的类型来确定是否是要注入的类的实例的提供方法。所以返回值的类型非常重要,一定要和目标类里面标注的类型一样。
@Qualifier
- 在上面,提到了Component是通过返回值类型来确定要调用的方法的,那么如果出现下面这种情况呢?
@Module
public class DaggerModules {
private DemoApplication mDemoApplication;
public DaggerModules(DemoApplication demoApplication) {
mDemoApplication = demoApplication;
}
@Provides
Teacher getWomanTeacher() {
return new WomanTeacher();
}
@Provides
Teacher getManTeacher() {
return new ManTeacher();
}
}
Component不知道如何选择了,两个方法都是返回Teacher类型,但是其实返回的对象时不同的对象,如果要依赖ManTeacher,则应该是第二个。很多人把这个现象叫Component迷失,Component只关注返回类型,所以当相同的时候,它就不知道如何选择了。
- 那怎么解决这个问题呢?
这个时候@Qualifier就要发挥它的作用了。Qualifier正如其名,就是限定词,通过添加这个注释可以限定这个方法返回实例的类型。光说不如上代码更通俗易懂。
- 代码范例:
//自定义两个限定词,Man与Woman。用来标示男女。
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Man {}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Woman {}
将上面的代码修改之后如下:
@Module
public class DaggerModules {
private DemoApplication mDemoApplication;
public DaggerModules(DemoApplication demoApplication) {
mDemoApplication = demoApplication;
}
@Provides
@Woman
Teacher getWomanTeacher() {
return new WomanTeacher();
}
@Provides
@Man
Teacher getManTeacher() {
return new ManTeacher();
}
}
在目标类中使用的时候代码如下:
//目标类
public class MainActivity extends Activity {
@Inject
DaggerTest mDaggerTest;
@Inject
@Woman
Teacher mTeacherWoman;
@Inject
@Man
Teacher mTeacherMan;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
这样就可以区分开来啦!这里稍微提一下:@Named 是Dagger预置的一个@Qualifier的实现类。用@Named也是用于区分相同返回值类型的方法的一种注释。
- 代码范例
@Module
public class DaggerModules {
private DemoApplication mDemoApplication;
public DaggerModules(DemoApplication demoApplication) {
mDemoApplication = demoApplication;
}
@Provides
@Named(Woman)
Teacher getWomanTeacher() {
return new WomanTeacher();
}
@Provides
@Named(Man)
Teacher getManTeacher() {
return new ManTeacher();
}
}
目标类只需要把@Man
改成@Named(Man)
,把@Woman
改成@Named(Woman)
就可以了。但是个人觉得直接自定义一个@Qualifier
的标识比用Named好,不用担心括号里面不小心粗心大意地写错了,导致整个程序错误。
@Singleton
@Singleton是Dagger2预置的@Scope的实现类,用于标识这个实例是单例的。
@Scope
@Scope就是限定依赖的对象的,生命周期的范围的一个标识符。可以自定义自己的@Scope,例如常见的有@PerActivity,@PerApplication。而@Singleton是Dagger2预置的实现了@Scope的一个注解,代表了依赖对象时单例的。
Dagger2获取依赖实例的规则
最后我们来讲一下,Dagger2是如何获取要依赖的实例的,因为Module与Inject注解的构造函数都可以提供类的实例,那当需要实例化一个类的时候,肯定有一个规则,才不会天下大乱。Dagger2查找并实例化一个类的规则如下:
步骤1:查找Module中是否存在创建该类的方法。
-
步骤2:若存在创建类方法,查看该方法是否存在参数
- 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
- 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
-
步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
- 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
从上面的步骤可以看出来,module的优先级高于@Inject注解的构造函数。并且如果要实例化的类还依赖与其他的类,那么会先实例化其他的类,然后实例化要依赖的类。当module中找不到创建类的方法,才去找Inject注解的构造函数。
最后的最后,分享给大家一些我觉得不错的入门文档,有兴趣的可以看看,有些讲的还是不错的。 这篇文章是我第一次写的知识分享类文章,有说的不对的地方欢迎指正,多多指教,不喜勿喷。
参考文档