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 還能夠管理對象的生命週期,這是一個很是重要也是一個很是容易弄錯的方面,咱們將在下一章單獨討論。

有了上一章的鋪墊,本章類比不是特別多了,若是有概念忘記的(特別在講模塊化的時候)必定要回到上一章看看,否則下一章必定會更加痛苦。

相關文章
相關標籤/搜索