Android 應用中使用Dagger-2

簡要

看此篇前

前篇需先看完,因為是接續的Android 應用中使用Dagger-1

如果還不知道Dagger是什麼,參考這篇Android Dagger基本知識與簡單測試

如果因為@Inject lateinit這行編譯錯誤,參考Android Dagger @Inject lateinit編譯錯誤?

1. Dagger 子组件

上一章節Android 應用中使用Dagger-1的LoginViewModel流程結束後,依然會存在內存內

如果希望LoginViewModel作用域限定為LoginActivity生命週期

首先先新增一個容器

@Component
interface LoginComponent {
    fun inject(activity: LoginActivity)
}

這時有個問題,LoginComponent必須要可以訪問ApplicationComponent

因為LoginViewModel依賴於UserRepository

父物件中提供的所有對象,也會在子物件中提供

如需創建子物件實例,必須要父物件實例

因此在此必須將 LoginComponent 定義為 ApplicationComponent 的子組件

@Subcomponent 為 LoginComponent 添加註釋

@Subcomponent
interface LoginComponent {
fun inject(activity: LoginActivity)
}

還必須在 LoginComponent 內部定義子組件工廠

以便 ApplicationComponent 知道如何創建 LoginComponent 的實例

@Subcomponent
interface LoginComponent {
@Subcomponent.Factory
interface Factory {
fun create(): LoginComponent
}
fun inject(activity: LoginActivity)
}

創建新的Dagger 模塊(例如 SubcomponentsModule),將子組件的類傳遞給屬性註釋的子組件

@Module(subcomponents = [LoginComponent::class])
class SubcomponentsModule {
}

將新模塊(即SubcomponentsModule)添加到ApplicationComponent

並提供LoginComponent 實例的 factory

@Singleton
@Component(modules = [NetworkModule::class, SubcomponentsModule::class])
interface ApplicationComponent {
    //已刪除fun inject(activity: LoginActivity)
    //提供LoginComponent 實例的 factory
    fun loginComponent(): LoginComponent.Factory
    fun getInt(): Int
}

請注意,ApplicationComponent 不再需要注入 LoginActivity,因為現在由 LoginComponent 負責注入,因此您可以從 ApplicationComponent 中移除 inject() 方法

2. 為子組件分配作用域

ApplicationComponent按應用的生命週期,基本上都是相同實例

LoginComponent對於每個Activity都使用新的實例,Fragment中使用相同實例

注意:作用域限定規則如下:

  1. 如果某個類型標記有作用域註釋,該類型就只能由帶有相同作用域註釋的組件使用。
  2. 如果某個組件標記有作用域註釋,該組件就只能提供帶有該註釋的類型或不帶註釋的類型。
  3. 子組件不能使用其某一父組件使用的作用域註釋。

組件還涉及此上下文中的子組件

在Activity中新增

lateinit var loginComponent: LoginComponent

Activity onCreate中新增

override fun onCreate(
        savedInstanceState: Bundle?
    ) {
        println("result: ${(applicationContext as MyApplication)
            .appComponent.getInt()}")
        //注入Activity
        loginComponent = (applicationContext as MyApplication)
            .appComponent.loginComponent().create()
        loginComponent.inject(this)
        //現在loginViewModel是可靠的,可使用
        super.onCreate(savedInstanceState)
}

每次請求時,LoginComponent 必須始終提供 LoginViewModel 的同一實例

@Scope
@Retention(value = AnnotationRetention.RUNTIME)
annotation class ActivityScope
@ActivityScope
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository
) {
fun testFunction() {
println("into testFunction")
}
}
@ActivityScope
@Subcomponent
interface LoginComponent {
@Subcomponent.Factory
interface Factory {
fun create(): LoginComponent
}
fun inject(activity: LoginActivity)
}

如果您有兩個需要 LoginViewModel 的 Fragment,系統就會為它們提供同一實例。

例如,如果您有 LoginUsernameFragment 和 LoginPasswordFragment,它們需要由 LoginComponent 注入

class LoginUsernameFragment: Fragment() {
@Inject
lateinit var loginViewModel: LoginViewModel

override fun onAttach(context: Context) {
super.onAttach(context)
(activity as LoginActivity).loginComponent.inject(this)
}
}
class LoginPasswordFragment: Fragment() {
@Inject
lateinit var loginViewModel: LoginViewModel

override fun onAttach(context: Context) {
super.onAttach(context)
(activity as LoginActivity).loginComponent.inject(this)
}
}
@ActivityScope
@Subcomponent
interface LoginComponent {
@Subcomponent.Factory
interface Factory {
fun create(): LoginComponent
}
fun inject(activity: LoginActivity)
fun inject(usernameFragment: LoginUsernameFragment)
fun inject(passwordFragment: LoginPasswordFragment)
}
新的Dagger圖長這樣

