Android依赖注入框架Dagger2全面使用总结
一 依赖注入
依赖注入(DI)是控制反转(Inversion of Control,IoC)的一种重要方式,IoC是将依赖对象的创建和绑定通过被依赖对象类的外部来实现。依赖注入提供一种机制,将需要依赖对象的引用传递给被依赖对象。
它是面向对象的一种设计模式,其目的是为了降低耦合。举个栗子:
1 | public class Person { |
这里的Person对象的初始化为实例化Decorate,如果Decorate类实例化参数增加,则必须对Person对象初始化进行修改。如果还有其他类的也是按照这种方式创建Decorate类,那么就需要修改多处,这违背了单一职责原则和开闭原则。
因此需要引入依赖注入来解决这个问题。依赖注入,就是将依赖注入到宿主类(或者叫目标类)中,从而解决上面所述的问题。
二 依赖注入实现的三种方式
下面将会介绍三种简单的依赖注入方式,一般依赖注入的框架的基本原理基本一样,避免在被依赖对象中直接实例化依赖对象,而是通过其他方式引入。根据依赖注入的定义,下面例子中依赖对象为Decorate,被依赖对象为Person。
1. 构造注入
通过构造函数直接注入
1 | public class Person { |
2. 属性注入
通过属性来传递依赖即通过set方法
1 | public class Person { |
3. 通过Java注解
1 | public class Person { |
在Dagger2中用的就是最后一种注入方式,通过注解的方式,将依赖注入到宿主类中。
三 Dagger2注解使用
Dagger2是Dagger的升级版,是一个依赖注入框架,现在由Google接手维护。在github上Dagger2是这样定义的,Dagger2是一个Android和java快速依赖注入框架。
Dragger2是在编译时注解的方式实现依赖注入,是Dagger的升级版,取消了反射的使用。通过@Component的接口替代ObjectGraph/Injector,从而使代码更精简。
早期的一些注入框架是通过反射机制在运行时完成注入的工作,而反射对性能影响很大,所以现在基本上是采用编译时通过工具生成相应的类文件实现的。
1.Dagger2的依赖引入
完成Dagger2的依赖方式有两种,分别为annotationProcessor和android-apt。android-apt是开发者自己开发的apt框架,随着谷歌Android Gradle2.2插件的发布,插件提供了annotationProcessor来替换android-apt,
自此基本上都使用annotationProcessor。
项目中通过annotationProcessor的方式进行依赖,在app的build.gradle添加:
1 | implementation 'com.google.dagger:dagger:2.10' |
2.Inject方式注入(Inject+Component)
- 定义一个Person类,在构造函数前加上@Inject,表明支持依赖注入。
1
2
3
4
5
6
7
8public class Person {
@Inject
public Person(){
}
public String getName(){
return "xiaoming";
}
} - 定义接口MainActivityComponent,在接口前添加@Component,定义相应的抽象方法,方法的参数为需要注入对象的真实所在的类,方法名一般为Inject。
Component可以理解为Person(依赖对象)和MainActivity(被依赖对象)之间的桥梁。1
2
3
4@Component
public interface MainActivityComponent {
void inject(MainActivity activity);
} - 对项目进行Rebuild,Dagger2框架会根据MainActivityComponent自动生成DaggerMainActivityComponent(命名规则Dagger+Component名称)。
在MainActivity声明Person对象,并用@Inject的注解,然后通过DaggerMainActivityComponent注入Person对象。执行结果:1
2
3
4
5
6
7
8
9
10
11
12
13public class DaggerSimpleActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Inject
Person person;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
DaggerMainActivityComponent.builder().build().inject(this);
Log.d(TAG,person.getName());
}
}可以看出MainActivity成功注入了Person类,下面分析是如何注入Person类的:1
MainActivity: xiaoming
打开DaggerMainActivityComponent,可以看出其对应的路径\app\build\generated\source\apt\debug,在其目录下同时也有其他两个类Person_Factory,MainActivity_MembersInjector。
DaggerMainActivityComponent.java:在MainActivity中DaggerMainActivityComponent.builder().build()将会通过构建者实例化DaggerMainActivityComponent,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
34
35
36public final class DaggerMainActivityComponent implements MainActivityComponent {
private DaggerMainActivityComponent() {
}
public static Builder builder() {
return new Builder();
}
//1.调用create实际上是调用Builder().build()方法
public static MainActivityComponent create() {
return new Builder().build();
}
//3.重写MainActivityComponent的inject方法
@Override
public void inject(MainActivity activity) {
injectMainActivity(activity);
}
//4. 调用 MainActivity_MembersInjector的injectMPerson方法将MainActivity的引用和new 创建的Person对象传入
private MainActivity injectMainActivity(MainActivity instance) {
MainActivity_MembersInjector.injectMPerson(instance, new Person());
return instance;
}
public static final class Builder {
private Builder() {
}
//2.build()会通过new关键字创建DaggerMainActivityComponent对象
public MainActivityComponent build() {
return new DaggerMainActivityComponent();
}
}
}
DaggerMainActivityComponent实现了MainActivityComponent接口.重写MainActivityComponent的inject方法.因此我们再MainActivity中调用inject方法时会走
4中的步骤MainActivity_MembersInjector的injectMPerson方法将MainActivity的引用和new 创建的Person对象传入.
MainActivity_MembersInjector.java
1 |
|
MainActivity_MembersInjector类是实例化Person类的关键,也是注入具体的实现方式。aggerMainActivityComponent的injectMainActivity方法中将MainActivity对象和Person对象传入,
然后对MainActivity的mPerson对象赋值了刚传入的Person对象.因此在MainActivity就完成了Person的依赖注入.MainActivity持有了Person对象.
3.带Module的Inject方式(Inject+Component+Module)
在上面的Person中是自己定义的类,如果是某个库中的类,我们不能够改它的源码因此是不能够去该类中添加@Inject注解了,那么注入这种类该如何注入呢,这个时候就需要Module。
Module可以理解为对象的实例化,向Component的提供依赖对象。下面以Person类为对象说明:
1 .创建一个MainModule,并用@Module注解,在类中提供Person对象的方法并用@Provider注解(取消之前Person类中的@Inject注解)
1 | @Module |
- 修改MainActivityComponent,为其添加Module应用,来说明其可能需要用到MainModule中提供的对象。
1
2
3
4@Component(modules = MainModule.class)
public interface MainActivityComponent {
void inject(MainActivity activity);
} - MainActivity保持不变
执行结果:
1 | MainActivity: xiaoming |
可以看出和上面的例子结果一样,主要不同的是增加了MainModule。我们接下来再来分析一下生成的源码.
DaggerMainActivityComponent.java:
1 | public final class DaggerMainActivityComponent implements MainActivityComponent { |
我们看到调用MainActivity_MembersInjector.injectMPerson的时候的Person对象是通过MainModule_ProvidePersonFactory获取到的.
MainModule_ProvidePersonFactory.java:
1 | public final class MainModule_ProvidePersonFactory implements Factory<Person> { |
5.Module带参数
一般情况下大多数的类都需要传入参数的,下面看下带参数的类的实例化是如何的。
- 修改Person类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class Person {
private int age;
public Person(int age) {
this.age = age;
}
public String getName() {
return "mike";
}
public int getAge() {
return age;
}
} - 将Person类的age参数传入3 .在MainActivity中设置MainModule对象的参数
1
2
3
4
5
6
7
8
9
10
11@Module
public class MainModule {
int age;
public MainModule(int age){
this.age = age;
}
@Provides
public Person providePerson(){
return new Person(age);
}
}执行结果:1
DaggerMainActivityComponent.builder().mainModule(new MainModule(19)).build().inject(this);
需要注意的是在无参数的时候,DaggerMainActivityComponent会提供create方法,但是有参数时,只有build方法,因为需要传入MainModule对象1
2MainActivity: 19
。一般建议通过provide方法提供参数,主要是解耦和增加代码的可读性。Module中不能出现参数和返回参数一致的情况,否则会导致死循环。如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21@Module
public class MainModule {
int age;
public MainModule(int age){
this.age = age;
}
@Provides
public int provideAge(){
return age;
}
@Provides
public Person providePerson(){
return new Person(age);
}
@Provides
public Decorate providerDecorate(){
return new Decorate();
}
}1
2
3
4
5@Provides
public int provideAge(int age){
return age;
}
四.@Inject 和 @Provides 的优先级
Dagger2 依赖查找的顺序是先查找 Module 内所有的 @Provides 提供的依赖,如果查找不到再去查找 @Inject 提供的依赖。
- 步骤1:查找Module中是否存在创建该类的方法。
- 步骤2:若存在创建类方法,查看该方法是否存在参数
- 步骤2.1:若存在参数,则按从步骤1开始依次初始化每个参数
- 步骤2.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
- 步骤3:若不存在创建类方法,则查找Inject注解的构造函数,看构造函数是否存在参数
- 步骤3.1:若存在参数,则从步骤1开始依次初始化每个参数
- 步骤3.2:若不存在参数,则直接初始化该类实例,一次依赖注入到此结束
五.Scope作用域
1.Scope作用域
scope 就是作用域的意思,在不使用@Scope的情况下,每次注入的对象都会是一个新的不同的对象,而@Scope能限制被注入的对象.
在MainActivity中注入两个Person对象
1 | @Inject |
运行程序可以看到可以看到person1和person2的hash值并不相同,因此说明它们是两个不同的对象.在 dagger2 中,@scope 的一个默认实现就是 @Singleton.
2.Singleton注解
如果标注了Scope注解,那么注入器可能就会保持这个实例,下次注入需要这个依赖时就可以重用了.@scope 的一个默认实现就是 @Singleton.
@Singleton,这是一个标注了@Scope的注解
1 | /** |
从Scope文档中的示例可以看出,这个Singleton的写法并没有什么特殊的,仅仅是在文档注释写了句标记单例的类型.实际上,Scope就是作为一种类型的标记而已,而这种标记的目的是为了更好地区分作用域.
@Scope所描述的注解用于两个地方:
- Component类
- Module中用于创建实例的provideXXX方法
使用Singleton注解来看看效果.
- 在PersonModule中加入
1
2
3
4
5
6
7
8@Module
public class PersonModule {
@Provides
@Singleton //1.添加@Singleton标明该方法产生只产生一个实例
public Person providePerson(){
return new Person();
}
} - 在Component中加入
1 | @Singleton //2.添加@Singleton标明该Component中有Module使用了@Singleton |
添加完Singleton注解以后.再次运行程序.我们发现在MainActivity中person1和person2的hash值相同.说明他们在MainActivity中保持了单例.
我们通过分析Dagger自动生成的代码来分析如何实现单例的:
1 | public final class DaggerMainActivityComponent implements MainActivityComponent { |
在创建DaggerMainActivityComponent时会调用initialize生成providePersonProvider对象.
MainModule_ProvidePersonFactory.java:
1 | public final class MainModule_ProvidePersonFactory implements Factory<Person> { |
在DaggerMainActivityComponent的inject方法中providePersonProvider是单例的所以providePersonProvider.get()获取到的对象也是相同的.就算换个Scope,生成的也是一样的代码.
这个单例只针对同一个Component实例的情况下,毕竟Component本身也是能重复创建的,Dagger通过Singleton创建出来的单例并不保持在静态域上,而是保留在Component实例中。
那在别的Activity中是否也会是使用MainActivity中的单例对象呢?
我们再添加一个界面SecondActivity.
在SecondActivity中注入两个Person对象
1 | @Inject |
重新运行程序.我们发现在SecondActivity中的person3和person4对象是相同的,但是并不和MainActivity中person1和person2的hash值相同.
这里也印证了我们上面的结论:用 @Singleton 确定的单例作用域应该也是在 Component 的范围内。这个范围在单个的 Component 中。
MainActivity 和 SecondActivity 运用了不同的 Component 即使他们都添加了@Singleton 注解 MainActivity 和 SecondActivity 中的生成的对象 也不是同一个。
如果在应用的Application中创建Component,在Activity中进行注入,那么被注入的对象在整个应用程序中就是单例的。
**在 Dagger2 中 Scope 机制可以保证在 Scope 标记的 Component 作用域内 ,类会保持单例 **
并不是只有 @Singleton 注解标记的相关类生产的实例是单例的,是所有的 Scope(自定义 Scope) 标记的相关类生产的实例 都是单例 的,只不过这个单例是有条件的 – 在 Scope 注解标记 Component 的作用域内生产的实例是单例的 。
Scope 机制下的单例其实和 @Singleton 的字面意义 没有一点点关系,可以把 @Singleton 换成任意单词,什么 @Dog、@Cat、@XXx 都可以,你只要保证这个注解标记的 Component 在 App 进程中为单例的,并且得到正确的实现(被正确的标记到 类构造器 或 Module 中的 @Provides 标记的方法),那么它对应生成的类实例就是 单例 的。
@Singleton 之所以被默认实现,只是因为这可以让人根据它的字面意思,知道被他标记的相关生成的类实例为单例,这符合了 Java 的命名规范。
例如我们要创建整个应用程序中的单例对象:
MyApplication.Java:
1 | public class MyApplication extends Application { |
FirstActivity.java:
1 | public class FirstActivity extends AppCompatActivity { |
SecondActivity.java
1 | public class SecondActivity extends AppCompatActivity { |
按照如上修改我们就可以得到全局的单例对象.
3.自定义@Scope
对于Android,我们通常会定义一个针对整个APP全生命周期的@PerApp的Scope注解,通过仿照@Singleton
1 | @Scope |
你可能会发现,这个自定义的@Scope 和@Singleton代码完全一样,那@PerApp是不是也能具有实现单例模式的功能?答案是肯定的.
那你可能会有疑问,既然功能都是一样的,干嘛还自定义@Scope 结论就是不同的@Scope ,定义单例对象的生命周期,也就是使用范围。
在写代码时,程序员更加清楚什么时候创建Component,什么时候结束。
自定义@Scope总结起来有以下两点好处:
更好的管理ApplicationComponent和Module之间的关系,Component和Component之间的依赖和继承关系。如果关系不匹配,在编译期间会报错
代码可读性,让程序猿更好的了解Module中创建的类实例的使用范围。
@Singleton”并没有真正为你产生单列”,该注解只是保证只是在同一个Component对象中是单列而已,不同的Component进行注入,就会导致不是单例了,真正要实现全局单列,还是得靠自己控制,保证我们所有的地方都是使用同一个Component注入的对象.
总结的结论:
- 必须使用同一个Component对象来生成,也就是DaggerActivityComponent.builder().build()只能执行一次。
- Component和它的Module要使用相同的Scope。同一个Module中需要使用同样的Scope,否则相应的Component不知道使用什么Scope。
- 有依赖关系或者包含关系的Component不能使用同样的Scope。
注意,Scope只的单例是在Component的生命周期中相关依赖是单例的,也就是同一个Component对象注入的同类型的依赖是相同的。按上面例子,如果我们又创建了个AppComponent,它注入的InfoRepository对象与之前的肯定不是一个。
这里所谓的「生命周期」是与 Component 相关联的。与 Activity 等任何 Android 组件没有任何关系.
六.@Named和@Qualifier注解
如果一个类有多个构造方法,或者有两个相同依赖时,它们都继承同一个父类或者实现同一个接口,那么怎么区分呢?这就要用到@Named或者@Qualifier注解了。
1. @Named
1 | public class Car { |
1 | @Module |
1 | public class DaggerQualifierActivity extends AppCompatActivity() { |
2.@Qualifier
以上通过 @Named 实现的标识功能 @Qualifier 同样可以实现,但是需要我们自定义注解来完成,具体一个使用场景如下:
自定义注解
1 | @Qualifier |
注意,这里自定义注解需要使用 @Qualifier 进行标注。
Module 类中标记
1 | @Module |
目标类
1 | public class ThirdActivity extends AppCompatActivity { |
同样的,以上代码需要重点关注的是 Module 类 中使用 @UserThirdQualifier 注解方法和在 目标类 中使用 @UserThirdQualifier 注解标记类的实例变量,并且 Module 中的 @UserThirdQualifier(“c”) 和 目标类中的 @UserThirdQualifier(“c”)是一 一对应的。
七.Dagger2 中的懒加载和重加载
1. Dagger2 中的懒加载
智能懒加载,是Dagger2实现高性能的重要举措之一:在需要的时候才对成员变量进行初始化,可以大幅缩短应用初始化的时间。
使用方法:用Lazy
1 | @Inject |
用Lazy
1 | public class Car { |
2.Provider 强制重新加载
@Singleton 标注实现的单例可以让我们每次获取的都是同一个对象(暂不细究全局/局部单例),但有时,我们希望每次都创建一个新的实例,这种情况与 @Singleton 完全相反。
Dagger2 通过 Provider 就可以实现。它的使用方法和 Lazy 很类似。
使用方法:用Provider
1 | @Inject |
用Provider
1 | public class Car { |
但是,需要注意的是 Provider 所表达的重新加载是说每次重新执行 Module 相应的 @Provides 方法,如果这个方法本身每次返回同一个对象,那么每次调用 get() 的时候,对象也会是同一个。
八.Component间依赖的两种方式
Component 管理着依赖实例,根据依赖实例之间的关系就能确定 Component 的关系。在 Dagger 2 中 Component 的组织关系分为两种:dependence和subcomponent,有些像组合和继承的关系。
- 依赖关系:一个 Component 依赖其他 Compoent ,以获得其中公开的依赖实例,用 Component 中的dependencies声明。
- 继承关系:一个 Component 继承(扩展)其他的 Component, 以获得其他的Component中的依赖,SubComponent 就是继承关系的体现。
1.Dependence方式
Component 依赖 是通过 @Component 的注解中 dependencies 选项来标识的,意思是指 该 Component 依赖 dependencies 指定的 Component 。
举个例子,看以下代码:
创建RetrofitUtils网络请求工具
1 | public class RetrofitUtils { |
创建JsonUtils解析工具
1 | public class JsonUtils { |
创建NetWorkModule提供RetrofitUtils和JsonUtils对象
1 | @Module |
以MVP架构为例:在Presenter中我们需要用到RetrofitUtils来请求网络数据,还需要用到JsonUtils来解析数据.
1 | public class Presenter { |
创建提供Presenter的PresenterModule
1 | @Module |
创建NetWork依赖注入接口NetWorkComponent
1 | @Component(modules = NetWorkModule.class) |
创建PresenterComponent依赖注入对象:
1 | @Component(modules = PresenterModule.class, dependencies = NetWorkComponent.class) |
DaggerDependenciesActivity.java
1 | public class DaggerDependenciesActivity extends AppCompatActivity { |
2.SubComponent
@SubComponent 也是管理 Component 间的依赖,不同的是这种方式不需要 在被依赖的 Component 中显式的声明可以获取相应类实例的方法。
通过 @SubComponent 来管理的 Component 之间是一种 继承关系,子 Component 理所当然的可以使用父 Component 的可以提供的类实例
子Component需要使用@Subcomponent注解,同时需要提供一个Builder接口,供父Component来生成Component,Builder接口中需提供一个返回子Component的抽象接口方法
子组件可以使用父组件所有的Module实现进行依赖注入,同时在构建子组件时的代码实现方式也和构建组件依赖时不一样。
首先定义一个使用@Subcomponent注解的依赖注入组件接口:
1 | @Subcomponent(modules = {SubModule.class}) |
它依赖的 SubModule 为:
1 | @Module |
如果我们想让 DetailComponent 做为 MySubComponent 的父组件,则需要在 DetailComponent 中定义一个返回 MySubComponent 的方法,方法参数为其依赖的 Module 类型:
1 | @DetailActivityScope |
到这里我们的子组件就实现完成了,如何使用呢?子依赖注入注入组件是不能单独直接使用的,因为编译后并不会生成类似DaggerMySubComponent的辅助类,所以需要通过父组件来获取,
这也是我们需要在父组件中定义返回子组件方法的原因。具体的用法如下:
1 | public class SubActivity extends AppCompatActivity { |
我们并没有在 SubModule 中定义提供 Flowerd 对象的方法,但是同过这种“继承”,MySubComponent 就可以提供 Flower 对象了.
5.依赖关系 vs 继承关系
相同点:
- 两者都能复用其他 Component 的依赖
- 有依赖关系和继承关系的 Component 不能有相同的 Scope
区别:
- 依赖关系中被依赖的 Component 必须显式地提供公开依赖实例的接口,而 SubComponent 默认继承 parent Component 的依赖。
- 依赖关系会生成两个独立的 DaggerXXComponent 类,而 SubComponent 不会生成 独立的 DaggerXXComponent 类。
- 在 Android 开发中,Activity 是 App 运行中组件,Fragment 又是 Activity 一部分,这种组件化思想适合继承关系,所以在 Android 中一般使用 SubComponent。
4.SubComponent 的其他问题
抽象工厂方法定义继承关系
除了使用 Module 的subcomponents属性定义继承关系,还可以在 parent Component 中声明返回 SubComponent 的抽象工厂方法来定义:
1 | @ManScope |
这种定义方式不能很明显地表明继承关系,一般推荐使用 Module 的subcomponents属性定义。
Component 之间共用相同依赖,可以有两种组织关系:依赖关系与继承关系。在 Android 开发中,一般使用继承关系,以 AppComponent 作为 root Component,
AppComponent 一般还会使用 @Singleton 作用域,而 ActivityComponent 为 SubComponent。
参考资料:
史上最适合新手的Dagger2教程(一)基本注入
Android |《看完不忘系列》之dagger
深入浅出依赖注入框架Dagger2
Android – 带你从源码角度领悟Dagger2入门到放弃(一)
Android – 带你从源码角度领悟Dagger2入门到放弃(二)
Android – 带你从源码角度领悟Dagger2入门到放弃(三)
Android基础知识:Dagger2入门
Dagger2从入门到放弃再到恍然大悟
Dagger 2 系列(一) – 前奏篇:依赖注入的基本介绍
Android 神兵利器Dagger2使用详解(一)基础使用
Dagger 2 使用及原理
听说你还不会用Dagger2?Dagger2 For Android最佳实践教程
轻松学,听说你还没有搞懂 Dagger2
Dagger2的组件依赖及使用详解
Dagger2 in Android(二)进阶
Dagger 2 完全解析(三),Component 的组织关系与 SubComponent
Dagger2 Android应用:@Scope和@Subcomponent
Dagger2利器系列二:懒/重加载+Component 的组织关系
Android | 从 Dagger2 到 Hilt 玩转依赖注入(一)
Dagger2学习笔记4(@Singleton 与@ Scope 实现全局单例与作用域单例)
详解 Dagger2 的 @Scope 和 @Subcomponent
Dagger2 in Android(三)Scope与生命周期
Dagger 2 系列(六) – 进阶篇:Component 依赖、@SubComponent 与多 Component 下的 Scope 使用限制