Dagger2,依赖注入框架,一个刚接触时感觉麻烦,用久了就会“嘴上说不要,身体却很诚实”的开发润滑剂(◐‿◑)。(本文为拖更而生)
一、Dagger2 介绍
1、为什么使用dagger2
谁用谁知道Σ( ̄。 ̄ノ)ノ,如丝般顺滑,奶不死的Dagger2 ,主要优势体现在:
- 解决项目中多实例依赖创建问题,如:
new A(new B(new C()))
。 - 更好的对象生命周期依赖和管理,如通过
@Scope
规范实例的生命周期。 - 规范代码,提高解耦能力,增强代码的拓展能力,如类的依赖、创建、复用、拓展都通过
@Component
、@Module
、@Inject
的规范实现。 - 最重要的是:代码看起来比较装逼!
2、简单原理介绍
Dagger2 可以理解为一套开发规范,遵守这套规范编写的代码,通过Dagger2 的运行时注解,在编译时自动生成模版代码,已达到注入和复用的目的。
自动生成的代码,一般存在路径build\generated\source\apt
下。那么了解完这套模版规范,Dagger2 将不再神秘,“深入浅出”“指日可待”(˶‾᷄ ⁻̫ ‾᷅˵)啊。
关于运行时注解不了解的可查阅:《Android注解快速入门和实用解析》
二、Dagger2 剖析
让我们循环渐进的开始吧。
首先看下图,Dagger2中主要的三个注解是 :@Inject
、@Component
、@Module
。
它们是最基础,也是使用最多注解,我们将从它们身上开始“摸索”Σ( ̄。 ̄ノ)ノ。
- 由
@Inject
指向需要构成注入的类和环境。 - 由
@Module
提供生成对象所需的参数。(一般是在 @Inject 注解的对象,其构造函数无法添加 @Inject时使用。) - 由
@Component
作为中间桥梁连接注入对象和工厂。
1、Inject
一切的“插入”从它开始!@Inject
指定需要注入的类和环境,如下方图2,TasksActivity 中 mTasksPresenter 是被Inject注解的对象,同时TasksPresenter 的构造函数也被Inject注解。
内部成员被 @Inject
注解时,如 mTasksPresenter,Dagger2 就会找 TasksPresenter 中被 @Inject
注解的构造函数,如果找到了,便对其构建注入。
那么问题来了!这时候如果 TasksPresenter 的构造方法为空构造方法,便万事大吉。可惜现实总是那么骚!图中 TasksPresenter 的构造函数有参,需要 tasksRepository 和 tasksView 两个参数,那便是后面的@Module
的用武之地,它将提供“后门注入”支持。
既然知道 @Module
的作用,我们先继续往下走,构造方法被注解的类,会生成一个继承Factory
的类,如 TasksPresenter 生成 TasksPresenter_Factory , 如下图3,这是由Dagger2的自动编译生成的,这个工厂用于提供实例化类,其中的get()
方法便是在注入时被调用。
当然,你完全可以不关心这些。但是了解这些,有时候可以更愉悦的装逼(-_^),所以我选择憋着往下看。
继续深入,如下图四,在被 @Inject
注解的内部参数或方法,会生成对应的继承MembersInjector的类, 如 TasksActivity_MembersInjector。在它调用.injectMembers(this);
时,实际上就是调用了上面 TasksPresenter_Factory 的 get()
方法注入进去的。
看到没,这不就联系起来了么,城市套路深啊,而且还很滑!(˶‾᷄ ⁻̫ ‾᷅˵)
2、Module
上面说过,因为构造方法包含参数,而所包含的参数,其构造方法无法被 @Inject
注解,这时候就需要 @Module
提供“后门”。
举个栗子:类A的构造方法需要类B,但类B的构造方法无法添加 @Inject
注解。这时候通过 @Module
,在内部使用 @Provides
注解的以provide开头的方法,这些方法就是所提供注入所需的依赖,如图5。
同样是自动生成模版代码,@Module
注解的类中,每一个 @Provides
修饰的方法,都会生成一个工厂类,如图六下生成:TasksPresenterModule_ProvideTasksContractViewFactory ,通过get()
方法提供注入时所需的参数。
这时候回到图三,可以看到这些get()
方法,便是在 TasksPresenter_Factory 的 get()
被调用时使用 看到没,这不又联系起来了么,套路套路啊!(˶‾᷄ ⁻̫ ‾᷅˵)。
3、Component
做了那么多,总得用起来吧。Component 就是将 @Inject
和 @Module
联系起来的桥梁,从 @Module
中获取依赖,并将依赖注入给 @Inject
注解的参数。
如图七,@Component
接口指定了使用的 Module 和依赖的 Component。是的,Component依赖,这样更有利于代码的复用。
@Component
的接口的内部方法简单可分为:
- 如果是void就必须有注入环境类。
- 如果参数为空,就必须有返回类。返回类必须有 @Inject 提供构造方法类,或者引用的
@Module
有 @Provide提供的。
同样的套路,TaskComponent会生成 DaggerTaskComponent 类,这个类便是我们需要使用的对象,如图八图九,可以看到上面生成的对象,都是在其中被使用,最后通过我们定义好的 inject()
方法,如下方代码实现注入效果。
//注入咯注入咯
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);
怎么样,这样的套路清晰了么?(˶‾᷄ ⁻̫ ‾᷅˵),简单总结起来其实如下方这样,需要我们动手的,其实并不多:
- 使用
@Inject
注解需要注入的参数和方法。 - 使用
@Module
提供构造方法中无法注解的参数。 - 使用
@Component
连起起来,并且调用注入。
4、关联总结
来简单总结下生成模版的关系,不感兴趣的可以略过···吧(#゚Д゚):
注解构造方法,生成的
TasksPresenter_Factory
,内部通过create
方法 创建Factory,通过get
方法 提供TasksPresenter
对象。TasksModule_ProvideTasksViewFactory
,由Module中@Provide注解的方法生成,内部通过create
方法创建,通过get
方法提供TasksPresenter_Factory
在创建TasksPresenter
需要的参数。TasksComponent生成
DaggerTasksComponent
初始化时 ,通过TasksModule_ProvideTasksViewFactory
作为参数创建了TasksPresenter_Factory
, 在TasksActivity_MembersInjector
中,通过injectMembers
方法,利用TasksPresenter_Factory
的get
实现注入。DaggerTasksComponent
中外部提供Builder设置方法,和依赖的Component与module数量有关。如果依赖的未被使用,会有@deprecated提示。
三、稍微再“深入”
1、Scope
让我们再稍微深入一点去了解Dagger2吧,生命周期是值得关心的。比如内置的 @Singleton
注解,字面上就是单例的意思,但是实际上直接使用 @Singleton
并不会有单例的效果。
@Singleton
实际上是一种规范注解,它属于 @Scope
注解的一种,正如字面上所示,它代表着单例的生命周期,事实上 @Scope
是一种作用域注解,通过Component、Module一其配种使用,才能达到作用域的效果。
如上图十,下图11,12中, TasksRepository、TasksRepositoryComponent 、TasksRepositoryModule 都被 @Singleton
注解了,但是这并不代表着他们就是单例,只是通过注解限定作用域,并且在字面上表明了它们单例。
真正的单例效果, 是下图13中在 ToDoApplication
中 DaggerTasksRepositoryComponent
的创建。粗俗点说,就是因为Application中的 mRepositoryComponent
对象只有一个,由于作用域 @Scope
的作用,那么getTasksRepository()
获取的TasksRepository
达到了单例的效果。
这里理解下来,如下方代码,我们创建一个@ActivityScoped
的Scope注解,限定注入对象 TasksPresenter
的生命周期是和Activity相关的,然后我们在 TasksActivity
中,调用 DaggerTasksComponent
注入。因为 DaggerTasksComponent
在 TasksActivity
中唯一,所以 TasksPresenter
在Activity中也唯一。
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ActivityScoped {
}
········
@ ActivityScoped
public class TasksRepository implements TasksDataSource{
···
}
·······
public class TasksActivity extends AppCompatActivity {
@Inject TasksPresenter mTasksPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
// Create the presenter
DaggerTasksComponent.builder()
.tasksRepositoryComponent(((ToDoApplication) getApplication()).getTasksRepositoryComponent())
.tasksPresenterModule(new TasksPresenterModule(tasksFragment)).build()
.inject(this);
}
上面这段看起来是不是很多余?有点废话?
但是确也是最好理解的,如果换个方法,比如把 @Inject TasksPresenter mTasksPresenter;
写在Fragment,DaggerTasksComponent
在Activity构建,然后在多个Fragment的onCreateView调用inject(this)
呢?
很明显注入的mTasksPresenter对象就是Activity生命周期,而和Fragment生命周期无关,并且由于Dagger2内部的限定,Scope可以更好的规范Module和Component的生命周期。
还有更多的场景和规范要求,这里就不一一展开了,有兴趣深入了解的可谷歌之。(肯定不是因为懒得展开!!)
2、Qualifier
简单的理解,它就是一个Tag标记的作用。
如下方代码,我们定义了Remote和Local两种@Qualifier
注解,在Module中,提供的 TasksDataSource
通过Remote和Local两种Tag区分,这样在注入的时候,也可以通过Tag指定注入参数。
很好理解吧!这里就不展开了,一展开了又是一堆····╮(╯▽╰)╭,是时候收尾了,你(wo)也需要休息休息了。
//类型一
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Remote {
}
······
//类型二
@Qualifier
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Local {
}
······
//提供时区分类型
@Module
abstract class TasksRepositoryModule {
@Singleton
@Local
abstract TasksDataSource provideTasksLocalDataSource(TasksLocalDataSource dataSource);
@Singleton
@Remote
abstract TasksDataSource provideTasksRemoteDataSource(FakeTasksRemoteDataSource dataSource);
}
······
//根据类型注入
@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
@Local TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = tasksRemoteDataSource;
mTasksLocalDataSource = tasksLocalDataSource;
}
最后推荐下相关的demo
android-architecture : todo-mvp-dagger2 分支中有详细的demo演示。
LazyRecyclerAdapter :个人在这个开源项目包含有Dagger2在java和kotlin中的使用demo。