下面我們詳細介紹該圖的各個部分:

  1. NetworkModule(以及由此產生的 LoginRetrofitService)包含在 ApplicationComponent 中,因為您在組件中指定了它。
  2. UserRepository 保留在 ApplicationComponent 中,因為其作用域限定為 ApplicationComponent。如果項目擴大,您會希望跨不同功能(例如註冊)共享同一實例。
    由於 UserRepository 是 ApplicationComponent 的一部分,其依賴項(即 UserLocalDataSource 和 UserRemoteDataSource)也必須位於此組件中,以便能夠提供 UserRepository 的實例。
  3. LoginViewModel 包含在 LoginComponent 中,因為只有 LoginComponent 注入的類才需要它。 LoginViewModel 未包含在 ApplicationComponent 中,因為 ApplicationComponent 中的任何依賴項都不需要 LoginViewModel。

除了將對像作用域限定為不同的生命週期之外,創建子組件是分別封裝應用的不同部分的良好做法

根據應用流程構建應用以創建不同的 Dagger 子圖有助於在內存和啟動時間方面實現性能和擴容性更強的應用

注意:如果您需要使容器在出現設備旋轉等配置更改後繼續存在,請遵循保存界面狀態指南。您可能需要採用與處理進程終止相同的方式處理配置更改;否則,您的應用可能會在低端設備上丟失狀態。

3. 構建 Dagger 圖的最佳做法

為應用構建 Dagger 圖時:

  • 創建組件時,應該考慮什麼元素會決定該組件的生命週期。在本示例中,應用類負責 ApplicationComponent,而 LoginActivity 負責 LoginComponent。
  • 請僅在必要時使用作用域限定。過度使用作用域限定可能會對應用的運行時性能產生負面影響只要組件在內存中,對象就會在內存中獲取限定作用域的對象的成本更高。當 Dagger 提供對象時,它使用 DoubleCheck 鎖定,而不是 factory 類型提供程序。

4. 使用 Dagger 模塊

良好做法是模塊只在組件中聲明一次,特定高級 Dagger 用例除外

假設圖的配置方式如下。 ApplicationComponent 包括 Module1 和 Module2,Module1 包括 ModuleX

@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module
class Module2 { ... }

如果 Module2 現在依賴於 ModuleX 提供的類。錯誤做法是將 ModuleX 包含在 Module2 中,因為這樣 ModuleX 在圖中就出現了兩次,如以下代碼段所示:

//此處為不好的做法
@Component(modules = [Module1::class, Module2::class])
interface ApplicationComponent { ... }

@Module(includes = [ModuleX::class])
class Module1 { ... }

@Module(includes = [ModuleX::class])
class Module2 { ... }

您應改為執行以下某項操作:

  1. 重構模塊,並將共同模塊提取到組件中。
  2. 使用兩個模塊共享的對象創建一個新模塊,並將其提取到組件中。

如果不以這種方式進行重構,就會導致許多模塊相互包含而沒有清晰的結構,並且更難以了解每個依賴項的來源

良好做法(選項 1):在 Dagger 圖中聲明一次 ModuleX

@Component(modules = [Module1::class, Module2::class, ModuleX::class])
interface ApplicationComponent { ... }

@Module
class Module1 { ... }

@Module
class Module2 { ... }

良好做法(選項 2):將 ModuleX 中 Module1 和 Module2 的共同依賴項提取到包含在該組件中的名為 ModuleXCommon 的新模塊。

然後,使用特定於每個模塊的依賴項創建名為 ModuleXWithModule1Dependencies 和 ModuleXWithModule2Dependencies 的另外兩個模塊。

所有模塊在 Dagger 圖中都只聲明一次。

@Component(modules = [Module1::class, Module2::class, ModuleXCommon::class])
interface ApplicationComponent { ... }

@Module
class ModuleXCommon { ... }

@Module
class ModuleXWithModule1SpecificDependencies { ... }

@Module
class ModuleXWithModule2SpecificDependencies { ... }

@Module(includes = [ModuleXWithModule1SpecificDependencies::class])
class Module1 { ... }

@Module(includes = [ModuleXWithModule2SpecificDependencies::class])
class Module2 { ... }

以上內容參考Android 官網


相關文章

Android 依賴項注入(Dependency injection)Android Dagger基本知識與簡單測試
簡要
1. 非依賴項注入 vs 依賴項注入
2. 自動依賴項注入
簡要
1. Android使用Dagger前置作業
2. 基本使用方法
Android 應用中使用Dagger-1
簡要
建構方法

訂閱Codeilin的旅程,若有最新消息會通知。

發表留言

透過 WordPress.com 建置的網站.

向上 ↑

使用 WordPress.com 設計專業網站
立即開始使用