Jetpack 新成員 Hilt 與 Dagger 大不一樣(三)落地篇

在 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

  • 初始化對比?
  • 與 Android 框架類對比?
  • 與 Room、WorkManager 對比?
  • 與 ViewModule 對比?
  • Hilt 在多模塊中的侷限性?

初始化對比

不管使用 Hilt 仍是使用 Dagger,使用它們以前都須要在 Application 裏面進行初始化,這是依賴注入容器的入口。面試

Dagger

在 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

在 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。性能優化

與 Android 框架類對比

咱們來看一下 Dagger 和 Hilt 對於最多見的 Android 類 Application、Activity、Fragment、View、Service、BroadcastReceiver 的支持,咱們以 Activity 和 Fragment 爲例。

Dagger

在 Dagger 中對於每個 Activity 和 Fragment 都須要告訴 Dagger 如何注入它們,因此咱們須要建立對應的 ActivityModuleFragmentModule

每次有新增的 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

在 Hilt 中 Android 框架類徹底由 Hilt 幫我管理,咱們只要在 Activiyt 和 Fragmetn 中添加 @AndroidEntryPoint 便可。

@AndroidEntryPoint
class HitAppCompatActivity : AppCompatActivity() {

}

@AndroidEntryPoint
class HiltFragment : Fragment() {
}
複製代碼

Hilt 真作了不少優化工做,相比於 Dagger 而言,刪除不少模板代碼,不須要開發者手動管理,開發者只須要關注如何進行綁定便可。

與 Room、WorkManager 對比

接下來咱們來看一下 Dagger 和 Hilt 對於 Room、WorkManager 在使用上有什麼區別,這裏以 Room 爲例。

Dagger

在 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

在 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

與 ViewModule 對比?

咱們在 Android 組件中注入一個 ViewModels 實例,須要經過 ViewModelFactory 來綁定 ViewModels 實例,傳統的調用方式以下所示:

ViewModelProviders.of(this).get(DetailViewModel::class.java)
複製代碼

接下來咱們來看一下在 Dagger 和 Hilt 中如何使用 ViewModule。

Dagger

在 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

Hilt 爲咱們提供了 @ViewModelInject 註解來注入 ViewModel 實例,另外 Hilt 爲 SavedStateHandle 類提供了 @Assisted 註解來注入 SavedStateHandle 實例。

class HiltViewModel @ViewModelInject constructor(
    private val tasksRepository: Repository,
    //SavedStateHandle 用於進程被終止時,存儲和恢復數據
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel()
複製代碼

Hilt 的侷限性

  • Hilt 不支持 ContentProvider,若是你在想在 ContentProvider 中獲取 Hilt 提供的依賴,須要使用 @EntryPoint 註解。具體如何使用,能夠看以前的內容 在 Hilt 不支持的類中執行依賴注入

  • Hilt 在多模塊項目中的侷限性,多模塊項目大概分爲兩種類型:

    • 分級模塊組成的應用程序。
    • 動態功能模塊(dynamic feature modules)組成的應用程序。

分級模塊組成的應用程序

若是多模塊項目是由分級模塊組成的應用程序,那麼可使用 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 中組件依賴問題?

    1. 在 app module 中或者任何其它能夠被 Hilt 處理的模塊中,定義一個接口並添加 @EntryPoint 註解,而後添加 @InstallIn 註解指定 module 的範圍。
// LoginModuleDependencies.kt - File in the app module.

@EntryPoint
@InstallIn(ApplicationComponent::class)
interface LoginModuleDependencies {

  @AuthInterceptorOkHttpClient
  fun okHttpClient(): OkHttpClient
}
複製代碼
    1. 在 dynamic-feature1 模塊中,使用 Dagger 中的 @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 方式同樣。

  • 在 Application 中 初始化 Dagger,並實現 HasActivityInjector 接口。
  • 對於每個 ViewModel、Fragment 和 Activity 咱們須要告訴 Dagger 如何注入它們。
  • 在每一個 modules 中添加 Fragments、Activities 和 ViewModels。
  • 全部的 Activity 繼承 BaseActivity,咱們須要實現 HasSupportFragmentInjector。

總結

關於 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 10 源碼分析的文章,瞭解系統源碼,不只有助於分析問題,在面試過程當中,對咱們也是很是有幫助的,若是你同我同樣喜歡研究 Android 源碼,能夠關注我 GitHub 上的 Android10-Source-Analysis,文章都會同步到這個倉庫。

Android 應用系列

精選譯文

目前正在整理和翻譯一系列精選國外的技術文章,不只僅是翻譯,不少優秀的英文技術文章提供了很好思路和方法,每篇文章都會有譯者思考部分,對原文的更加深刻的解讀,能夠關注我 GitHub 上的 Technical-Article-Translation,文章都會同步到這個倉庫。

工具系列

相關文章
相關標籤/搜索