Android Hilt實戰初體驗: Dagger替換成Hilt

在組件化AwesomeGithub項目中使用了Dagger來減小手動依賴注入代碼。雖然它能自動化幫咱們管理依賴項,可是寫過以後的應該都會體會到它仍是有點繁瑣的。項目中處處充斥着Component,這讓我想起了傳統MVP模式的接口定義。android

簡單來講就是費勁,有許多大量的相似定義。可能google也意識到這一點了,因此前不久發佈出了Hiltgit

Hilt

爲了防止沒據說過的小夥伴們一頭霧水,首先咱們來了解下Hilt是什麼?github

HiltAndroid的依賴注入庫,可減小在項目中執行手動依賴項注入的樣板代碼。bash

Hilt經過爲項目中的每一個 Android 類提供容器並自動管理其生命週期,提供了一種在應用中使用 DI(依賴項注入)的標準方法。HiltDagger 的基礎上構建而成,於是可以具備 Dagger 的編譯時正確性、運行時性能、可伸縮性。微信

那麼有的小夥伴可能會有疑問,既然已經有了Dagger那爲何還要Hilt的呢?架構

HiltDagger的主要目標都是一致的:app

  1. 簡化 Android 應用的 Dagger 相關基礎架構。
  2. 建立一組標準的組件和做用域,以簡化設置、提升可讀性以及在應用之間共享代碼。
  3. 提供一種簡單的方法來爲各類構建類型(如測試、調試或發佈)配置不一樣的綁定。

可是Android中會實例化許多組件類,例如Activity,所以在應用中使用Dagger須要開發者編寫大量的樣板代碼。Hilt能夠減小這些樣板代碼。ide

Hilt作的優化包括函數

  1. 無需編寫大量的Component代碼
  2. Scope也會與Component自動綁定
  3. 預約義綁定,例如 ApplicationActivity
  4. 預約義的限定符,例如@ApplicationContext@ActivityContext

下面經過AwesomeGithubDagger來對比了解Hilt的具體使用。組件化

依賴

使用以前將Hilt的依賴添加到項目中。

首先,將hilt-android-gradle-plugin插件添加到項目的根級 build.gradle文件中:

buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
複製代碼

而後,應用Gradle插件並在app/build.gradle文件中添加如下依賴項:

...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
 
android {
    ...
}
 
dependencies {
    implementation "com.google.dagger:hilt-android:2.28-alpha"
    kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
複製代碼

Application類

使用Dagger時,須要一個AppComponent單例組件,項目中的其它SubComponent都將依賴於它,因此在AwesomeGithub中它大概是這個樣子

@Singleton
@Component(
    modules = [
        SubComponentModule::class,
        NetworkModule::class,
        ViewModelBuilderModule::class
    ]
)
interface AppComponent {
 
    @Component.Factory
    interface Factory {
        fun create(@BindsInstance applicationContext: Context): AppComponent
    }
 
    fun welcomeComponent(): WelcomeComponent.Factory
 
    fun mainComponent(): MainComponent.Factory
     
    ...
 
    fun loginComponent(): LoginComponent.Factory
 
}
 
@Module(
    subcomponents = [
        WelcomeComponent::class,
        MainComponent::class,
        ...
        LoginComponent::class
    ]
)
object SubComponentModule
複製代碼

上面的我已經省略了大半,是否是看起來不少,並且最重要的是不少重複的結構基本都是同樣的。因此Hilt基於這一點進行了簡化,將這些重複的編寫轉成構建的時候自動生成。

Hilt要作的很簡單,添加幾個註釋

@HiltAndroidApp
class App : Application() { ... }
複製代碼

全部的Hilt應用都必須包含一個帶@HiltAndroidApp註釋的Application。它將替代Dagger中的AppComponent

Android類

對於Android類,使用Dagger時須要定義SubComponent並將它依賴到Application類中。下面以WelcomeActivity爲例。

@Subcomponent(modules = [WelcomeModule::class])
interface WelcomeComponent {
    @Subcomponent.Factory
    interface Factory {
        fun create(): WelcomeComponent
    }

    fun inject(activity: WelcomeActivity)
}
複製代碼

module的部分先不說,後面會說起

下面看Hilt的實現

@AndroidEntryPoint
class MainActivity : BaseHiltActivity<ActivityMainBinding, MainVM>() { ... }
複製代碼

Hilt要作的是添加@AndroidEntryPoint註釋便可。

驚訝,結合上面的,兩個註解就替換了Dagger的實現,如今是否體會到Hilt的簡潔?對新手來講也能夠下降很大的學習成本。

目前Hilt支持下面Android

  1. Application (@HiltAndroidApp)
  2. Activity
  3. Fragment
  4. View
  5. Searvice
  6. BroadcastReceiver

有一點須要注意,若是使用@AndroidEntryPoint註釋了某個類,那麼依賴該類的其它類也須要添加。

典型的就是Fragment,因此除了Fragment還須要給依賴它的全部Activity進行註釋。

@AndroidEntryPoint的做用,對照一下Dagger就知道了。它會自動幫咱們生成對應Android類的Componennt,並將其添加到Application類中。

@Inject

@Inject的使用基本與Dagger一致,能夠用來定義構造方法或者字段,聲明該構造方法或者字段須要經過依賴獲取。

class UserRepository @Inject constructor(
    private val service: GithubService
) : BaseRepository() { ... }
複製代碼

@Module

Hilt模塊也須要添加@Module註釋,與Dagger不一樣的是它還必須使用@InstallIn爲模塊添加註釋。目的是告知模塊用在哪一個Android類中。

@Binds

@Binds註釋會告知Hilt在須要提供接口的實例時要使用哪一種實現。 它的用法與Dagger沒什麼區別

@Module
@InstallIn(ActivityComponent::class)
abstract class WelcomeModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(WelcomeVM::class)
    abstract fun bindViewModel(viewModel: WelcomeVM): ViewModel
}
複製代碼

