这篇文章主要谈一下本人在学习Dagger2的心得,若有错漏,敬请谅解。json
依赖注入就是把下面这样的代码:数组
class A { public A() { } class B { A a; public B() { a = new A(); } } class C { A a; B b; public C() { a = new A(); b = new B(); b.a = a; } }
main() { C c = new C(); }
变成:框架
class A { A() { } } class B { A a; B(A a) { this.a = a; } } class C { A a; B b; C(A a, B b) { this.a = a; this.b = b; } }
main() { A a = new A(); B b = new B(a); C c = new C(a, b); }
这种把对象之间的依赖生成的责任交给外界的作法,叫作依赖注入。ide
咱们有类和它们之间的依赖关系,便很天然地会用图来表示这种状态。如上例子所示,可用下面这样一个图来表示:学习
+-----+ | | +----> | A | <----+ | | | | | +-----+ | | | | | +---+---+ +---+---+ | | | | | B | <---------+ C | | | | | +-------+ +-------+
箭头表示依赖的对象。ui
咱们想要这样的一种依赖注入框架:当咱们须要一个B对象时,框架按照依赖遍历这个图,生成A,而后将其注入B,最后返回一个已经生成好的B对象。大概是:this
B b = Injector.create(B.class)设计
另外,若是要求A对象是单例(这里不解释什么是单例)或对象的生成符合某种指定的规则,框架应自动识别并做出处理。code
咱们面对两个主要问题:如何表示依赖图和如何生成对象。component
咱们需定义一种声明依赖的方法。能够用xml,json,甚至DSL来完成这个任务。这里咱们采用比较流行和简便的注解(annotation)来表示依赖关系。
假设咱们要的效果以下所示:
@dependence class A { } @dependence(A.class) class B { } @dependence({A.class, B.class}) class C { }
能够看到,咱们用@dependence注解来表示上面例图中的箭头。各个类之间的依赖关系十分清晰。
若是要求A是单例,咱们能够这样:
@singleton @dependence() class A { }
创建了依赖图之后,须要经过某种方式生成咱们须要的对象。咱们但愿是这样的:
B b = Injector.create(B.class)
或者经过注解实现自动注入
class Main { @Inject B b; main() { Injector.inject(this); } }
咱们来看一下Dagger2是如何实现上述两个目标的。
Dagger2中,是经过@Inject注解或者@Module和@Provide这两个注解创建依赖图,以下所示:
首先定义好类:
public class A { public A(){ } } public class B { A a; public B(A a) { this.a = a; } } public class C { A a; B b; public C(A a, B b) { this.a = a; this.b = b; } }
而后咱们用第一种方法来声明依赖:
public class A { @Inject public A() { } } public class B { A a; @Inject public B(A a) { this.a = a; } } public class C { A a; B b; @Inject public C(A a, B b) { this.a = a; this.b = b; } }
能够看到咱们为每个类的方法添加了@Inject声明,表示该类是依赖图中的一个节点。若是该初始化方法含有参数,那么这些从参数也应是依赖图中的节点。
第二种方法是经过一个module类来声明依赖,以下所示:
@Module public class ABCModule { @Provides public A provideA() { return new A(); } @Provides public B provideB(A a) { return new B(a); } @Provides public C provideC(A a, B b) { return new C(a, b); } }
@Module注解表示这个ABCModule的做用是声明“依赖图”的。@Provides注解表示当前方法的返回值是图中的一个节点,方法的参数是依赖的对象,即前文中箭头指向的目标。
再强调一次,Dagger要求图中的每个节点都要声明,即每个节点都要在module中有@Provides注解的方法或者@Inject注解的初始化方法。
能够看到第二种方式(module)无需修改原来的对象。为了让模块尽可能少地依赖第三方库,通常采用第二种方式来声明依赖图。
Dagger2中,从依赖图中获取对象需经过component。component是依赖图和被注入对象之间的桥梁。以下所示:
@Component(module=ABCModule.class) public interface ABCComponent { public A provideA(); public B provideB(); public C provideC(); void inject(Activity mainActivity); }
@Component注解表示ABCComponent这个接口是一个Component。Component的方法隐含着以下两条规则:
不带参数的方法为“provider”方法,该方法的返回值是从依赖图中取得的对象。以下所示(伪代码):
class Main { C c; public void init() { c = Component.provideC(); } }
带参数的方法,参数为“注入对象”。一般于@Inject标签同时使用。以下所示(伪代码):
class Main { @Inject C c; public void init() { Component.inject(this); } }
即调用Component.inject(foorbar)的时候,框架自动为用@Inject标签标注的属性注入依赖。要求@Inject的属性的类必须是依赖图中的节点。
注意:component的方法必需至少符合以上两条规则中的一条。
注意:provider方法的名字通常为“provider”,inject方法的名字通常为“inject”,但名字不影响这两个方法的功能。
当Component声明好之后,框架会在编译时生成一个DaggerComponent名字的类,咱们能够用它来实施依赖注入,以下所示:
ABCComponent abcComponent = DaggerABCComponent.create(); A a = abcComponent.provideA(); B b = abcComponent.provideB(); C c = abcComponent.provideC();
或者:
class Main { @Inject A a; @Inject B b; @Inject C c; public static void main() { ABCComponent abcComponent = DaggerABCComponent.create(); abcComponent.inject(this); } }
Component标签的module属性能够是一个数组,即一个Component实施多个module的注入。引入类D和DModule:
class D { public D() { } } @Module public class DModule { @Provides public D provideD() { return new D(); } }
修改ABCComponent,以下:
@Component(module={ABCModule.class, DModule.class}) public interface ABCComponent { public A provideA(); public B provideB(); public C provideC(); public D provideD(); void inject(Activity mainActivity); }
如上便可实现D对象的注入。
真正实施工程的时候,会将对象以功能分类。例如network相关,DB相关,Util相关的类集中在一块儿管理。Dagger2为方便咱们达到这一个目的,在component中引入了dependence这个功能。
例如咱们有以下component
@Component(modules = DModule.class) public interface DComponent { D provideD(); }
假设DComponent负责提供一个对象D。这种能力是项目无关的,咱们把这个Component独立出来。而后咱们能够经过@Component的dependence属性来为其余Component引入DComponent的能力。例如:
@Component(modules = ABCModule.class, dependencies = DComponent.class) public interface ABCComponent { A provideA(); B provideB(); C provideC(); D provideD(); void inject(Main main); }
能够看到,声明了dependencies=DComponent.class之后,provideD方法能够顺利拿到D对象。inject方法也能够注入D对象。
public class Main { @Inject D d; // inject D by ABCComponent public Main() { DComponent dComponent = DaggerDComponent.create(); D d1 = dComponent.provideD(); // inject D by DComponent ABCComponent abcComponent = DaggerABCComponent .builder() .dComponent(dComponent) .build(); D d2 = abcComponent.provideD(); abcComponent.inject(this); } }
DComponent不知道ABCComponent的存在,故能够像普通Component那样子使用。但在使用ABCComponent时,咱们须要显式地为ABCComponent注入DComponent对象:
ABCComponent abcComponent = DaggerABCComponent .builder() .dComponent(dComponent) .build();
如上面例子所示,若是要求D对象为单例,能够经过@Singleton注解来实现。首先咱们须要在依赖图中声明对象是单例的:
@Module public class DModule { @Provides @Singleton public D provideD() { return new D(); } }
DComponent接口也须要声明:
@Singleton @Component(modules = DModule.class) public interface DComponent { D provideD(); }
如此,当咱们注入D对象时,可保证每次注入的是同一个D对象:
DComponent dComponent = DaggerDComponent.create(); D d1 = dComponent.provideD(); D d2 = dComponent.provideD(); // d1 == d2
这篇文章只是简单地介绍了Dagger2的基本用法,下一篇打算讲Dagger2中的Scope和Subcomponent还在一些别的东西,敬请期待。