Dagger2 in Android(三)Scope與生命週期

系列文章android

前言

以前咱們已經學習了 Dagger 的基礎知識、模塊化管理,本章將是 Dagger 基礎使用的最後一章。git

Scope 被誤稱 Dagger 的黑科技,但實際上它很是簡單,但錯誤理地解它的人卻前仆後繼。但願小夥伴們認真閱讀這一章,第一次學習時必定要正確理解,否則後邊再糾正會感受世界觀都被顛覆了。github

@Scope

終於來了。Scope 正如字面意思,它能夠管理所建立對象的「生命週期」。Scope 的定義方式相似 Qualifier,都須要利用這個註解來定義新的註解,而不是直接使用。數據庫

重點!!!這裏所謂的「生命週期」是與 Component 相關聯的。與 Activity 等任何 Android 組件沒有任何關係!緩存

下面是典型的錯誤案例多線程

定義一個 @PerActivity 的 Scope, 因而認爲凡是被這個 PerActivity 註解的 Provides 所建立的實例, 就會自動與當前 Activity 的生命週期同步。app

上述想法很是可愛,很是天真,因此不少不少程序猿們都是可愛的 (o´・ェ・`o) 要是僅僅靠一個註解就能全自動同步生命週期,那也太智能了。ide


下面開始好好學習啦。先來講說正常的注入流程:目標類首先須要建立一個 Component 的實例,而後調用它定義的注入方法,傳入自身。Component 就會查找須要注入的變量,而後去 Module 中查找對應的函數,一旦找到就調用它來獲取對象並注入。模塊化

這裏咱們能夠發現一個關鍵,也就是對象最終是 Module 裏的函數提供的。這個函數固然也是咱們本身編寫的,大部分狀況下在這裏會直接 new 一個出來。所以,若是屢次注入同一類型的對象,那麼這些對象將分別建立,各不相同。看下面的例子:函數

class MainAty : AppCompatActivity() {

    @Inject
    lateinit var chef1: Chef
	@Inject
	lateinit var chef2: Chef

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DaggerMainComponent.builder().mainModule(MainModule()).build().inject(this)
}
複製代碼

執行這段代碼會進行兩次注入,最終 chef1 與 chef2 將是兩個徹底不一樣的對象。

那若是咱們想得到一個「局部單例」呢?這時候就須要 Scope 了。首先咱們要定義一個 Scope:

@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class ActivityScope
複製代碼

再次強調!ActivityScope 只是一個名字而已,不表明它會自動與 Activity 的生命週期綁定!

而後在 Module 對應的方法上加上 @ActivityScope:

@Module
class MainModule {

    @Provides
    @ActivityScope
    fun provideChef() = Chef()

    @Provides
    @ActivityScope
    fun provideStove() = Stove()
}
複製代碼

最後根據要求,若是 Module 有 Scope,那麼對應的 Component 也必須有,因此給 Component 也加上:

@Component(modules = [MainModule::class])
@ActivityScope
interface MainComponent {
    fun inject(activity: MainAty)
}
複製代碼

[注] Component 能夠關聯多個 Scope。

此時咱們再執行上述代碼,會發現 chef1 與 chef2 是同一個對象。這就實現了局部單例,也就是 Scope 的做用。神奇吧!雖然咱們只簡單地 new 了一個對象,卻能實現單例。其實也不奇怪,看看源碼就能夠發現,加上了 Scope 後 Dagger 內部會自動把建立的對象緩存起來。

何爲局部單例

局部單例意思是,在同一個 Component 下是單例的,也呼應了前面所說 這裏所謂的「生命週期」是與 Component 相關聯的。由於咱們在這個 Activity 中只建立了一個 Component 所以注入的對象是單例的。但若換一個 Activity 那麼仍是會生成新的對象,其本質緣由是 Component 實例變了。

爲何能實現 Activity 生命週期同步

這個是真的能實現的,但和 Dagger 不要緊。一塊兒思考下:咱們在 Activity 的 onCreate() 方法中進行了注入,此時對象被建立,也就是建立週期同步了√。建立後有兩個對象會持有它的引用:① Activity ② Component(爲了實現局部單例會緩存),而 Component 實際上並無被咱們保存引用,它在注入完成後隨時會被回收掉。所以最終注入的對象只有 Activity 在引用,那天然當 Activity 被銷燬時就會被同步銷燬√。進而實現了所謂的「生命週期同步」。

結論很明顯了,Scope 不能管理生成對象的真正生命週期,只能控制對於同一個 Component 是不是局部單例的,請各位務必準確理解這一點。

@Singleton

理解了前面的 @Scope,那麼這個 @Singleton 就沒有任何難度了。

上面爲了實現局部單例,咱們自定義了一個 Scope 名爲 @ActivityScope。這很麻煩對不對?由於幾乎全部程序有會用到單例對象,爲了方便,Dagger 幫咱們預約義了一個 Scope ,這就是 @Singleton。

因此 @Singleton 沒有任何特殊之處(其實有一點點點點的特殊,最後講),它僅僅是爲了方便而已。你能夠把 @Singleton 直接替換成任何一個自定義的 Scope 代碼邏輯不會發生任何改變!

任何 Provides 都不會由於被 Scope 而自動地變成「全局單例」,@Singleton 亦然。

@Reusable

它的做用相似 Scope 但不徹底相同。Scope 目的是在 Component 的生命週期內保證對象的單例,其實它緩存了生成的對象,並使用 DoubleCheck 來檢查保證單例。所以被 Scope 標註的 Provides 是綁定到 Component 的。

而 Reusable 只是爲了儘量地重用對象。它沒有進行多線程檢查,所以沒法保證單例。最關鍵的是 Reusable 並不綁定 Component。所以一個被 Reusable 註解的 Provides 所提供的對象,會盡量地在全局範圍內重用,這將擁有比 Scope 更好的性能。

由於 Reusable 不與 Component 綁定,所以須要在 Component 也標記註解,只需在 Module 標記便可。如今咱們把上面的例子改爲 Reusable:

@Module
class MainModule {

    @Provides
    @Reusable // 替換 Scope
    fun provideChef() = Chef()

    @Provides
    @Reusable
    fun provideStove() = Stove()
}
複製代碼
@Component(modules = [MainModule::class])
//@ActivityScope 再也不須要額外的註解
interface MainComponent {
    fun inject(activity: MainAty)
}
複製代碼

OK~ 就這麼簡單。如今 Chef 已經能夠全局重用了,但不保證是單例的。

全局單例

既然 Scope 只能保證局部單例,但咱們如何實現全局單例呢。

咱們已經知道了,局部單例是與 Component 綁定的,所以只要 Component 是全局單例的,那麼它對應的 Module 下生成的全部對象都會變成全局單例,舉個例子:已知 a < b,那如何實現 a < 100?答:只需令 b = 100 便可。

那如何保證 Component 全局單例?由於 Component 是 Dagger 自動生成的,咱們不可能直接把他改成傳統的單例模式,那就只能從應用生命週期入手。咱們只需規定:只在 Application 類的 onCreate() 函數中實例化 Component,那個這個 Component 必定是單例的。其餘地方若是須要用到,徹底能夠 (getApplication() as MyApp).component 這樣獲取。

下面是一個例子:

@Module
class AppModule(val context: Context) {

    @Provides
    @Singleton
    fun provideContext() = context

}

@Component(modules = [AppModule::class])
@Singleton
interface AppComponent {
	fun context(): Context
}
複製代碼
class MyApplication: Application {

    lateinit var component: AppComponent

    override fun onCreate() {
        super.onCreate();
        component = DaggerAppComponent.builder().appModule(AppModule(this)).build();
    }
}
複製代碼

如此一來,咱們就實現了單例的 Component,其餘 Component 能夠依賴這個,進而可以在任何地方拿到 Context 來用。根據業務須要,咱們能夠在 AppModule 裏定義更多的 Provides 來注入全局單例的對象,例如數據庫等。

@BindsInstance

BindsInstance 用於簡化編寫含參構造函數的 Module。 遇到這種狀況咱們應該首選 BindsInstance 方式,而不是在 Module 的構造函數中增長參數。上面的 AppModule 是一個典型的例子。下面咱們將改寫它:

@Component()
@Singleton
interface AppComponent {
	fun context(): Context
	
    @Component.Builder // 自定義Builder
    interface Builder {
        @BindsInstance
        fun context(context: Context): Builder

        fun build(): AppComponent
    }
}
複製代碼

看到沒,這下連 Module 都免了。

以前咱們注入時是這樣寫的:

DaggerAppComponent.builder().appModule(AppModule(this)).build();
複製代碼

如今只需這樣寫:

DaggerAppComponent.builder().context(this).build();
複製代碼

注意: 在調用 build() 以前,必須先調用全部 BindsInstance 的函數來傳入所需參數。

Scope 的要求

多個 Scope 和多個 Component 使用時有一些要求須要遵照:

  • Component 和他所依賴的 Component 不能用相同的 Scope。編譯時會報錯,由於這有可能破壞 Scope 的範圍,詳見 issues
  • @Singleton 的 Component 不能依賴其餘 Component。這個好理解,畢竟 Singleton 設計及就是用來作全局的。若是有需求請自定義 Scope。(這算是 Singleton 的一點點特殊)
  • 無 Scope 的 Component 不能依賴有 Scope 的 Component,這也會致使 Scope 被破壞。
  • Module 以及經過構造函數注入依賴的類以及其 Component 必須有相同 Scope。

總結

寫了一夜一晚上,終於寫完就 Dagger 基礎了。下面會繼續寫 Android 方面 Dagger 的特殊功能。

回想起本身學 Dagger 的歷程,真的是很是頭疼。各類概念越看越暈。網上還有不少不負責任的教程本身都沒搞懂就開始誤導別人。但願這個系列文章能給 Dagger 的初學者帶來一點清新的感受吧。

相關文章
相關標籤/搜索