系列文章java
Dagger2 是一个由 Google (以前是 Square)维护的开源依赖注入框架。我曾两次试图学习 Dagger 最终被乱七八糟的名词弄得晕头转向,连个 demo 都没写出来就放弃了。因此本文也会重点解释 Dagger 的各个名词,只有熟悉了它们的做用,才能顺畅无阻地使用,也才能看懂别人的 demo。android
虽然标题叫 Dagger2 in Android,可是前几节都是 Dagger 通用的基础知识,与 Android 没有关系。git
本系列使用 Kotlin 语言,其与 Java 100% 兼容,仅仅是语法不一样而已,不会形成太大影响。Kotlin 最终依然会被编译成 jvm 字节码。github
例如 java 定义函数:
public String getName() {return "David"}
数组kotlin 版本:
fun getName(): String {return "David"}
,甚至能够更简洁:fun getName() = "David"
框架
那么首先咱们得先清楚什么是依赖注入。依赖注入是实现控制反转(IoC)的一个方案。 那什么叫控制反转?别急,我尽可能用最通俗的方式解释所涉及的全部概念。jvm
一般在面向对象语言中,一个类每每依赖其余类,一个对象依赖其余对象。那么最直接的方案就是 new 一个出来。这很好理解,我依赖计算机,因此我就本身造一个出来。那么造出的计算机固然是我本身控制的,这就是“控制正转”。那么反转就是,我须要计算机,可是我不本身造,而是厂家造好以后交给我。这就是控制反转。由于我再也不控制计算机的生产,此时厂家就叫作 IoC 容器
,它负责生产维持对象,而我只负责拿来用。因此 IoC 不是技术,而是思想,利用这种思想能够下降对象间的耦合,提升代码重用率。 假若计算机的配置发生了变动,在以前每一个人都要本身更新图纸,可是如今只须要厂家更新就行了,我总能够拿到最新款的计算机。ide
为了实现控制反转,有不少方案,常见的有 依赖注入
、依赖查找
、服务定位器
。这里咱们讲依赖注入。函数
其实依赖注入不是什么新东西,咱们每天都在用。依赖注入有三种常见的手段:构造函数
、Setter
、注解
。举个厨师的例子:厨师依赖炉子。那么代码能够这么写:post
// 经过构造函数注入炉子
class Chef(val stove: Stove) {
}
// 经过 Setter 注入
class Chef() {
var stove: Stove
// 其实 Kotlin 默认实现了 setter,为了更加清晰我手动写了一个。
fun setStove(stove: Stove) {
this.stove = stove
}
}
复制代码
看到没,依赖注入就在咱们身边,咱们一直都在用。厨师须要炉子,但他不本身造炉子,而是别人造好后传给他用,也就是「注入」。
既然彻底能够经过构造函数注入,那为何要 dagger 呢?固然是由于 dagger 更方便哈哈。
继续厨师的例子,咱们知道炉子必须依赖燃料。那么为了获得一个厨师,咱们必须前后获得一个燃料、炉子,看下面的代码:
class Stove(val fuel: Fuel) {}
class Fuel(){}
// 建立一个厨师
val fuel = Fuel()
val stove = Stove(fuel)
val chef = Chef(stove)
复制代码
看到没,为了获得一个厨师,咱们须要建立一堆东西。若是你以为还不够,其实厨师还依赖菜刀,燃料还依赖自然气。总有一天你会不耐烦。
事实上,有时候2个厨师能够共用1个炉子,而3个炉子能够共用1瓶燃料。这些问题 Dagger 统统能够优雅地解决。怎么样 是否是有点感受了<( ̄︶ ̄)↗
Dagger 能够经过多种方式引入,详见 README。做为 Android 咱们可使用 Gradle 声明依赖(kotlin):
dependencies {
def dagger_version = "2.23.1"
implementation "com.google.dagger:dagger:$dagger_version"
kapt "com.google.dagger:dagger-compiler:$dagger_version"
// 若是须要使用 Android 的特有 Dagger 功能,还要引入下面的库
implementation "com.google.dagger:dagger-android:$dagger_version"
implementation "com.google.dagger:dagger-android-support:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
}
复制代码
@Inject
是咱们接触到的第一个 Dagger 注解。它有两个做用:① 标注哪些东西须要注入。② 标注这些东西怎么建立。
假设 Dagger 是后勤管理部,那么做为厨师,你必须告诉 Dagger 须要哪些东西,而后还要告诉他这些怎么造,这样 Dagger 才能注入给你。因此厨师和炉子(简单起见忽略燃料)咱们能够这样改写:
// 告诉 Dagger 炉子能够经过一个无参的构造函数造出来
class Stove @Inject constructor() {}
class Chef() {
@Inject // 告诉 Dagger 我须要一个炉子
val stove: Stove
}
复制代码
就是这么简单。如今 Dagger 已经知道咱们须要什么、这个东西怎么建立了。这里我把厨师称为目标类,也就是「炉子要往哪注入」,这就是目标。
以前咱们已经让厨师和炉子创建了无形的联系。可是要真正得到炉子,可是还得让这个联系直接一点。毕竟以前那都是泛泛而谈,针对具体的一个厨师(实例)咱们得具体操做。这个桥梁就是 @Component。Component 将具体链接厨师所依赖的炉子,和炉子的构造函数。
Component 是一个注解类,且是一个接口或抽象类。所以必须对一个接口标记 @Component 才能得到一个 Component。它将得到一个目标类实例的引用(也就是一个活生生的厨师),而后查找这个类所依赖的类(询问厨师你须要啥)。获得答案以后它会去查找是否有已知的构造函数(以前 @Inject 标注过的),而后实例化并注入到目标类(造个炉子交给厨师)。
根据上面的解释,咱们能够轻松写出一个 Component:
@Component
interface MainComponent {
// 定义一个函数,以便拿到目标类实例的引用
fun inject(chef: Chef)
}
复制代码
其实到目前为止咱们已经实现一个完整的依赖注入了:告诉 Dagger 厨师须要炉子、告诉炉子应该怎么造,而且创建了一个直接的桥梁。
恭喜!(。⌒∇⌒)
这又是什么鬼呢?咱们来想一想,若是厨师也不会造炉子咋办。 反应到项目中就是,咱们引入了第三方库,这个库没有在构造函数上标记 @Inject,总不能修改源码本身加上吧。这时候就要 Module 出场拉。Module 至关于给第三方库套一层封装,给他从外面包裹一个可以通知 Dagger 的建立方法。好比厨师不会造炉子,可是他知道去哪买,那么这也是OK的。
按照这个思路咱们修改一下厨师炉子的代码:
// 如今去掉炉子的 @Inject 表明厨师不会造炉子
// 假设炉子是第三方库,咱们无权添加 @Inject
class Stove(){}
@Module
class MainModule() {
// 虽然我不会造,但我可去 [MainModule] 这个商店买
provideStove():Stove {
return Stove()
}
}
复制代码
Module 就像工厂模式,里面提供了各类建立实例的方法。
如今咱们必须让 @Component 知道 Module(商店)的存在。很是简单:
// 直接传入 Mudoule 类数组就好啦
@Component(modules = [MainModule::class])
interface MainComponent {
fun inject(chef: Chef)
}
复制代码
而后有一个新问题,以前利用 @Inject 标注了类的构造方法,如今 @Module 只标注了一个商店,并无指明某个具体的类(炉子)到底怎么得到。就好像如今后勤管理处知道去家乐福能够买个炉子,可是不知道具体在哪一个区域。
[注] 一个 Module 能够被多个 Component 引用。由于有可能多家超市都卖炉子。
Provides 将最终解决第三方库的问题。咱们把 Module 中全部建立实例的函数都用 Provides 标注。那么这些函数就是会 Dagger 所识别而后选择一个返回值类型匹配的进行调用。
如今对于厨师依赖的第三方炉子,Dagger 将这样处理:首先桥梁告诉他有个商店(MainModule)可能有炉子,因而到商店筛选全部货架(Provides),找到匹配的商品带回来便可。
@Module
class MainModule() {
@Provides // 加上一个注解代表这个函数能够提供一个炉子(或其余物品)
provideStove():Stove {
return Stove()
}
}
复制代码
到目前为止,咱们已经了解了 Dagger 的基础内容。咱们已经学会了如何通知 Dagger 所需的依赖、依赖类如何建立,以及对于第三方提供的类该如何包装并使 Dagger 识别。
但愿厨师的类比能帮助你更好地理解相关概念,下面咱们将继续学习其余注解。