Android Hilt實戰(zhàn)初體驗: Dagger替換成Hilt

在組件化AwesomeGithub項目中使用了Dagger來減少手動依賴注入代碼。雖然它能自動化幫我們管理依賴項,但是寫過之后的應(yīng)該都會體會到它還是有點繁瑣的。項目中到處充斥著Component,這讓我想起了傳統(tǒng)MVP模式的接口定義。

簡單來說就是費勁,有許多大量的類似定義。可能google也意識到這一點了,所以前不久發(fā)布出了Hilt。

## Hilt

為了防止沒聽說過的小伙伴們一頭霧水,首先我們來了解下Hilt是什么?

Hilt是Android的依賴注入庫,可減少在項目中執(zhí)行手動依賴項注入的樣板代碼。

Hilt通過為項目中的每個Android類提供容器并自動管理其生命周期,提供了一種在應(yīng)用中使用DI(依賴項注入)的標(biāo)準(zhǔn)方法。Hilt在Dagger的基礎(chǔ)上構(gòu)建而成,因而能夠具有Dagger的編譯時正確性、運行時性能、可伸縮性。

那么有的小伙伴可能會有疑問,既然已經(jīng)有了Dagger那為什么還要Hilt的呢?

Hilt與Dagger的主要目標(biāo)都是一致的:

1.簡化Android應(yīng)用的Dagger相關(guān)基礎(chǔ)架構(gòu)。

2.創(chuàng)建一組標(biāo)準(zhǔn)的組件和作用域,以簡化設(shè)置、提高可讀性以及在應(yīng)用之間共享代碼。

3.提供一種簡單的方法來為各種構(gòu)建類型(如測試、調(diào)試或發(fā)布)配置不同的綁定。

但是Android中會實例化許多組件類,例如Activity,因此在應(yīng)用中使用Dagger需要開發(fā)者編寫大量的樣板代碼。Hilt可以減少這些樣板代碼。

Hilt做的優(yōu)化包括

1.無需編寫大量的Component代碼

2.Scope也會與Component自動綁定

3.預(yù)定義綁定,例如Application與Activity

4.預(yù)定義的限定符,例如@ApplicationContext與@ActivityContext

下面通過AwesomeGithub中Dagger來對比了解Hilt的具體使用。

依賴

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

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

buildscript {

? ? ...

? ? dependencies {

? ? ? ? ...

? ? ? ? classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

? ? }

}

然后,應(yīng)用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

上面的我已經(jīng)省略了大半,是不是看起來很多,而且最重要的是很多重復(fù)的結(jié)構(gòu)基本都是一樣的。所以Hilt基于這一點進行了簡化,將這些重復(fù)的編寫轉(zhuǎn)成構(gòu)建的時候自動生成。

Hilt要做的很簡單,添加幾個注釋

@HiltAndroidAppclass App :Application() { ... }復(fù)制代碼

所有的Hilt應(yīng)用都必須包含一個帶@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的實現(xiàn)

@AndroidEntryPointclass MainActivity : BaseHiltActivity() { ... }復(fù)制代碼

Hilt要做的是添加@AndroidEntryPoint注釋即可。

驚訝,結(jié)合上面的,兩個注解就替換了Dagger的實現(xiàn),現(xiàn)在是否體會到Hilt的簡潔?對新手來說也可以降低很大的學(xué)習(xí)成本。

目前Hilt支持下面Android類

1.Application (@HiltAndroidApp)

2.Activity

3.Fragment

4.View

5.Searvice

6.BroadcastReceiver

有一點需要注意,如果使用@AndroidEntryPoint注釋了某個類,那么依賴該類的其它類也需要添加。

典型的就是Fragment,所以除了Fragment還需要給依賴它的所有Activity進行注釋。

@AndroidEntryPoint的作用,對照一下Dagger就知道了。它會自動幫我們生成對應(yīng)Android類的Componennt,并將其添加到Application類中。

@Inject

@Inject的使用基本與Dagger一致,可以用來定義構(gòu)造方法或者字段,聲明該構(gòu)造方法或者字段需要通過依賴獲取。

class UserRepository @Inject constructor(? ? private val service: GithubService) :BaseRepository() { ... }復(fù)制代碼

@Module

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

@Binds

@Binds注釋會告知Hilt在需要提供接口的實例時要使用哪種實現(xiàn)。它的用法與Dagger沒什么區(qū)別