不一樣的是須要添加@InstallInActivityComponent::class用來代表該模塊做用範圍爲Activity

其實上面這塊對ViewModel的注入,使用Hilt時會自動幫咱們編寫,這裏只是爲了展現與Dagger的不一樣之處。後續會提到ViewModel的注入。

@Providers

提供一個FragmentManager的實例,首先是Dagger的使用

@Module
class MainProviderModule(private val activity: FragmentActivity) {
 
    @Provides
    fun providersFragmentManager(): FragmentManager = activity.supportFragmentManager
}
複製代碼

對比一下Hilt

@InstallIn(ActivityComponent::class)
@Module
object MainProviderModule {
 
    @Provides
    fun providerFragmentManager(@ActivityContext context: Context) = (context as FragmentActivity).supportFragmentManager
}
複製代碼

區別是在Hilt@Providers必須爲static類而且構造方法不能有參數。

@ActivityContextHilt提供的預約限定符,它能提供來自與ActivityContext,對應的還有@ApplicationContext

提供的組件

對於以前提到的@InstallIn會關聯不一樣的Android類,除了@ActivityComponent還有如下幾種

對應的生命週期以下

同時還提供了相應的做用域

因此Hilt的默認提供將大幅提升開發效率,減小許多重複的定義

ViewModel

最後再來講下ViewModel的注入。若是你使用到了Jetpack相信少不了它的注入。

對於Dagger咱們須要自定義一個ViewModelFactory,而且提供注入方式,例如在AwesomeGithubcomponentbridget模塊中定義了ViewModelFactory

@Module
abstract class ViewModelBuilderModule {
 
    @Binds
    abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
  
}
 
class ViewModelFactory @Inject constructor(private val creators: @JvmSuppressWildcards Map<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {
 
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        var creator = creators[modelClass]
        if (creator == null) {
            for ((key, value) in creators) {
                if (modelClass.isAssignableFrom(key)) {
                    creator = value
                }
            }
        }
 
        if (creator == null) {
            throw IllegalArgumentException("Unknown model class: $modelClass")
        }
 
        try {
            @Suppress("UNCHECKED_CAST")
            return creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException()
        }
    }
 
}
複製代碼

經過@Inject來注入構造實例,但構造方法中須要提供Map類型的creators。這個時候可使用@IntoMap,爲了匹配Map的類型,須要定義一個@MapKey的註釋

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MapKey
annotation class ViewModelKey(val value: KClass<out ViewModel>)
複製代碼

而後再到對應的組件下使用,例如匹配MainVM

@Module
abstract class MainModule {
 
    @Binds
    @IntoMap
    @ViewModelKey(MainVM::class)
    abstract fun bindViewModel(viewModel: MainVM): ViewModel
 
}
複製代碼

這樣就提供了Map<Class<MainVM>, MainVM>的參數類型,這時咱們自定義的ViewModelFactory就可以被成功注入。

例如basic模塊裏面的BaseDaggerActivity

abstract class BaseDaggerActivity<V : ViewDataBinding, M : BaseVM> : AppCompatActivity() {

    protected lateinit var viewDataBinding: V

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    protected val viewModel by lazy { ViewModelProvider(this, viewModelFactory)[getViewModelClass()] }
    ...
}
複製代碼

固然,別忘了MainVM也須要使用@Inject來聲明注入

class MainVM @Inject constructor() : BaseVM() { ... }
複製代碼

以上是DaggerViewModel使用的注入方式。

雖然自定義的ViewModelFactory是公用的,可是對於不一樣的ViewModel仍是要手動定義不一樣的bindViewModel方法。

而對於Hilt卻能夠省略這一步,甚至說上面的所有都不須要手動編寫。咱們須要作的是隻需在ViewModel的構造函數上添加@ViewModelInject

例如上面的MainVM,使用Hilt的效果以下

class MainVM @ViewModelInject constructor() : BaseVM() { ... }
複製代碼

至於Hilt爲何會這麼簡單呢?咱們不要忘了它的本質,它是在Dagger之上創建的,本質是爲了幫助咱們減小沒必要要的樣板模板,方便開發者更好的使用依賴注入。

Hilt中,上面的實現會自動幫咱們生成,因此纔會使用起來這麼簡單。

若是你去對比看AwesomeGithub上的feat_daggerfeat_hilt兩個分支中的代碼,就會發現使用Hilt明顯少了許多代碼。對於簡單的Android類來講就是增長几個註釋而已。

目前惟一一個比較不理想的是對於@Providers的使用,構造方法中不能有參數,若是在用Dagger使用時已經有參數了,再轉變成Hilt可能不會那麼容易。

慶幸的是,DaggerHilt能夠共存。因此你能夠選擇性的使用。

可是總體而言Hilt真香,你只要嘗試了毫不會後悔~

AwesomeGithub

AwesomeGithub是基於Github的客戶端,純練習項目,支持組件化開發,支持帳戶密碼與認證登錄。使用Kotlin語言進行開發,項目架構是基於JetPack&DataBinding的MVVM;項目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger與Hilt等流行開源技術。

除了Android原生版本,還有基於Flutter的跨平臺版本flutter_github

若是你喜歡個人文章模式,或者對我接下來的文章感興趣,你能夠關注個人微信公衆號:【Android補給站】

或者掃描下方二維碼,與我創建有效的溝通,同時可以更方便的收到相關的技術推送。

相關文章
相關標籤/搜索