系列文章android
前面已经讲了 Dagger 的基础注解,而且最后咱们也搭建了一个最简单的 Dagger 注入。ide
这一篇咱们继续学习 Dagger 更多的注解,以及如何模块化地管理。这些将帮助咱们妥善组织不一样的组件、明确各自的生命周期。模块化
依赖注入迷失函数
以前说过 @Module
和 @Provides
配合能够包装没有 @Inject
标注的构造函数。但若是包装了一个已经有了 @Inject
的类会怎么样?其实这俩有优先级的。Dagger 会优先从 Module 中查找实例化方法,若是找不到再去找被 Inject
的标记的构造函数。 这也很是好理解,通常人确定会选择优先去超市买东西,而不是直接去拜访工厂。post
通常来讲,为了便于管理,咱们会统一用 Module 封装一层,不管构造函数有没有被标注。这能够帮助咱们更好地管理依赖结构与生命周期,这些后面会讲到。学习
但若是 Module 里有两个返回值类型同样的 Provides 呢?考虑下面的代码:ui
class Stove() {
var name: String? = null
constructor(name: String) : this() {
this.name = name
}
}
@Module
class MainModule() {
@Provides
provideStove():Stove {
return Stove()
}
@Provides
provideStove():Stove { // 如今有两个Provides都返回炉子
return Stove("Boom")
}
}
复制代码
如今家乐福里有两个炉子,Dagger 不知道该买哪个,咱们给这种状况起个名字叫「依赖注入迷失」。依赖注入迷失会在编译期报错,很容易发现。this
解决它spa
为了解决这个问题,必须引入一个新的注解 @Named
,也就是厨师会指明到底须要哪一个型号的炉子,这样就不会买错了。同时,记得给超时货架上的炉子也代表型号,否则怎么买对吧 -。-code
改造后的 Module 与 Chef 以下:
@Module
class MainModule() {
@Provides
@Named("noname")
provideStove():Stove {
return Stove()
}
@Provides
@Named("boom")
provideStove():Stove { // 如今有两个Provides都返回炉子
return Stove("Boom")
}
}
复制代码
class Chef() {
@Inject
@Named("noname")
val stove1: Stove
@Inject
@Named("boom")
val stove2: Stove
}
复制代码
咱们的厨师比较贪婪,他两个型号全都要。但与一开始胡乱买不一样,如今他清楚地指明了我须要两个型号,而且能分清这两个型号。因而就不会报错了。
Qualifier
与 Named
的做用如出一辙。只不过 Named 是用单纯的字符串区分,而 Qualifier 须要先自定义注解。如今咱们把刚才的例子改用 Qualifier 实现。
// 定义一个新的注解,名叫 StoveQualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class StoveQualifier
复制代码
@Module
class MainModule() {
@Provides
@StoveQualifier("noname")
provideStove():Stove {
return Stove()
}
@Provides
@StoveQualifier("boom")
provideStove():Stove { // 如今有两个Provides都返回炉子
return Stove("Boom")
}
}
复制代码
class Chef() {
@Inject
@StoveQualifier("noname")
val stove1: Stove
@Inject
@StoveQualifier("boom")
val stove2: Stove
}
复制代码
看到没,和 Named 用法如出一辙对吧。确定有人要问,既然那么麻烦问什么不直接用 Named 呢。
你能够把 Qualifier 看作是自定义命名空间。以前全部的型号都标注在 Named 空间下。也就是空调、炉子、电磁炉、冰箱等等,型号所有糅杂在一块儿,显然这不是个好办法。经过自定义 Qualifier,咱们可让每一个类有本身的型号命名空间,不要担忧冲突与混淆了。
一开始已经提到,为了便于管理咱们会统一用 Module 封装一层,而 Module 最终要被关联到 Component。所以问题的关键就成了该如何组织 Component。
既然标题叫 Dagger2 in Android,天然是要重点考虑 Android 上面的应用。一个思惟正常的程序猿都不会把全部注入都写进一个 Component,不然会变得很是庞大、难以维护。可是划分的粒度也不能够过小,若是为每一个类都建立一个 Component,也会变得很是复杂、难以维护。
让咱们回到一开始 Dagger 究竟是干什么用的?通过一轮学习相信你们都有本身的答案。我认为它主要做用是「建立并管理对象,将其注入到须要它们的类」。既然是管理对象,那就不得不考虑生命周期。所以基于生命周期的划分也许是个不错的点子。
一个 Android 应用有不少生命周期,大体有两类:
因此咱们彻底能够按照生命周期来对 Component 进行划分。
咱们知道 Component 本质就是一个接口(抽象类),所以它互相也能够有联系,关系分为两种:依赖关系与包含关系。
如今咱们有两个 Component,分别是 AppComponent 与 ActivityComponent,前者持有一个全局 Context 对象,咱们但愿后者依赖前者。那么能够这么作:
@Module
class AppModule(private val context: Context) {
@Provides
fun provideContext(): Context = context
}
@Component(modules = [AppModule::class])
interface AppComponent {
fun context(): Context // 注意这行
}
复制代码
@Module
class ActivityModule {
@Provides
fun provideSp(context: Context) =
context.getSharedPreferences("Cooker", Context.MODE_PRIVATE)
}
// 声明了依赖关系
@Component(dependencies = [AppComponent::class], modules = [ActivityModule::class])
interface ActivityComponent {
}
复制代码
分析一下这段代码:
ActivityModule 定义了一个 Provides 可以返回 SharedPreferences 的实例。可是建立这个实例须要 context,它是哪来的?因为它声明了依赖 AppComponent,而 AppComponent 拥有的 AppModule 中有能够提供 context 的 Provides,所以 ActivityModule 从 AppComponent 那里拿到了 context。
但这不是无条件的,依赖别人的前提是别人愿意被你依赖才行。所以 AppComponent 中必须显示地定义一个可以返回 Context 类型的函数,依赖它的 Component 才能拿到。若是不定义,即便有,也不会给别人的。
注意区分 Component 中的函数与 Module 中 Provides 的区别:前者做用是:① 用于注入 ② 用于给依赖的 Component 提供对象;后者做用仅仅是建立对象。
依赖就像朋友,对方愿意才能够分享。包含就像父母,分享是无条件的。
声明继承须要如下几步
@Subcomponent
注解。subcomponents
属性来指明拥有哪些子 Component。上面的例子用包含关系能够这样改写:
@SubComponent(modules = [ActivityModule::class]) // 子Component用@Subcomponent注解。
interface ActivityComponent {
// 声明一个Builder来告诉父Component如何建立本身
@Subcomponent.Builder
interface Builder {
fun build(): ActivityComponent
}
}
// 父Component对应的Module用subcomponents属性来指明拥有哪些子Component
@Module(subcomponents = [ActivityComponent::class])
class AppModule(private val context: Context) {
@Provides
fun provideContext(): Context = context
}
@Component(modules = [AppModule::class])
interface AppComponent {
//fun context(): Context // 不须要显示定义了
// 父Component声明一个抽象方法来获取子Component的Builder
fun activityComponent(): ActivityComponent.Builder
}
复制代码
声明包含关系后,父接口所能提供的全部对象子接口下的 Module 均可以直接使用,再也不须要显示声明了。
对于包含关系,子 Component 将再也不生成 DaggerXxxComponent 类,须要经过父 Component 的实例来建立子 Component。
相同点:
不一样点:
那么究竟选用哪一个,彷佛没有准确的规范,在更多的实践中体会吧。(通常在 Android 中,会让 Activity 包含于 AppComponent)
这一章主要学习了 Dagger 的模块化管理。一开始提到过,Dagger 还能够管理对象的生命周期,这是一个很是重要也是一个很是容易弄错的方面,咱们将在下一章单独讨论。
有了上一章的铺垫,本章类比不是特别多了,若是有概念忘记的(特别在讲模块化的时候)必定要回到上一章看看,否则下一章必定会更加痛苦。