在 Google 的 Hilt 文檔中 Dependency injection with Hilt 只是簡單的告訴咱們 Hilt 是 Android 的依賴注入庫,它減小了在項目中進行手動依賴,Hilt 是基於 Dagger 基礎上進行開發的,爲常見的 Android 類提供容器並自動管理它們的生命週期等等。java
文檔中的概念過於模糊,那麼 Hilt 與 Dagger 在使用上有那些區別,並無一個直觀感覺,而本文的目的就是詳細的分析一下 Hilt 與 Dagger 到底有那些不一樣之處。android
在以前的兩篇文章中已經詳細的介紹了 Hilt 註解的含義以及用法,並附上詳細的案例,在代碼中都有詳細的註釋,爲了節省篇幅,本文不會在詳細介紹 Hilt 註解的含義,能夠點擊下方連接前往查看。git
在以前的文章中 放棄 Dagger 擁抱 Koin 分析了 Dagger 和 Koin 編譯時間和使用上的不一樣等等,這篇文章主要從如下幾個方面分析 Hilt 與 Dagger 的不一樣之處。github
不管使用 Hilt 仍是使用 Dagger,使用它們以前都須要在 Application 裏面進行初始化,這是依賴注入容器的入口。面試
在 Dagger 中咱們必須經過 @Module
和 @Component
註解,建立對應的文件,並注入 Application算法
// Component 聲明瞭全部的 modules
// ActivityAllModule 配置了全部的 activity
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
複製代碼
而後建立完 modules 和 components 文件以後,須要在 Application 中 初始化 Dagger, 須要實現 HasActivityInjector
接口,用來自動管理 Activity。數據庫
class App : Application(), HasActivityInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
}
override fun onCreate() {
super.onCreate()
DaggerAppCompoment.builder()
.bindApplication(this)
.build()
.inject(this)
}
override fun activityInjector(): AndroidInjector<Activity> {
return dispatchingAndroidInjector
}
}
複製代碼
在 Hilt 中咱們不須要手動指定包含每一個模塊,在 Application 中添加 @HiltAndroidApp
註解將會觸發 Hilt 代碼的生成,用做應用程序依賴項容器的基類。編程
@HiltAndroidApp
class HiltApplication : Application() {
/**
* 1. 全部使用 Hilt 的 App 必須包含一個使用 @HiltAndroidApp 註解的 Application
* 2. @HiltAndroidApp 將會觸發 Hilt 代碼的生成,包括用做應用程序依賴項容器的基類
* 3. 生成的 Hilt 組件依附於 Application 的生命週期,它也是 App 的父組件,提供其餘組件訪問的依賴
* 4. 在 Application 中設置好 @HiltAndroidApp 以後,就可使用 Hilt 提供的組件了,
* Hilt 提供的 @AndroidEntryPoint 註解用於提供 Android 類的依賴(Activity、Fragment、View、Service、BroadcastReceiver)等等
* Application 使用 @HiltAndroidApp 註解
*/
}
複製代碼
全部使用 Hilt 的 App 必須包含一個使用 @HiltAndroidApp
註解的 Application,這是依賴注入容器的入口。數組
Hilt 提供了 @ApplicationContext
和 @ActivityContext
兩種預約義限定符,咱們能夠直接使用,不須要開發人員本身注入 Application。性能優化
咱們來看一下 Dagger 和 Hilt 對於最多見的 Android 類 Application、Activity、Fragment、View、Service、BroadcastReceiver 的支持,咱們以 Activity 和 Fragment 爲例。
在 Dagger 中對於每個 Activity 和 Fragment 都須要告訴 Dagger 如何注入它們,因此咱們須要建立對應的 ActivityModule
、FragmentModule
。
每次有新增的 Fragment 和 Activity 必須添加在對應的 Module 文件中,每次添加 Activity 時都須要添加 @ContributesAndroidInjector
註解,用於自動生成子組件相關代碼,幫咱們減小重複的模板代碼,編譯的時候會自動建立的一個類 ActivitylModule_ContributeXXXXActivity
,幫咱們生成注入的代碼。
// 把全部的 Activity 放到 ActivitylModule 進行統一管理
@Module
abstract class ActivitylModule(){
// ContributesAndroidInjector 用於自動生成子組件相關代碼,幫咱們減小重複的模板代碼
// modules 指定子組件(當前 MainActivity 包含了 2 個 fragment,因此咱們須要指定包含的 fragment)
// 每次新建的 activity 都須要在這裏手動添加
// 經過註解 @ActivityScope 指定 Activity 的 生命週期
@ActivityScope
@ContributesAndroidInjector(modules = arrayOf(FragmentModule::class))
abstract fun contributeMainActivity():MainActivity
}
// 管理全部的 Fragment
@Module
abstract class FragmentModule {
// 若是當前有新增的 Fragment 須要添加到這個模塊中
@ContributesAndroidInjector
abstract fun contributeHomeFragment(): HomeFragment
@ContributesAndroidInjector
abstract fun contributeAboutFragment(): AboutFragment
}
複製代碼
Dagger 提供了 HasSupportFragmentInjector
接口去自動管理 Fragment,全部的 Activity 繼承 BaseActivity,咱們須要實現 HasSupportFragmentInjector
,而且須要在 Activity 和 Fragment 中添加 AndroidInjection.inject(this)
。
abstract class BaseActivity : AppCompatActivity(),HasSupportFragmentInjector {
@Inject
lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
}
override fun supportFragmentInjector(): AndroidInjector<Fragment> {
return dispatchingAndroidInjector
}
}
abstract class BaseFragment : Fragment() {
override fun onAttach(context: Context?) {
super.onAttach(context)
AndroidSupportInjection.inject(this)
}
}
複製代碼
在 Hilt 中 Android 框架類徹底由 Hilt 幫我管理,咱們只要在 Activiyt 和 Fragmetn 中添加 @AndroidEntryPoint
便可。
@AndroidEntryPoint
class HitAppCompatActivity : AppCompatActivity() {
}
@AndroidEntryPoint
class HiltFragment : Fragment() {
}
複製代碼
Hilt 真作了不少優化工做,相比於 Dagger 而言,刪除不少模板代碼,不須要開發者手動管理,開發者只須要關注如何進行綁定便可。
接下來咱們來看一下 Dagger 和 Hilt 對於 Room、WorkManager 在使用上有什麼區別,這裏以 Room 爲例。
在 Dagger 中使用 Room 須要使用 @Module
註解建立 RoomModule 文件,而後在 Component 中添加 RoomModule
。
@Module
class RoomModule(val app: Application) {
@Provides
@Singleton
fun providerAppDataBase(): AppDataBase = Room
.databaseBuilder(app, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
// Component 聲明瞭全部的 modules
// ActivityAllModule 配置了全部的 activity
// RoomModule 和數據庫相關的
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
RoomModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
複製代碼
在 Dagger 中須要在對應的模塊中添加組件對應的生命週期 @Singleton
、@ActivityScope
等等。
@Singleton
對應的 Application。@ActivityScope
對應的 Activity。在 Hilt 中咱們只須要使用註解 @Module
建立 RoomModule
文件便可,不須要本身手動去添加 Module。
使用 @InstallIn
註解指定 module 的生命週期,例如使用 @InstallIn(ApplicationComponent::class)
註解 module 會綁定到 Application 的生命週期上。
@Module
@InstallIn(ApplicationComponent::class)
// 這裏使用了 ApplicationComponent,所以 RoomModule 綁定到 Application 的生命週期。
object RoomModule {
/**
* @Provides 經常使用於被 @Module 註解標記類的內部的方法,並提供依賴項對象。
* @Singleton 提供單例
*/
@Provides
@Singleton
fun provideAppDataBase(application: Application): AppDataBase {
return Room
.databaseBuilder(application, AppDataBase::class.java, "dhl.db")
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
}
}
複製代碼
Hilt 提供瞭如下組件來綁定依賴與對應的 Android 類。
Hilt 提供的組件 | 對應的 Android 類 |
---|---|
ApplicationComponent | Application |
ActivityRetainedComponent | ViewModel |
ActivityComponent | Activity |
FragmentComponent | Fragment |
ViewComponent | View |
ViewWithFragmentComponent | View annotated with @WithFragmentBindings |
ServiceComponent | Service |
咱們在 Android 組件中注入一個 ViewModels 實例,須要經過 ViewModelFactory 來綁定 ViewModels 實例,傳統的調用方式以下所示:
ViewModelProviders.of(this).get(DetailViewModel::class.java)
複製代碼
接下來咱們來看一下在 Dagger 和 Hilt 中如何使用 ViewModule。
在 Dagger 中,對於每個 ViewModel,須要告訴 Dagger 如何注入它們,因此咱們須要建立 ViewModelModule
文件,在 ViewModelModule
中管理全部的 ViewModel。
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(DetailViewModel::class)
abstract fun bindDetailViewModel(viewModel: DetailViewModel): ViewModel
@Binds
abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
}
複製代碼
建立完 ViewModelModule
文件以後,須要在 Component 中添加 ViewModelModule
// Component 聲明瞭全部的 modules
// RoomModule 和數據庫相關的
// ActivityAllModule 配置了全部的 activity
// ViewModelModule 配置全部的 ViewModel
@Singleton
@Component(modules = arrayOf(
AndroidInjectionModule::class,
RoomModule::class,
ViewModelModule::class,
ActivitylModule::class))
interface AppCompoment {
fun inject(app: App)
@Component.Builder
interface Builder {
@BindsInstance
fun bindApplication(app: Application): Builder
fun build(): AppCompoment
}
}
複製代碼
Hilt 爲咱們提供了 @ViewModelInject
註解來注入 ViewModel 實例,另外 Hilt 爲 SavedStateHandle
類提供了 @Assisted
註解來注入 SavedStateHandle
實例。
class HiltViewModel @ViewModelInject constructor(
private val tasksRepository: Repository,
//SavedStateHandle 用於進程被終止時,存儲和恢復數據
@Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel()
複製代碼
Hilt 不支持 ContentProvider
,若是你在想在 ContentProvider
中獲取 Hilt 提供的依賴,須要使用 @EntryPoint
註解。具體如何使用,能夠看以前的內容 在 Hilt 不支持的類中執行依賴注入
Hilt 在多模塊項目中的侷限性,多模塊項目大概分爲兩種類型:
若是多模塊項目是由分級模塊組成的應用程序,那麼可使用 Hilt 來完成依賴注入,分級模塊依賴以下圖所示。圖來自 Google。
從上到下層層依賴,這種狀況下能夠直接使用 Hilt 進行依賴注入,和在單個 App 模塊中使用是同樣的,這裏再也不詳述了,Hilt 在多模塊中的使用的項目示例 HiltWithMultiModuleSimple 已經上傳到 GitHub 上了,代碼中有詳細的註釋。
若是是模塊項目是 dynamic feature modules (動態功能模塊)組成的應用程序,那麼使用 Hilt 就有些侷限性了,dynamic feature modules 簡稱 DFMs。
在 DFMs 中,模塊之間相互依賴的方式是顛倒的,所以 Hilt 沒法在動態功能模塊中使用,因此在 DFMs 中只能使用 Dagger 完成依賴注入,在 DFMs 中模塊依賴以下圖所示。圖來自 Google。
一個 App 被分割成一個 Base APK 和多個模塊 APK。
Base APK: 這個 APK 包含了基本的代碼和資源(services, content providers, permissions)等等,其餘被分割的模塊 APK 均可以訪問,當一個用戶請求下載你的應用,這個 APK 首先下載和安裝。
Configuration APK:每一個 APK 包含特定的資源。當用戶下載你的應用程序時,他們的設備只下載和安裝針對他們設備的 Configuration APK。
Dynamic Feature APK:每一個 APK 包含應用程序的某個功能的代碼和資源,這些功能在首次安裝應用程序時是不須要的。用戶能夠按需安裝 Feature APK,從而爲用戶提供額外的功能。每一個 Feature APK 都依賴於 Base APK。
例如 dynamic-feature1 依賴於 Base APK,因此在 DFMs 中,模塊之間相互依賴的方式是顛倒的。
如何解決 Hilt 在 DFMs 中組件依賴問題?
@EntryPoint
註解,而後添加 @InstallIn
註解指定 module 的範圍。// LoginModuleDependencies.kt - File in the app module.
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface LoginModuleDependencies {
@AuthInterceptorOkHttpClient
fun okHttpClient(): OkHttpClient
}
複製代碼
@Component
註解,建立 Component 文件,並在 dependencies
中指定經過 @EntryPoint
註解聲明的接口。@Component(dependencies = [LoginModuleDependencies::class])
interface LoginComponent {
fun inject(activity: LoginActivity)
@Component.Builder
interface Builder {
fun context(@BindsInstance context: Context): Builder
fun appDependencies(loginModuleDependencies: LoginModuleDependencies): Builder
fun build(): LoginComponent
}
}
複製代碼
上面步驟完成以後,就能夠在 DFMs 中使用 Dagger 完成依賴注入,就跟咱們以前介紹的使用 Dagger 方式同樣。
關於 Hilt 在多模塊中的使用的項目示例 HiltWithMultiModuleSimple 已經上傳的 GitHub 能夠前去查看。
到這裏 Hilt 入門三部曲終於完結了,從入門、進階、到落地,全部註解的含義以及項目示例、以及和 Jetpack 組件的使用,Hilt 與 Dagger 不一樣之處,以及在多模塊中侷限性以及使用,所有都介紹完了。
對於使用 Dagger 小夥伴們,應該可以感覺到從入門到放棄是什麼感受,Dagger 學習的成本是很是高的,若是項目中引入了 Dagger 意味着團隊每一個人都要學習 Dagger,無疑這個成本是巨大的,並且使用起來很是的複雜。對於每個 ViewModel、Fragment 和 Activity 咱們須要告訴 Dagger 如何注入它們。
而 Hilt 的學習成本相對於 Dagger 而言成本很是低,Hilt 集成了 Jetpack 庫和 Android 框架類,並自動管理它們的生命週期,讓開發者只須要關注如何進行綁定,而不須要管理全部 Dagger 配置的問題。
正在創建一個最全、最新的 AndroidX Jetpack 相關組件的實戰項目 以及 相關組件原理分析文章,目前已經包含了 App Startup、Paging三、Hilt 等等,正在逐漸增長其餘 Jetpack 新成員,倉庫持續更新,能夠前去查看:AndroidX-Jetpack-Practice, 若是這個倉庫對你有幫助,請幫我點個贊,我會陸續完成更多 Jetpack 新成員的項目實踐。
致力於分享一系列 Android 系統源碼、逆向分析、算法、翻譯、Jetpack 源碼相關的文章,正在努力寫出更好的文章,若是這篇文章對你有幫助給個 star,一塊兒來學習,期待與你一塊兒成長。
因爲 LeetCode 的題庫龐大,每一個分類都能篩選出數百道題,因爲每一個人的精力有限,不可能刷完全部題目,所以我按照經典類型題目去分類、和題目的難易程度去排序。
每道題目都會用 Java 和 kotlin 去實現,而且每道題目都有解題思路、時間複雜度和空間複雜度,若是你同我同樣喜歡算法、LeetCode,能夠關注我 GitHub 上的 LeetCode 題解:Leetcode-Solutions-with-Java-And-Kotlin,一塊兒來學習,期待與你一塊兒成長。
正在寫一系列的 Android 10 源碼分析的文章,瞭解系統源碼,不只有助於分析問題,在面試過程當中,對咱們也是很是有幫助的,若是你同我同樣喜歡研究 Android 源碼,能夠關注我 GitHub 上的 Android10-Source-Analysis,文章都會同步到這個倉庫。
目前正在整理和翻譯一系列精選國外的技術文章,不只僅是翻譯,不少優秀的英文技術文章提供了很好思路和方法,每篇文章都會有譯者思考部分,對原文的更加深刻的解讀,能夠關注我 GitHub 上的 Technical-Article-Translation,文章都會同步到這個倉庫。