Dagger2 in Android(二)进阶

系列文章android

前面已经讲了 Dagger 的基础注解,而且最后咱们也搭建了一个最简单的 Dagger 注入。ide

这一篇咱们继续学习 Dagger 更多的注解,以及如何模块化地管理。这些将帮助咱们妥善组织不一样的组件、明确各自的生命周期。模块化

@Named

依赖注入迷失函数

以前说过 @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

QualifierNamed 的做用如出一辙。只不过 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 应用有不少生命周期,大体有两类:

  • Application:这是最长的生命周期,从咱们应用启动开始,直到被完全销毁。
  • Activity/Fragment: 都表示一个页面。打开时开始,离开时销毁。

因此咱们彻底能够按照生命周期来对 Component 进行划分。

组织 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 提供对象;后者做用仅仅是建立对象。

包含关系(子组件)(组件继承)

依赖就像朋友,对方愿意才能够分享。包含就像父母,分享是无条件的。

声明继承须要如下几步

  1. 子 Component 用 @Subcomponent 注解。
  2. 子 Component 声明一个 Builder 来告诉父 Component 如何建立本身。
  3. 父 Component 对应的 Module 用 subcomponents 属性来指明拥有哪些子 Component。
  4. 父 Component 声明一个抽象方法来获取子 Component 的 Builder。

上面的例子用包含关系能够这样改写:

@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。

对比

相同点:

  • 均可以使用父接口所提供的对象。

不一样点:

  • 生成代码不一样。依赖关系每个 Component 都会生成一个 DaggerXxxComponent 类;而包含关系只会生成一个。
  • 对父接口对象访问限制不一样。依赖关系必须主动声明才能获取到;包含关系默认能获取到。

那么究竟选用哪一个,彷佛没有准确的规范,在更多的实践中体会吧。(通常在 Android 中,会让 Activity 包含于 AppComponent)

总结

这一章主要学习了 Dagger 的模块化管理。一开始提到过,Dagger 还能够管理对象的生命周期,这是一个很是重要也是一个很是容易弄错的方面,咱们将在下一章单独讨论。

有了上一章的铺垫,本章类比不是特别多了,若是有概念忘记的(特别在讲模块化的时候)必定要回到上一章看看,否则下一章必定会更加痛苦。

相关文章
相关标签/搜索