简介
dagger2:https://github.com/google/dagger
compile 'com.google.dagger:dagger:2.11'//2017-9-17最新版本为【2.11】
annotationProcessor 'com.google.dagger:dagger-compiler:2.11'//dagger-compiler为编译时期生成代码等相关的类库
Dagger2 是一个Android依赖注入框架,由谷歌开发,最早的版本Dagger1 由Square公司开发。
依赖注入框架主要用于模块间解耦,提高代码的健壮性和可维护性。
Dagger 这个库的取名不仅仅来自它的本意“匕首”,同时也暗示了它的原理。Jake Wharton 在对 Dagger 的介绍中指出,Dagger 即 DAG-er,这里的 DAG 即数据结构中的 DAG:有向无环图(Directed Acyclic Graph)。也就是说,Dagger 是一个基于有向无环图结构的依赖注入库,因此Dagger的使用过程中不能出现循环依赖。
Android开发从一开始的MVC框架,到MVP,到MVVM,不断变化。现在MVVM的data-binding还在实验阶段,传统的MVC框架Activity内部可能包含大量的代码,难以维护,现在主流的架构还是使用MVP的方式。但是 MVP 框架也有可能在Presenter中集中大量的代码,引入DI框架Dagger2 可以实现 Presenter 与 Activity 以及 Presenter 和其它业务逻辑之间的解耦,提高模块化和可维护性。
依赖注入就是将调用者需要的另一个对象实例不在调用者内部实现,而是通过一定的方式从外部传入实例,解决了各个类之间的耦合。
那么这个外部,到底指的是哪里,如果指的是另一个类,那么,另一个类内部不就耦合了?能不能有一种方式,将这些构造的对象放到一个容器中,具体需要哪个实例时,就从这个容器中取就行了。这样,类的实例和使用就不在有联系了,而是通过一个容器将他们联系起来,实现了解耦。这个容器,便是Dagger2。
Dagger2的原理是:在【编译期】生成相应的依赖注入代码。这也是和其他依赖注入框架不同的地方,其他框架是在【运行时】通过【反射】获取【注解内容】,影响了运行效率。
【Dagger1】
Dagger1可以说是如今Android上最流行的依赖注入框架。它是由Square公司受到Guice启发创建的。
基本特点:
- 多个注入点:依赖,通过injected
- 多种绑定方法:依赖,通过provided
- 多个modules:实现某种功能的绑定集合
- 多个对象图: 实现一个范围的modules集合
Dagger1是在编译的时候实行绑定,不过也用到了反射机制。但这个反射不是用来实例化对象的,而是用于图的构成。Dagger会在运行的时候去检测是否一切都正常工作,所以使用的时候会付出一些代价:偶尔会无效和调试困难。
【Dagger2】
Dagger2是Dagger1的分支,由谷歌公司接手开发。Dagger2是受到AutoValue项目的启发。刚开始,Dagger2解决问题的基本思想是:利用生成和写的代码混合达到看似所有的产生和提供依赖的代码都是手写的样子。
如果我们将Dagger2和1比较,他们两个在很多方面都非常相似,但也有很重要的区别,如下:
- 再也没有使用反射:图的验证、配置和预先设置都在编译的时候执行。
- 容易调试和可跟踪:完全具体地调用提供和创建的堆栈
- 更好的性能:谷歌声称他们提高了13%的处理性能
- 代码混淆:使用派遣方法,就如同自己写的代码一样
当然所有这些很棒的特点都需要付出一个代价,那就是缺乏灵活性,例如:Dagger2没用反射所以没有动态机制。
【JSR-330】
为了最大程度的提高代码的复用性、测试性和维护性,java的依赖注入为注入类中的使用定义了一整套注解(和接口)标准。Dagger1和Dagger2(还有Guice)都是基于这套标准,给程序带来了稳定性和标准的依赖注入方法。
使用dagger2的好处
好处:
- 增加开发效率、省去重复的简单体力劳动
首先new一个实例的过程是一个重复的简单体力劳动,dagger2完全可以把new一个实例的工作做了,因此我们把主要精力集中在关键业务上、同时也能增加开发效率上。
省去写单例的方法,并且也不需要担心自己写的单例方法是否线程安全,自己写的单例是懒汉模式还是饿汉模式,因为dagger2都可以把这些工作做了。
- 更好的管理类实例
每个app中的ApplicationComponent管理整个app的全局类实例,所有的全局类实例都统一交给ApplicationComponent管理,并且它们的生命周期与app的生命周期一样。
每个页面对应自己的Component,页面Component管理着自己页面所依赖的所有类实例。
因为Component,Module,整个app的类实例结构变的很清晰。
- 解耦
假如不用dagger2的话,一个类的new代码是非常可能充斥在app的多个类中的,假如该类的构造函数发生变化,那这些涉及到的类都得进行修改。设计模式中提倡把容易变化的部分封装起来。
解耦还有个好处,就是方便测试,若需要替换为网络测试类,只需要修改相应的Module即可。
我们用了dagger2后。
- 假如是通过用Inject注解标注的构造函数创建类实例,则即使构造函数变的天花乱坠,我们基本上都不需要修改任何代码。
- 假如是通过工厂模式Module创建类实例,Module其实就是把new类实例的代码封装起来,这样即使类的构造函数发生变化,只需要修改Module即可。
GitHub中的介绍
A fast dependency injector for Android and Java. 适用于Android和Java的快速依赖注入器。
【About Google's Fork】
Dagger 2 is a compile-time evolution approach to dependency injection. Taking the approach started in Dagger 1.x to its ultimate conclusion, Dagger 2.x eliminates all reflection, and improves code clarity by removing the traditional ObjectGraph/Injector in favor of user-specified @Component interfaces.
Dagger 2是接近依赖注入的一种编译时进化。采用Dagger 1.x开始的方式达成最终结论,Dagger 2.x消除了所有的反射,并通过删除传统的ObjectGraph / Injector来改善代码清晰度,并以用户指定的@Component接口来取代。
evolution [ˌɛvəˈluʃən, ˌivə-] n. 演变; 进化; 发展;
approach [əˈproʊtʃ] vt.接近,走近,靠近; vt.接近; 着手处理; 使移近; n. 方法; 途径; 接近;
in favor of 赞成[支持](某人或某事物); 以…取代; (支票) 以某人[某部门]为受款人;
This github project represents the Dagger 2 development stream. The earlier project page (Square, Inc's repository) represents the earlier 1.0 development stream. Both versions have benefitted from strong involvement from Square, Google, and other contributors.
这个github项目代表了Dagger 2开发流。较早的项目页面(Square,Inc的存储库)代表较早的1.0开发流。这两个版本都受益于Square、Google和其他贡献者的强烈参与。
Dagger is currently in active development, primarily internally at Google, with regular pushes to the open-source community. Snapshot releases are auto-deployed to sonatype's central maven repository on every clean build with the version HEAD-SNAPSHOT.
Dagger 目前正在积极发展,主要是内部在谷歌,定期push到开源社区。快照版本将自动部署到sonatype的*maven存储库,每个干净的版本与版本HEAD-SNAPSHOT。
Dagger 2's main documentation website can be found here. Dagger 2的主要文档网站可以在这里找到。
【Documentation】
You can find the dagger documentation here which has extended usage instructions and other useful information. Substantial usage information can be found in the API documentation.
您可以在这里找到扩展使用说明和其他有用信息的匕首文档。可以在API文档中找到实质的使用信息。
You can also learn more from the original proposal, this talk by Greg Kick, and on the dagger-discuss@googlegroups.com mailing list.
您还可以从原始提案中了解更多信息,Greg Kick的此演讲以及**邮件列表。
【Installation】
You will need to include the dagger-2.x.jar in your application's runtime. In order to activate code generation and generate implementations to manage your graph you will need to include dagger-compiler-2.x.jar in your build at compile time.
您可能需要将dagger-2.x.jar包含在应用程序的运行时中。为了激活代码生成器并生成实现以管理图形,您将需要在编译时将dagger-compiler-2.x.jar包含在构建中。
Gradle配置
1、在项目的build.gradle中添加:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'//每个Android项目默认都会带的
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成DaggerXXXComponent。
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'//这个是需要我们手动添加的,apt是用于自动生成代码来进行依赖注入的
}
android-apt是Gradle编译器的插件,根据其官方文档,主要两个目的:
- 编译时使用该工具,最终打包时不会将该插件打入到apk中。
- 能够根据设置的源路径,在编译时期生成相应代码。
2、在module的build.gradle中添加:
apply plugin: 'com.android.application'//每个Android项目默认都会带的。在build.gradle的第一行
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成DaggerXXXComponent。
apply plugin: 'com.neenbedankt.android-apt'//apt支持
3、在module的build.gradle中添加:
compile 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'//dagger-compiler为编译时期生成代码等相关的类库
在android-apt的文档中,也推荐使用这种方式。因为,编译时期生成代码的类库在运行期并不需要,那么将其分为两个库:运行类库butterknife/dagger和 编译器生成代码类库butterknife-compiler/dagger-compiler。那么在打包时,就不需要将butterknife-compiler/dagger-compiler打入其中,减小APK 的大小。
4、其他配置
If you're using classes in dagger.android you'll also want to include:
compile 'com.google.dagger:dagger-android:2.x'
compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
If you're using a version of the Android gradle plugin below 2.2, see https://bitbucket.org/hvisser/android-apt .
If you're using the Android Databinding library, you may want to increase the number of errors that javac will print. When Dagger prints an error, databinding compilation will halt and sometimes print more than 100 errors, which is the default amount for javac. For more information, see Issue 306.
如果您正在使用Android Databinding库,您可能需要增加javac将打印的错误数量。当Dagger打印错误时,数据绑定编译将停止,有时会打印超过100个错误,这是javac的默认值。 有关详细信息,请参阅问题306。
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xmaxerrs" << "500" // or whatever number you want
}
}
一个简单的Dagger2示例
通过Dagger2的目的是将程序分为三个部分:
- - 实例化部分:对象的实例化。类似于容器,将类的实例放在容器里。
- - 调用者:需要实例化对象的类。
- - 沟通桥梁:利用Dagger2中的一些API 将两者联系。
实例化部分Module:
@Module//作为实例对象的容器
public class MainModule {
String name;
public MainModule(String name) {
this.name = name;
}
@Provides//标注能够提供实例化对象的方法。方法名字可以随意,但建议以provider开头
Person providerPerson() {//先判断Module中是否有提供该对象实例化的方法(根据返回值类型及@Provides注解来判断),如果有则注入返回的对象
return new Person(name);//如果没有,则查找Inject注解的构造函数。如果有,则注入使用此构造方法创建的对象
//如果@Provides方法或@Inject构造方法需要参数,则先按同样的步骤初始化参数。若存在参数但无法初始化参数,则编译不通过。
}
}
沟通桥梁部分Component:
@Component(modules = MainModule.class) //作为桥梁,沟通调用者和依赖对象库
public interface MainComponent {
void inject(MainActivity activity);//定义注入的方法。方法名随意,但建议以inject开头
}
调用者Actvity:
public class MainActivity extends AppCompatActivity {
@Inject Person person;//标注需要注入的对象。注意:使用@Inject时,不能用private修饰成员属性
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerMainComponent.builder().mainModule(new MainModule("白乾涛")).build().inject(this);
Log.i("bqt", person.name);//包青天。如果去掉providerPerson上的【@Provides】注解,则会调用Person具有@Inject的构造方法
}
}
看一下Person类:
public class Person {
public String name;
@Inject
public Person() {
name = "默认的名字";
}
public Person(String name) {
this.name = name;
}
}
总结:dagger2依赖注入的步骤
dagger2进行一次依赖注入的步骤:
根据@Inject注解,查找需要依赖注入的对象。注意:使用@Inject注解时,不能用private修饰成员属性或构造方法,否则编译不通过,提示如下错误:
Error:(16, 25) 错误: Dagger does not support injection into private fields
Error:(54, 10) 错误: Dagger does not support injection into private constructors
- 先判断Module中是否有提供该对象实例化的方法(即:根据返回值类型及是否@Provides注解来判断),如果有:
- 若不存在参数,则直接初始化该类实例,此次依赖注入到此结束!
- 若存在参数,则从**步骤1**开始依次初始化每个参数(PS:若存在参数但无法初始化参数,则编译不通过)
- 若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 若不存在参数,则直接初始化该类实例,此次依赖注入到此结束!
- 若存在参数,则从**步骤1**开始依次初始化每个参数(PS:若存在参数但无法初始化参数,则编译不通过)
- PS:若存在多个@Inject注解的构造函数,需要使用@Qulifier限定符,否则编译不通过,提示如下错误:
Error:(51, 9) 错误: Types may only contain one @Inject constructor.
> java.lang.IllegalStateException: Found multiple @Inject constructors: [Person(), Person(java.lang.String)]
如果以上两种方式都没有提供,则编译不通过,提示如下错误:
Error:(44, 7) 错误: com.bqt.dagger.Person cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
其实这个步骤是一个递归的过程,并且在查找类的实例的过程中Module的级别要高于Inject。
PS:本来通过@Inject就可以完成依赖注入了,为什么还要用到Module类来提供依赖呢?
这是为了适配第三方类库中的类(我们没办法给修改这些类),或那些没有提供构造方法的类(比如单例模式的类、系统类、框架类,比如Activity,其构造方法一般都是私有的),我们无法给这些类的构造方法添加@Inject标注,只能通过Module类来提供依赖。
Component的组织方式
假如一个app中只有一个Component,那这个Component是很难维护、并且变化率是很高,很庞大的,就是因为Component的职责太多了导致的。所以就有必要把这个庞大的Component进行划分,划分为粒度小的Component。
划分的规则建议是这样的:
- 要有一个全局的Component,负责管理整个app的全局类实例,全局类实例是整个app都要用到的类的实例,这些类基本都是单例的
- 每个【页面】对应一个Component,比如一个Activity页面定义一个Component,一个Fragment定义一个Component。当然这不是必须的,有些页面之间的依赖的类是一样的,可以公用一个Component。
为什么以页面为粒度来划分Component?
- 一个app是由很多个页面组成的,从组成app的角度来看一个页面就是一个完整的最小粒度了。
- 一个页面的实现是要依赖各种类的,可以理解成一个页面把各种依赖的类组织起来共同实现一个大的功能,每个页面都组织着自己的需要依赖的类,一个页面就是一堆类的组织者。
- 划分粒度不能太小了。假如使用mvp架构搭建app,划分粒度是基于每个页面的m、v、p各自定义Component的,那Component的粒度就太小了,定义这么多的Component,管理、维护就很非常困难。
整体来说,在Android项目中,以页面划分Component在管理、维护上面相对来说更合理。
Scope的真正用处就在于Component的组织:
- 更好的管理Component之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定义的Scope注解标注这些Component,这些注解最好不要一样,不一样是为了能更好的体现出Component之间的组织方式。还有编译器检查有依赖关系或包含关系的Component,若发现有Component没有用自定义Scope注解标注,则会报错。
- 更好的管理Component与Module之间的匹配关系,编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。
- 可读性提高,如用Singleton标注全局类,这样让程序猿立马就能明白这类是全局单例类。
涉及到的注解介绍
单词释义
Dagger [ˈdæɡɚ] n. 匕首; 短剑; vt. 用剑刺;
Inject [ɪnˈdʒɛkt] vt.(给…)注射(药物等);(给…)注射(液体);(给…) 添加;(给…)投入(资金);
Component [kəmˈpoʊnənt] n. 成分; 组分; 零件; [数] 要素; adj. 成分的; 组成的; 合成的; 构成的;
Module [ˈmɑ:dʒul] n. 模块; 组件; (宇宙飞船上各个独立的) 舱; 测量流水等的单位;
Scope [skoʊp] n.(处理、研究事务的)范围; 眼界,见识; (活动或能力的)余地; 广袤,地域; v.审视,仔细研究;
Singleton [ˈsɪŋɡəltən] n.一个,独身,单独;
Qualifier [ˈkwɑ:lɪfaɪə(r)] n.合格者,已取得资格的人; [语] 修饰语; 预选赛,资格赛;
【Inject】
用来标注所依赖的目标类的实例和所依赖的目标类的构造函数。带有此注解的属性或构造方法将参与到依赖注入中,Dagger2会实例化有此注解的类。
Identifies injectable constructors, methods, and fields. May apply to static as well as instance members. An injectable member may have any access modifier (private, package-private, protected, public). Constructors are injected first, followed by fields, and then methods. Fields and methods in superclasses are injected before those in subclasses. Ordering of injection among fields and among methods in the same class is not specified.
标识可注入的构造函数,方法和字段。可能适用于静态和实例成员。可注射成员可以具有任何访问修饰符。首先注入构造函数,后跟字段,然后是方法。超类中的字段和方法被注入到子类之前。没有指定在同一类中的字段之间和方法之间的注入顺序。
Injectable constructors are annotated with @Inject and accept zero or more dependencies as arguments. @Inject can apply to at most one constructor per class.
可注射构造函数用@Inject注释,并接受零个或多个依赖关系作为参数。 @Inject可以每个类最多只能应用一个构造函数。
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})//方法,构造方法,字段
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Inject {
}
【Module】
带有此注解的类其实是一个简单工厂模式,去作用就是创建对象。带有此注解的类的方法基本都是创建类实例的方法,这些方法需要用@Provides注解,这些方法就是所提供的依赖,Dagger2会在该类中寻找实例化某个类所需要的依赖。
//Annotates a class that contributes to the object graph.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})//类
public @interface dagger.Module {
/**
* Additional额外的、补充的 @Module-annotated classes from which this module is composed 组成.
* The de-duplicated复制出的 contributions of the modules in includes,
* and of their inclusions recursively递归地, are all contributed to the object graph.
*/
Class<?>[] includes() default {};//代表此Module依赖其他Module,效果和在Component中直接依赖多个Module一样。
}
【Provides】
用来标注Module类中创建类实例的方法,这些方法就是所提供的依赖。
/**
* Annotates methods of a Module to create a provider method binding. The method's return type is bound to its returned value.
* The Component component implementation will pass dependencies to the method as parameters.
*/
@Documented
@Target({ElementType.METHOD})//方法
@Retention(RetentionPolicy.RUNTIME)
public @interface dagger.Provides {
Provides.Type type() default Provides.Type.UNIQUE;
public static enum Type {
UNIQUE, SET, SET_VALUES, MAP;
private Type() {
}
}
}
【Component】
带有此注解的类是将Inject和Module联系起来的桥梁。编译后通过调用自动生成的DaggerXXX类的API,便可以从Module中获取依赖,并将依赖注入到Inject中。通过modules属性可以把多个Module加入此Component中。
- dependencies代表需要哪一个Component提供依赖,扩展当前Component的支持的依赖。用起来有几个注意事项。
- module代表通过哪一个Module提供依赖。
/**
* Annotates an interface or abstract class for which a fully-formed, dependency-injected
* implementation is to be generated from a set of modules.
* The generated class will have the name of the type annotated with @Component prepended with Dagger.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})//类
@Documented
public @interface dagger.Component {
Class<?>[] modules() default {};//指定此Component通过哪些Module获取依赖
//A list of types that are to be used as <a href="#component-dependencies"> component dependencies </a>.
Class<?>[] dependencies() default {};//指定此Component依赖于哪些Component,扩展当前Component的支持的依赖
@Target({ElementType.TYPE})
@Documented
public @interface Builder {
}
}
【Qualifier】
限定符,用于区别不同的对象以及提供相应的对象实例的方法。Qualifier是一个元注解,用于注解其他注解,被注解的元素和@Named的作用完全一样。
//Identifies qualifier annotations. Anyone can define a new qualifier.
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Qualifier {
}
【Named】
限定符,用于区别不同的对象以及提供相应的对象实例的方法。Named注解有一个String类型的属性,所以可以直接传递值,但只能传递字符串。
注意:如果在一个字段上添加了Named注解(或任何用Qualifier注解标注的注解),那么必须能在Module中找到相应的提供对象的方法,否则报错。
//String-based Qualifier.
@Qualifier//@Named注解就是被元注解@Qualifier注解的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface javax.inject.Named {
String value() default "";//@Named可以直接传递值,但只能传递字符串
}
【Scope】
注解作用域。Scope是一个元注解,用于注解其他注解,被注解的元素和@Singleton的作用完全一样。
用@Scope标注的注解的主要作用:
- 1、限定对象的作用范围。可以使基于同一Component实例的Module中创建的对象具有单例效果。
比如我们用Scope注解标注一个自定义的名为MyScope的注解,假如在一个ComponentB前面加了@Myscope注解,那么如果对ComponentB管理的某个Module中的某个【ObjectA provideObjectA 】方法也加上@Myscope注解,那么Dagger2会在第一次使用该方法创建ObjectA时将它缓存起来,下次再需要提供ObjectA时,就直接使用缓存的对象,而不会再次创建新的ObjectA。如果没有加@Myscope注解,则会每次都创建一个新的ObjectA对象。
- 2、组织Component
Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection. If multiple threads can access a scoped instance, its implementation should be thread safe. The implementation of the scope itself is left up to the injector.
标识范围注释。 范围注释适用于包含可注入构造函数的类,并控制注射器如何重用类型的实例。默认情况下,如果没有范围注释,则注入器将创建一个实例(通过注入类型的构造函数),使用该实例进行一次注入,然后将其忽略。 如果存在范围注释,注射器可以保留实例以便在稍后的注射中可能重新使用。 如果多个线程可以访问作用域实例,其实现应该是线程安全的。 范围本身的实现由注射器决定。
Annotating scope annotations with @Scope helps the injector detect the case where a programmer used the scope annotation on a class but forgot to configure the scope in the injector. A conservative injector would generate an error rather than not apply a scope.
使用@Scope注释范围注释有助于注入器检测程序员在类上使用范围注释但忘记配置注入器中的范围的情况。 保守的注射器会产生错误,而不是应用范围。
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Scope {}
【Singleton】
作用为:标记为单例模式(这里有坑)。
注意,Singleton注解只是用Scope注解标注的一个普通的注解,其本身没有单例功能。Singleton注解的作用和任何一个用Scope注解标注的注解的作用完全一样。使用Singleton后之所以在某些情况下具有单例效果,更多的是其他方面的原因,而不仅仅是因为添加了Singleton注解的原因。
注意:
- 如果只在@Provides标注的方法上添加@Singleton注解(或者任何用@Scope标注的注解),而没在相应的Component类上添加,则编译失败!
- 相反,如果只在Component类上添加,而不再相应的Module中用@Provides标注的方法上添加,则没有任何效果!
//Identifies识别 a type that the injector only instantiates实例化 once. Not inherited.不可遗传
@Scope//Singleton注解是被元注解@Scope注解的注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface javax.inject.Singleton {}//如果在Module中使用了此注解,在Component上也要使用该注解,否则编译失败
【Subcomponent】
在需要父组件全部的提供对象,这时我们可以用包含方式而不是用依赖方式
//A subcomponent子组件 that inherits继承 the bindings绑定 from a parent Component or Subcomponent.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Documented
public @interface dagger.Subcomponent {
/**
* A list of classes annotated with Module whose bindings are used to generate the subcomponent implementation.
* Note that through the use of通过使用 【Module.includes】 the full set of modules used to implement the subcomponent
* may include more modules that just those listed here.
*/
Class<?>[] modules() default {};
@Target(TYPE)
@Documented
@interface Builder {}
}
2017-9-17
- 增加开发效率、省去重复的简单体力劳动
首先new一个实例的过程是一个重复的简单体力劳动,dagger2完全可以把new一个实例的工作做了,因此我们把主要精力集中在关键业务上、同时也能增加开发效率上。
省去写单例的方法,并且也不需要担心自己写的单例方法是否线程安全,自己写的单例是懒汉模式还是饿汉模式,因为dagger2都可以把这些工作做了。 - 更好的管理类实例
每个app中的ApplicationComponent管理整个app的全局类实例,所有的全局类实例都统一交给ApplicationComponent管理,并且它们的生命周期与app的生命周期一样。
每个页面对应自己的Component,页面Component管理着自己页面所依赖的所有类实例。
因为Component,Module,整个app的类实例结构变的很清晰。 - 解耦
假如不用dagger2的话,一个类的new代码是非常可能充斥在app的多个类中的,假如该类的构造函数发生变化,那这些涉及到的类都得进行修改。设计模式中提倡把容易变化的部分封装起来。
解耦还有个好处,就是方便测试,若需要替换为网络测试类,只需要修改相应的Module即可。
- 假如是通过用Inject注解标注的构造函数创建类实例,则即使构造函数变的天花乱坠,我们基本上都不需要修改任何代码。
- 假如是通过工厂模式Module创建类实例,Module其实就是把new类实例的代码封装起来,这样即使类的构造函数发生变化,只需要修改Module即可。
Dagger 2是接近依赖注入的一种编译时进化。采用Dagger 1.x开始的方式达成最终结论,Dagger 2.x消除了所有的反射,并通过删除传统的ObjectGraph / Injector来改善代码清晰度,并以用户指定的@Component接口来取代。
evolution [ˌɛvəˈluʃən, ˌivə-] n. 演变; 进化; 发展;
approach [əˈproʊtʃ] vt.接近,走近,靠近; vt.接近; 着手处理; 使移近; n. 方法; 途径; 接近;
in favor of 赞成[支持](某人或某事物); 以…取代; (支票) 以某人[某部门]为受款人;
这个github项目代表了Dagger 2开发流。较早的项目页面(Square,Inc的存储库)代表较早的1.0开发流。这两个版本都受益于Square、Google和其他贡献者的强烈参与。
您可以在这里找到扩展使用说明和其他有用信息的匕首文档。可以在API文档中找到实质的使用信息。
您还可以从原始提案中了解更多信息,Greg Kick的此演讲以及**邮件列表。
1、在项目的build.gradle中添加:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.1'//每个Android项目默认都会带的
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成DaggerXXXComponent。
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'//这个是需要我们手动添加的,apt是用于自动生成代码来进行依赖注入的
}
android-apt是Gradle编译器的插件,根据其官方文档,主要两个目的:
- 编译时使用该工具,最终打包时不会将该插件打入到apk中。
- 能够根据设置的源路径,在编译时期生成相应代码。
2、在module的build.gradle中添加:
apply plugin: 'com.android.application'//每个Android项目默认都会带的。在build.gradle的第一行
//PS:在新版本中千万不要加这些,日了狗了,加上去之后反而不会生成DaggerXXXComponent。
apply plugin: 'com.neenbedankt.android-apt'//apt支持
compile 'com.google.dagger:dagger:2.x'
annotationProcessor 'com.google.dagger:dagger-compiler:2.x'//dagger-compiler为编译时期生成代码等相关的类库
compile 'com.google.dagger:dagger-android:2.x'
compile 'com.google.dagger:dagger-android-support:2.x' // if you use the support libraries
annotationProcessor 'com.google.dagger:dagger-android-processor:2.x'
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs << "-Xmaxerrs" << "500" // or whatever number you want
}
}
- - 实例化部分:对象的实例化。类似于容器,将类的实例放在容器里。
- - 调用者:需要实例化对象的类。
- - 沟通桥梁:利用Dagger2中的一些API 将两者联系。
@Module//作为实例对象的容器
public class MainModule {
String name;
public MainModule(String name) {
this.name = name;
}
@Provides//标注能够提供实例化对象的方法。方法名字可以随意,但建议以provider开头
Person providerPerson() {//先判断Module中是否有提供该对象实例化的方法(根据返回值类型及@Provides注解来判断),如果有则注入返回的对象
return new Person(name);//如果没有,则查找Inject注解的构造函数。如果有,则注入使用此构造方法创建的对象
//如果@Provides方法或@Inject构造方法需要参数,则先按同样的步骤初始化参数。若存在参数但无法初始化参数,则编译不通过。
}
}
@Component(modules = MainModule.class) //作为桥梁,沟通调用者和依赖对象库
public interface MainComponent {
void inject(MainActivity activity);//定义注入的方法。方法名随意,但建议以inject开头
}
public class MainActivity extends AppCompatActivity {
@Inject Person person;//标注需要注入的对象。注意:使用@Inject时,不能用private修饰成员属性
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerMainComponent.builder().mainModule(new MainModule("白乾涛")).build().inject(this);
Log.i("bqt", person.name);//包青天。如果去掉providerPerson上的【@Provides】注解,则会调用Person具有@Inject的构造方法
}
}
public class Person {
public String name;
@Inject
public Person() {
name = "默认的名字";
}
public Person(String name) {
this.name = name;
}
}
Error:(16, 25) 错误: Dagger does not support injection into private fields
Error:(54, 10) 错误: Dagger does not support injection into private constructors
- 先判断Module中是否有提供该对象实例化的方法(即:根据返回值类型及是否@Provides注解来判断),如果有:
- 若不存在参数,则直接初始化该类实例,此次依赖注入到此结束!
- 若存在参数,则从**步骤1**开始依次初始化每个参数(PS:若存在参数但无法初始化参数,则编译不通过)
- 若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 若不存在参数,则直接初始化该类实例,此次依赖注入到此结束!
- 若存在参数,则从**步骤1**开始依次初始化每个参数(PS:若存在参数但无法初始化参数,则编译不通过)
- PS:若存在多个@Inject注解的构造函数,需要使用@Qulifier限定符,否则编译不通过,提示如下错误:
Error:(51, 9) 错误: Types may only contain one @Inject constructor.
> java.lang.IllegalStateException: Found multiple @Inject constructors: [Person(), Person(java.lang.String)]
如果以上两种方式都没有提供,则编译不通过,提示如下错误:
Error:(44, 7) 错误: com.bqt.dagger.Person cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
假如一个app中只有一个Component,那这个Component是很难维护、并且变化率是很高,很庞大的,就是因为Component的职责太多了导致的。所以就有必要把这个庞大的Component进行划分,划分为粒度小的Component。
划分的规则建议是这样的:
- 要有一个全局的Component,负责管理整个app的全局类实例,全局类实例是整个app都要用到的类的实例,这些类基本都是单例的
- 每个【页面】对应一个Component,比如一个Activity页面定义一个Component,一个Fragment定义一个Component。当然这不是必须的,有些页面之间的依赖的类是一样的,可以公用一个Component。
为什么以页面为粒度来划分Component?
- 一个app是由很多个页面组成的,从组成app的角度来看一个页面就是一个完整的最小粒度了。
- 一个页面的实现是要依赖各种类的,可以理解成一个页面把各种依赖的类组织起来共同实现一个大的功能,每个页面都组织着自己的需要依赖的类,一个页面就是一堆类的组织者。
- 划分粒度不能太小了。假如使用mvp架构搭建app,划分粒度是基于每个页面的m、v、p各自定义Component的,那Component的粒度就太小了,定义这么多的Component,管理、维护就很非常困难。
整体来说,在Android项目中,以页面划分Component在管理、维护上面相对来说更合理。
Scope的真正用处就在于Component的组织:
- 更好的管理Component之间的组织方式,不管是依赖方式还是包含方式,都有必要用自定义的Scope注解标注这些Component,这些注解最好不要一样,不一样是为了能更好的体现出Component之间的组织方式。还有编译器检查有依赖关系或包含关系的Component,若发现有Component没有用自定义Scope注解标注,则会报错。
- 更好的管理Component与Module之间的匹配关系,编译器会检查 Component管理的Modules,若发现标注Component的自定义Scope注解与Modules中的标注创建类实例方法的注解不一样,就会报错。
- 可读性提高,如用Singleton标注全局类,这样让程序猿立马就能明白这类是全局单例类。
涉及到的注解介绍
单词释义
Dagger [ˈdæɡɚ] n. 匕首; 短剑; vt. 用剑刺;
Inject [ɪnˈdʒɛkt] vt.(给…)注射(药物等);(给…)注射(液体);(给…) 添加;(给…)投入(资金);
Component [kəmˈpoʊnənt] n. 成分; 组分; 零件; [数] 要素; adj. 成分的; 组成的; 合成的; 构成的;
Module [ˈmɑ:dʒul] n. 模块; 组件; (宇宙飞船上各个独立的) 舱; 测量流水等的单位;
Scope [skoʊp] n.(处理、研究事务的)范围; 眼界,见识; (活动或能力的)余地; 广袤,地域; v.审视,仔细研究;
Singleton [ˈsɪŋɡəltən] n.一个,独身,单独;
Qualifier [ˈkwɑ:lɪfaɪə(r)] n.合格者,已取得资格的人; [语] 修饰语; 预选赛,资格赛;
【Inject】
用来标注所依赖的目标类的实例和所依赖的目标类的构造函数。带有此注解的属性或构造方法将参与到依赖注入中,Dagger2会实例化有此注解的类。
Identifies injectable constructors, methods, and fields. May apply to static as well as instance members. An injectable member may have any access modifier (private, package-private, protected, public). Constructors are injected first, followed by fields, and then methods. Fields and methods in superclasses are injected before those in subclasses. Ordering of injection among fields and among methods in the same class is not specified.
标识可注入的构造函数,方法和字段。可能适用于静态和实例成员。可注射成员可以具有任何访问修饰符。首先注入构造函数,后跟字段,然后是方法。超类中的字段和方法被注入到子类之前。没有指定在同一类中的字段之间和方法之间的注入顺序。
Injectable constructors are annotated with @Inject and accept zero or more dependencies as arguments. @Inject can apply to at most one constructor per class.
可注射构造函数用@Inject注释,并接受零个或多个依赖关系作为参数。 @Inject可以每个类最多只能应用一个构造函数。
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD})//方法,构造方法,字段
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Inject {
}
【Module】
带有此注解的类其实是一个简单工厂模式,去作用就是创建对象。带有此注解的类的方法基本都是创建类实例的方法,这些方法需要用@Provides注解,这些方法就是所提供的依赖,Dagger2会在该类中寻找实例化某个类所需要的依赖。
//Annotates a class that contributes to the object graph.
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})//类
public @interface dagger.Module {
/**
* Additional额外的、补充的 @Module-annotated classes from which this module is composed 组成.
* The de-duplicated复制出的 contributions of the modules in includes,
* and of their inclusions recursively递归地, are all contributed to the object graph.
*/
Class<?>[] includes() default {};//代表此Module依赖其他Module,效果和在Component中直接依赖多个Module一样。
}
【Provides】
用来标注Module类中创建类实例的方法,这些方法就是所提供的依赖。
/**
* Annotates methods of a Module to create a provider method binding. The method's return type is bound to its returned value.
* The Component component implementation will pass dependencies to the method as parameters.
*/
@Documented
@Target({ElementType.METHOD})//方法
@Retention(RetentionPolicy.RUNTIME)
public @interface dagger.Provides {
Provides.Type type() default Provides.Type.UNIQUE;
public static enum Type {
UNIQUE, SET, SET_VALUES, MAP;
private Type() {
}
}
}
【Component】
带有此注解的类是将Inject和Module联系起来的桥梁。编译后通过调用自动生成的DaggerXXX类的API,便可以从Module中获取依赖,并将依赖注入到Inject中。通过modules属性可以把多个Module加入此Component中。
- dependencies代表需要哪一个Component提供依赖,扩展当前Component的支持的依赖。用起来有几个注意事项。
- module代表通过哪一个Module提供依赖。
/**
* Annotates an interface or abstract class for which a fully-formed, dependency-injected
* implementation is to be generated from a set of modules.
* The generated class will have the name of the type annotated with @Component prepended with Dagger.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})//类
@Documented
public @interface dagger.Component {
Class<?>[] modules() default {};//指定此Component通过哪些Module获取依赖
//A list of types that are to be used as <a href="#component-dependencies"> component dependencies </a>.
Class<?>[] dependencies() default {};//指定此Component依赖于哪些Component,扩展当前Component的支持的依赖
@Target({ElementType.TYPE})
@Documented
public @interface Builder {
}
}
【Qualifier】
限定符,用于区别不同的对象以及提供相应的对象实例的方法。Qualifier是一个元注解,用于注解其他注解,被注解的元素和@Named的作用完全一样。
//Identifies qualifier annotations. Anyone can define a new qualifier.
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Qualifier {
}
【Named】
限定符,用于区别不同的对象以及提供相应的对象实例的方法。Named注解有一个String类型的属性,所以可以直接传递值,但只能传递字符串。
注意:如果在一个字段上添加了Named注解(或任何用Qualifier注解标注的注解),那么必须能在Module中找到相应的提供对象的方法,否则报错。
//String-based Qualifier.
@Qualifier//@Named注解就是被元注解@Qualifier注解的注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface javax.inject.Named {
String value() default "";//@Named可以直接传递值,但只能传递字符串
}
【Scope】
注解作用域。Scope是一个元注解,用于注解其他注解,被注解的元素和@Singleton的作用完全一样。
用@Scope标注的注解的主要作用:
- 1、限定对象的作用范围。可以使基于同一Component实例的Module中创建的对象具有单例效果。
比如我们用Scope注解标注一个自定义的名为MyScope的注解,假如在一个ComponentB前面加了@Myscope注解,那么如果对ComponentB管理的某个Module中的某个【ObjectA provideObjectA 】方法也加上@Myscope注解,那么Dagger2会在第一次使用该方法创建ObjectA时将它缓存起来,下次再需要提供ObjectA时,就直接使用缓存的对象,而不会再次创建新的ObjectA。如果没有加@Myscope注解,则会每次都创建一个新的ObjectA对象。 - 2、组织Component
Identifies scope annotations. A scope annotation applies to a class containing an injectable constructor and governs how the injector reuses instances of the type. By default, if no scope annotation is present, the injector creates an instance (by injecting the type's constructor), uses the instance for one injection, and then forgets it. If a scope annotation is present, the injector may retain the instance for possible reuse in a later injection. If multiple threads can access a scoped instance, its implementation should be thread safe. The implementation of the scope itself is left up to the injector.
标识范围注释。 范围注释适用于包含可注入构造函数的类,并控制注射器如何重用类型的实例。默认情况下,如果没有范围注释,则注入器将创建一个实例(通过注入类型的构造函数),使用该实例进行一次注入,然后将其忽略。 如果存在范围注释,注射器可以保留实例以便在稍后的注射中可能重新使用。 如果多个线程可以访问作用域实例,其实现应该是线程安全的。 范围本身的实现由注射器决定。
Annotating scope annotations with @Scope helps the injector detect the case where a programmer used the scope annotation on a class but forgot to configure the scope in the injector. A conservative injector would generate an error rather than not apply a scope.
使用@Scope注释范围注释有助于注入器检测程序员在类上使用范围注释但忘记配置注入器中的范围的情况。 保守的注射器会产生错误,而不是应用范围。
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface javax.inject.Scope {}
【Singleton】
作用为:标记为单例模式(这里有坑)。
注意,Singleton注解只是用Scope注解标注的一个普通的注解,其本身没有单例功能。Singleton注解的作用和任何一个用Scope注解标注的注解的作用完全一样。使用Singleton后之所以在某些情况下具有单例效果,更多的是其他方面的原因,而不仅仅是因为添加了Singleton注解的原因。
注意:
- 如果只在@Provides标注的方法上添加@Singleton注解(或者任何用@Scope标注的注解),而没在相应的Component类上添加,则编译失败!
- 相反,如果只在Component类上添加,而不再相应的Module中用@Provides标注的方法上添加,则没有任何效果!
//Identifies识别 a type that the injector only instantiates实例化 once. Not inherited.不可遗传
@Scope//Singleton注解是被元注解@Scope注解的注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface javax.inject.Singleton {}//如果在Module中使用了此注解,在Component上也要使用该注解,否则编译失败
【Subcomponent】
在需要父组件全部的提供对象,这时我们可以用包含方式而不是用依赖方式
//A subcomponent子组件 that inherits继承 the bindings绑定 from a parent Component or Subcomponent.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)//元注解
@Documented
public @interface dagger.Subcomponent {
/**
* A list of classes annotated with Module whose bindings are used to generate the subcomponent implementation.
* Note that through the use of通过使用 【Module.includes】 the full set of modules used to implement the subcomponent
* may include more modules that just those listed here.
*/
Class<?>[] modules() default {};
@Target(TYPE)
@Documented
@interface Builder {}
}
2017-9-17