Android常用开源工具(1)-Dagger2入门
2016-06-15 12:12
411 查看
介绍
Dagger 2是一种依赖注入的框架,能够在编译时自动生成出一些代码,这些代码可以帮助对应的实例初始化。
举个具体的例子,一个容器里面装的是苹果,不用Dagger2的情况下我们应该这么写:
2
3
4
上面例子面临着一个问题,Container依赖了Apple实现,如果某一天需要修改Apple为Banana,那么你一定得改Container的代码。有没有一种方法可以不改Container呢?
可以使用Dagger2,我们可以把代码改成
2
3
4
5
这样,Container的成员变量就自动初始化成Apple实例了,Container不用关心具体用哪个Fruit的实现,也不用关心到底用什么颜色多大的苹果。假如某一天要把苹果替换成香蕉,Container的代码是完全不需要改动的。从某种意义上说,Dagger2就是一个帮你写工厂代码的工具。当然Dagger2的功能比工厂模式更加强大。
Dagger2要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。
Container就是可以被注入的容器,具体对应上文例子中的Container,Container拥有需要被初始化的元素。需要被初始化的元素必须标上@Inject,只有被标上@Inject的元素才会被自动初始化。@Inject在Dagger2中一般标记构造方法与成员变量。
Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从Module生产的。
有了可以被注入的容器Container,也有了提供依赖对象的Module。我们必须将依赖对象注入到容器中,这个过程由Component来执行。Component将Module中产生的依赖对象自动注入到Container中。
project的build.gradle添加
2
3
4
module的build.gradle添加
2
3
4
5
6
7
8
实现Module
Module其实就是一个依赖的制造工厂。我们只需要为其添加制造依赖的方法即可,继续上文实现苹果容器的例子。
2
3
4
5
6
7
(1)中添加注解@Module注明本类属于Module
(2)中添加注解@Provides注明该方法是用来提供依赖对象的特殊方法
一个完整的Module必须拥有@Module与@Provides注解
实现Component
Component就是一个将Module生成的实例注入Container中的注入器。我们来写一个水果注入器:
2
3
4
(3)中@Component使用modules指向使用的Module的集合。
(4)所有的Component都必须以接口形式定义。Dagger2框架将自动生成Component的实现类,对应的类名是Dagger×××××,这个例子中对应的实现类是DaggerFruitComponent
(5)中添加注入方法,一般使用inject做为方法名,方法参数为对应的Container
实现Container
Container就是可以被注入依赖关系的容器。具体实现如下
2
3
4
5
6
7
Container除了代码中(6)标记f需要被注入外,还需要代码中(7)调用Component的实现类将Module的生成的对象注入到f中。
到此,当调用Container的init()方法时,Contianer中的f将会自动初始化成实
15080
现类Apple的对象。
以后如果想更改Fruit的实现类,只需要在@Component中的modules指向不同的Module即可。而Container的代码完全不需要改动。因为Container已经不再依赖Apple实现了。
尽管Dagger2看起来很容易,但其实里面各种细节很值得注意。
Module中@Provides方法可以带输入参数,其参数由Module集合中的其他@Provides方法提供,或者自动调用构造方法
下面是其他@Provides方法提供的例子
2
3
4
5
6
7
8
9
10
11
12
如果找不到@Provides方法提供对应参数的对象,自动调用带@Inject参数的构造方法生成相应对象
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
一个Component可以包含多个Module,这样Component获取依赖时候会自动从多个Module中查找获取,Module间不能有重复方法。添加多个Module有两种方法,一种是在Component的注解@Component(modules={××××,×××}) 添加多个modules,如下
2
3
4
另外一种添加多个Module的方法可以被使用Module中的@Module(includes={××××,×××}),如下
2
3
4
5
6
7
8
这种使用Module的includes的方法一般用于构建更高层的Module时候使用。
上面简单例子中,当调用DaggerFruitComponent.creaete()实际上等价于DaggerFruitComponent.builder().build()。可以看出,DaggerFruitComponent使用了构造者模式。在构建的过程中,默认使用Module无参构造器产生实例。如果需要传入特定的Module实例,可以使用
2
3
4
如果Module只有有参构造器,则必须显式传入Module实例。
当有Fruit需要注入时,Dagger2就会在Module中查找返回类型为Fruit的方法,也就是说,Dagger2是按照Provide方法返回类型查找对应的依赖。但是,当Container需要依赖两种不同的Fruit时,你就需要写两个@Provides方法,而且这两个@Provides方法都是返回Fruit类型,靠判别返回值的做法就行不通了。这就需要使用@Named来区分,如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这样,只有相同的@Named的@Inject成员变量与@Provides方法才可以被对应起来。
如果觉得@Named只能用字符串区分不满足需求,你也可以自定义类似@Named的注解,使用元注解@Qualifier可以实现这种注解,比如实现一个用int类型区分的@IntNamed
2
3
4
5
6
接下来使用我们定义的@IntNamed来修改上面FruitA,FruitB的例子如下
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1)对应上面苹果容器的例子,Component的方法输入参数一般只有一个,对应了需要注入的Container。有输入参数返回值类型就是void
2)Component的方法可以没有输入参数,但是就必须有返回值:
Step1:返回的实例会先从事先定义的Module中查找,如果找不到跳到Step2
Step2 : 使用该类带@Inject的构造器来生成返回的实例,并同时也会递归注入构造器参数以及带@Inject的成员变量。比如
2
3
4
5
6
7
8
9
10
11
12
13
上述代码会生成ComponentB的实现类DaggerComponetB,调用其apple()方法会自动使用Apple(info)构造器生成实例返回。
3 ) 假设ComponentA依赖ComponentB,B必须定义带返回值的方法来提供A缺少的依赖
ComponentA依赖ComponentB的代码如下
2
3
4
5
6
7
8
9
10
这样,当使用ComponentA注入Container时,如果找不到对应的依赖,就会到ComponentB中查找。但是,ComponentB必须显式把这些A找不到的依赖提供给A。怎么提供呢,只需要在ComponentB中添加方法即可,如下
2
3
4
5
6
7
1)@Inject可以标记Container中的成员变量,但是这些成员变量要求是包级可见,也就是说@Inject不可以标记private类型的成员变量。
2)当@Inject标记成员变量时,查找对应依赖按照以下规则
如果说因为Dagger2能生成工厂代码就使用Dagger2,那么有些牵强。Dagger2除了能产生工厂代码,还有其他强大的功能,这些也是使用Dagger2的理由。
Scope,Multibinding,Subcomponent,Provider与Lazy,这些将在下一篇文章介绍。
Dagger 2是一种依赖注入的框架,能够在编译时自动生成出一些代码,这些代码可以帮助对应的实例初始化。
举个具体的例子,一个容器里面装的是苹果,不用Dagger2的情况下我们应该这么写:
public class Container{ Fruit f=new Apple(color,size); ... }1
2
3
4
上面例子面临着一个问题,Container依赖了Apple实现,如果某一天需要修改Apple为Banana,那么你一定得改Container的代码。有没有一种方法可以不改Container呢?
可以使用Dagger2,我们可以把代码改成
public class Container{ @Inject Fruit f; ... }1
2
3
4
5
这样,Container的成员变量就自动初始化成Apple实例了,Container不用关心具体用哪个Fruit的实现,也不用关心到底用什么颜色多大的苹果。假如某一天要把苹果替换成香蕉,Container的代码是完全不需要改动的。从某种意义上说,Dagger2就是一个帮你写工厂代码的工具。当然Dagger2的功能比工厂模式更加强大。
结构
Dagger2要实现一个完整的依赖注入,必不可少的元素有三种,Module,Component,Container。 Container就是可以被注入的容器,具体对应上文例子中的Container,Container拥有需要被初始化的元素。需要被初始化的元素必须标上@Inject,只有被标上@Inject的元素才会被自动初始化。@Inject在Dagger2中一般标记构造方法与成员变量。
Module 可以说就是依赖的原材料的制造工厂,所有需要被注入的元素的实现都是从Module生产的。
有了可以被注入的容器Container,也有了提供依赖对象的Module。我们必须将依赖对象注入到容器中,这个过程由Component来执行。Component将Module中产生的依赖对象自动注入到Container中。
简单的例子
配置
project的build.gradle添加dependencies { ... // 其他classpath classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' //添加apt命令 }1
2
3
4
module的build.gradle添加
// 添加其他插件 apply plugin: 'com.neenbedankt.android-apt'//添加apt命令 dependencies { apt 'com.google.dagger:dagger-compiler:2.0.2' //指定注解处理器 compile 'com.google.dagger:dagger:2.0.2' //dagger公用api provided 'org.glassfish:javax.annotation:10.0-b28' //添加android缺失的部分javax注解 }1
2
3
4
5
6
7
8
实现
实现Module Module其实就是一个依赖的制造工厂。我们只需要为其添加制造依赖的方法即可,继续上文实现苹果容器的例子。
@Module //1 注明本类属于Module public class FruitModule{ @Provides //2 注明该方法是用来提供依赖对象的特殊方法 public Fruit provideFruit(){ return new Apple(Color.RED,Size.BIG); } }1
2
3
4
5
6
7
(1)中添加注解@Module注明本类属于Module
(2)中添加注解@Provides注明该方法是用来提供依赖对象的特殊方法
一个完整的Module必须拥有@Module与@Provides注解
实现Component
Component就是一个将Module生成的实例注入Container中的注入器。我们来写一个水果注入器:
@Component(modules={FruitModule.class}) //3 指明Component在哪些Module中查找依赖 public interface FruitComponent{ //4 接口,自动生成实现 void inject(Container container); //5 注入方法,在Container中调用 }1
2
3
4
(3)中@Component使用modules指向使用的Module的集合。
(4)所有的Component都必须以接口形式定义。Dagger2框架将自动生成Component的实现类,对应的类名是Dagger×××××,这个例子中对应的实现类是DaggerFruitComponent
(5)中添加注入方法,一般使用inject做为方法名,方法参数为对应的Container
实现Container
Container就是可以被注入依赖关系的容器。具体实现如下
public Container{ @Inject //6 添加@Inject,标记f可以被注入 Fruit f; public void init(){ DaggerFruitComponent.create().inject(this); //7 使用FruitComponent的实现类注入 } }1
2
3
4
5
6
7
Container除了代码中(6)标记f需要被注入外,还需要代码中(7)调用Component的实现类将Module的生成的对象注入到f中。
到此,当调用Container的init()方法时,Contianer中的f将会自动初始化成实
15080
现类Apple的对象。
以后如果想更改Fruit的实现类,只需要在@Component中的modules指向不同的Module即可。而Container的代码完全不需要改动。因为Container已经不再依赖Apple实现了。
拓展
尽管Dagger2看起来很容易,但其实里面各种细节很值得注意。
为@Provides方法添加输入参数
Module中@Provides方法可以带输入参数,其参数由Module集合中的其他@Provides方法提供,或者自动调用构造方法 下面是其他@Provides方法提供的例子
@Module public class FruitModule{ //8输入参数自动使用到provideFruit()的返回值Color.RED @Provides public Fruit provideFruit(Color color){ return new Apple(color,Size.BIG); } @Provides pulic Color provideFruit(){ return Color.RED; } }1
2
3
4
5
6
7
8
9
10
11
12
如果找不到@Provides方法提供对应参数的对象,自动调用带@Inject参数的构造方法生成相应对象
@Module public class FruitModule{ @Provides public Fruit provideFruit(FruitInfo info){//自动查找到FruitInfo中带@Inject的无参构造器并生成实例传入参数info return new Apple(info); } } public class FruitInfo{ Color mColor; Size mSize; @Inject FruitInfo(){ mColor=Color.RED; mSize=Size.BIG; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
添加多个Module
一个Component可以包含多个Module,这样Component获取依赖时候会自动从多个Module中查找获取,Module间不能有重复方法。添加多个Module有两种方法,一种是在Component的注解@Component(modules={××××,×××}) 添加多个modules,如下@Component(modules={ModuleA.class,ModuleB.class,ModuleC.class}) //添加多个Module public interface FruitComponent{ ... }1
2
3
4
另外一种添加多个Module的方法可以被使用Module中的@Module(includes={××××,×××}),如下
@Module(includes={ModuleA.class,ModuleB.class,ModuleC.class}) public class FruitModule{ ... } @Component(modules={FruitModule.class}) //添加多个Module public interface FruitComponent{ ... }1
2
3
4
5
6
7
8
这种使用Module的includes的方法一般用于构建更高层的Module时候使用。
Module实例的创建
上面简单例子中,当调用DaggerFruitComponent.creaete()实际上等价于DaggerFruitComponent.builder().build()。可以看出,DaggerFruitComponent使用了构造者模式。在构建的过程中,默认使用Module无参构造器产生实例。如果需要传入特定的Module实例,可以使用DaggerFruitComponent.builder() .moduleA(new ModuleA()) //指定Module实例 .moduleB(new ModuleB()) .build()。1
2
3
4
如果Module只有有参构造器,则必须显式传入Module实例。
区分返回类型相同的@Provides 方法
当有Fruit需要注入时,Dagger2就会在Module中查找返回类型为Fruit的方法,也就是说,Dagger2是按照Provide方法返回类型查找对应的依赖。但是,当Container需要依赖两种不同的Fruit时,你就需要写两个@Provides方法,而且这两个@Provides方法都是返回Fruit类型,靠判别返回值的做法就行不通了。这就需要使用@Named来区分,如下://定义Module @Module public class FruitModule{ @Named("typeA") @Provides public Fruit provideApple(){ //提供Apple给对应的mFruitA return new Apple(); } @Named("typeB") @Provides public Fruit provdeBanana(){ //提供Banana给对应的mFruitB return new Banana() } } //定义Component @Component(modules={FruitModule.class}) interface FruitComponent{ //Dagger根据接口自动生成FruitComponent void inject(Container container); } //定义Container class Container{ @Named("typeA") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的元依赖 @Inject Fruit mFruitA; @Named("typeB") //添加标记@Name("typeA"),只获取对应的@Name("typeA")的依赖 @Inject Fruit mFruitB; ... public void init(){ DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的实现类注入 } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
这样,只有相同的@Named的@Inject成员变量与@Provides方法才可以被对应起来。
如果觉得@Named只能用字符串区分不满足需求,你也可以自定义类似@Named的注解,使用元注解@Qualifier可以实现这种注解,比如实现一个用int类型区分的@IntNamed
@Qualifier //必须,表示IntNamed是用来做区分用途 @Documented //规范要求是Documented,当然不写也问题不大,但是建议写,做提示作用 @Retention(RetentionPolicy.RUNTIME) //规范要求是Runtime级别 public @interface IntNamed{ int value(); }1
2
3
4
5
6
接下来使用我们定义的@IntNamed来修改上面FruitA,FruitB的例子如下
//定义Module @Module class FruitModule{ @IntName(1) @Provides public Fruit provideApple(){ //提供Apple给对应的mFruitA return new Apple(); } @IntName(2) @Provides public Fruit provdeBanana(){ //提供Banana给对应的mFruitB return new Banana() } } //定义Component @Component(modules={FruitModule.class}) interface FruitComponent{ //Dagger根据接口自动生成FruitComponent void inject(Container container); } //定义Container class Container{ @IntName(1) //添加标记@IntName(1),只获取对应的@IntName(1)的元依赖 @Inject Fruit mFruitA; @IntName(2) //添加标记@IntName(2),只获取对应的@IntName(2)的依赖 @Inject Fruit mFruitB; ... public void init(){ DaggerFruitComponent.creaete().inject(this); //使用FruitComponent的实现类注入 } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Component定义方法的规则
1)对应上面苹果容器的例子,Component的方法输入参数一般只有一个,对应了需要注入的Container。有输入参数返回值类型就是void 2)Component的方法可以没有输入参数,但是就必须有返回值:
Step1:返回的实例会先从事先定义的Module中查找,如果找不到跳到Step2
Step2 : 使用该类带@Inject的构造器来生成返回的实例,并同时也会递归注入构造器参数以及带@Inject的成员变量。比如
//定义ComponentB @Component(modules={××××××××})//1.假设Module中没有provideApp()方法,但有provideInfo() interface ComponentB{ Apple apple(); //2.实现类自动返回由Apple(info)构建的实现类 } public class Apple{ @Inject Apple(Info info){//被@Inject标记,使用这个构造器生成实例 ... } Apple(){ //不会使用这个构造器,没有被@Inject标记 } }1
2
3
4
5
6
7
8
9
10
11
12
13
上述代码会生成ComponentB的实现类DaggerComponetB,调用其apple()方法会自动使用Apple(info)构造器生成实例返回。
3 ) 假设ComponentA依赖ComponentB,B必须定义带返回值的方法来提供A缺少的依赖
ComponentA依赖ComponentB的代码如下
//定义ComponentB @Component(modules={××××××××}) interface ComponentB{ ... } //定义ComponentA @Component(dependencies={ComponentB.class},modules={××××××××})//使用dependencies interface ComponentA{ ... }1
2
3
4
5
6
7
8
9
10
这样,当使用ComponentA注入Container时,如果找不到对应的依赖,就会到ComponentB中查找。但是,ComponentB必须显式把这些A找不到的依赖提供给A。怎么提供呢,只需要在ComponentB中添加方法即可,如下
@Component(modules={××××××××}) interface ComponentB{ // 假设A中module中找不到apple,banana,oranges,但是B的module有,B必须提供带返回值的方法如下 Apple apple(); Banana banana(); Oranges oranges(); }1
2
3
4
5
6
7
Container中的@Inject的规则
1)@Inject可以标记Container中的成员变量,但是这些成员变量要求是包级可见,也就是说@Inject不可以标记private类型的成员变量。 2)当@Inject标记成员变量时,查找对应依赖按照以下规则
1.该成员变量的依赖会从Module的@Provides方法集合中查找; 2.如果查找不到,则查找成员变量类型是否有@Inject构造方法,并注入构造方法且递归注入该类型的成员变量
强大的功能
如果说因为Dagger2能生成工厂代码就使用Dagger2,那么有些牵强。Dagger2除了能产生工厂代码,还有其他强大的功能,这些也是使用Dagger2的理由。 Scope,Multibinding,Subcomponent,Provider与Lazy,这些将在下一篇文章介绍。
相关文章推荐
- Android中framework层下添加aidl编译说程序包不存在
- Android常用开源工具(2)-Dagger2进阶
- Android 内容提供者简介
- Android targetSdkVersion 原理
- 安卓学习之--如何关闭所有的activity
- targetSdkVersion
- 容器与子类焦点优先——descendantFocusability
- Android屏幕录制AS自带功能
- Android其他应用修改系统时间
- Dialog的使用与总结
- Android通知栏微技巧,那些你所没关注过的小细节
- dex分包变形记
- ubuntu运行android studio出错unable to run mksdcard
- android 实现水波纹效果(一)
- Android入口类的修改
- Android 身份证、手机号、邮箱、银行卡号验证(正则表达式)
- 关于V7包下AlertDialog调用Dismiss的解决方法
- Android 百度地图开发 坐标误差问题
- android 开发问题总结
- Android Matrix详解