@Module

@InstallIn(ActivityComponent::class)

abstract class WelcomeModule {

? ? @Binds

? ? @IntoMap

? ? @ViewModelKey(WelcomeVM::class)

? ? abstract fun bindViewModel(viewModel: WelcomeVM): ViewModel

}

不同的是需要添加@InstallIn,ActivityComponent::class用來表明該模塊作用范圍為Activity

其實上面這塊對ViewModel的注入,使用Hilt時會自動幫我們編寫,這里只是為了展示與Dagger的不同之處。后續(xù)會提到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

}

區(qū)別是在Hilt中@Providers必須為static類并且構(gòu)造方法不能有參數(shù)。

@ActivityContext是Hilt提供的預(yù)定限定符,它能提供來自與Activity的Context,對應(yīng)的還有@ApplicationContext

提供的組件

對于之前提到的@InstallIn會關(guān)聯(lián)不同的Android類,除了@ActivityComponent還有以下幾種

對應(yīng)的生命周期如下

同時還提供了相應(yīng)的作用域

所以Hilt的默認(rèn)提供將大幅提高開發(fā)效率,減少許多重復(fù)的定義

最后再來說下ViewModel的注入。如果你使用到了Jetpack相信少不了它的注入。

對于Dagger我們需要自定義一個ViewModelFactory,并且提供注入方式,例如在AwesomeGithub的componentbridget模塊中定義了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來注入構(gòu)造實例,但構(gòu)造方法中需要提供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>)

然后再到對應(yīng)的組件下使用,例如匹配MainVM

@Module

abstract class MainModule {

? ? @Binds

? ? @IntoMap

? ? @ViewModelKey(MainVM::class)

? ? abstract fun bindViewModel(viewModel: MainVM): ViewModel

}

這樣就提供了Map<Class<MainVM>, MainVM>的參數(shù)類型,這時我們自定義的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()] }

? ? ...

}

當(dāng)然,別忘了MainVM也需要使用@Inject來聲明注入

class MainVM @Inject constructor() :BaseVM() { ... }復(fù)制代碼

以上是Dagger的ViewModel使用的注入方式。

雖然自定義的ViewModelFactory是公用的,但是對于不同的ViewModel還是要手動定義不同的bindViewModel方法。

而對于Hilt卻可以省略這一步,甚至說上面的全部都不需要手動編寫。我們需要做的是只需在ViewModel的構(gòu)造函數(shù)上添加@ViewModelInject。

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

class MainVM @ViewModelInject constructor() :BaseVM() { ... }復(fù)制代碼

至于Hilt為什么會這么簡單呢?我們不要忘了它的本質(zhì),它是在Dagger之上建立的,本質(zhì)是為了幫助我們減少不必要的樣板模板,方便開發(fā)者更好的使用依賴注入。

在Hilt中,上面的實現(xiàn)會自動幫我們生成,所以才會使用起來這么簡單。

如果你去對比看AwesomeGithub上的feat_daggerfeat_hilt兩個分支中的代碼,就會發(fā)現(xiàn)使用Hilt明顯少了許多代碼。對于簡單的Android類來說就是增加幾個注釋而已。

目前唯一一個比較不理想的是對于@Providers的使用,構(gòu)造方法中不能有參數(shù),如果在用Dagger使用時已經(jīng)有參數(shù)了,再轉(zhuǎn)變成Hilt可能不會那么容易。

慶幸的是,Dagger與Hilt可以共存。所以你可以選擇性的使用。

但是整體而言Hilt真香,你只要嘗試了絕不會后悔~

AwesomeGithub

AwesomeGithub是基于Github的客戶端,純練習(xí)項目,支持組件化開發(fā),支持賬戶密碼與認(rèn)證登陸。使用Kotlin語言進行開發(fā),項目架構(gòu)是基于JetPack&DataBinding的MVVM;項目中使用了Arouter、Retrofit、Coroutine、Glide、Dagger與Hilt等流行開源技術(shù)。

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

如果你喜歡我的文章模式,或者對我接下來的文章感興趣,你關(guān)注一下我吧

我們還有《Android學(xué)習(xí)、面試;文檔、視頻資源》,可復(fù)制鏈接后用石墨文檔 App 或小程序打開鏈接或者私信我資料領(lǐng)取。

https://shimo.im/docs/TG8PDh9D96WGTT8W

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。