簡要
看此篇前
前篇需先看完,因為是接續的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中使用相同實例
注意:作用域限定規則如下:
- 如果某個類型標記有作用域註釋,該類型就只能由帶有相同作用域註釋的組件使用。
- 如果某個組件標記有作用域註釋,該組件就只能提供帶有該註釋的類型或不帶註釋的類型。
- 子組件不能使用其某一父組件使用的作用域註釋。
組件還涉及此上下文中的子組件
在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)
}

下面我們詳細介紹該圖的各個部分:
- NetworkModule(以及由此產生的 LoginRetrofitService)包含在 ApplicationComponent 中,因為您在組件中指定了它。
- UserRepository 保留在 ApplicationComponent 中,因為其作用域限定為 ApplicationComponent。如果項目擴大,您會希望跨不同功能(例如註冊)共享同一實例。
由於 UserRepository 是 ApplicationComponent 的一部分,其依賴項(即 UserLocalDataSource 和 UserRemoteDataSource)也必須位於此組件中,以便能夠提供 UserRepository 的實例。 - 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):在 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 | |
簡要 建構方法 |