Jetpack Hilt 依賴注入框架上手指南

Jetpck Dagger-Hilt

在這裏插入圖片描述

依賴注入是什麼

個人理解:吧有依賴關係的類放在容器中,解析這些類的實例,並在運行時注入到對應的字段中,就是依賴注入,目的是爲了類的解耦

例子:A 類 中用到了 B 類,一般情況下需要在 A 類中 new B() 的實例對象

​ 採用依賴注入後,在 A 類中 定義一個私有的 B 類 字段。並在運行的時候通過從相關的容器中獲取出來 B 的對象並注入到 A 類中的 字段中。

這樣做的好處是什麼?

​ 如果有很多個類需要使用 B 類。難道都要在各自的類中進行 new B() 嗎。這樣對後期的維護和管理都是不方便的。使用 依賴注入則就變得很簡單了。

Hilt 是什麼

​ Hilt 是 Android 的依賴注入庫,是基於 Dagger 。可以說 Hilt 是專門爲 Andorid 打造的。

​ Hilt 創建了一組標準的 組件和作用域。這些組件會自動集成到 Android 程序中的生命週期中。在使用的時候可以指定使用的範圍,事情作用在對應的生命週期當中。


Hilt 常用的註解的含義

  • @HiltAndroidApp

    @HiltAndroidApp 將會觸發 Hilt 的代碼生成,作爲程序依賴項容器的基類

    生成的 Hilt 依附於 Application 的生命週期,他是 App 的父組件,提供訪問其他組件的依賴

    在 Application 中配置好後,就可以使用 Hilt 提供的組件了;組件包含 Application,Activity,Fragment,View,Service 等。

  • @HiltAndroidApp

    創建一個依賴容器,該容器遵循 Android 的生命週期類,目前支持的類型是: Activity, Fragment, View, Service, BroadcastReceiver.

  • @Inject

    使用 @Inject 來告訴 Hilt 如何提供該類的實例,常用於構造方法,非私有字段,方法中。

    Hilt 有關如何提供不同類型的實例信息也稱之爲綁定

  • @Module

    module 是用來提供一些無法用 構造@Inject 的依賴,如第三方庫,接口,build 模式的構造等

    使用 @Module 註解的類,需要使用 @InstallIn 註解指定 module 的範圍

    增加了 @Module 註解的類,其實代表的就是一個模塊,並通過指定的組件來告訴在那個容器中可以使用綁定安裝。

  • @InstallIn

    使用 @Module 注入的類,需要使用 @InstallIn 註解指定 module 的範圍。

    例如使用 @InstallIn(ActivityComponent::class) 註解的 module 會綁定到 activity 的生命週期上。

  • @Provides

    常用於被 @Module 註解標記類的內部方法上。並提供依賴項對象。

  • @EntryPoint

Hilt 支持最常見的 Android 類 Application、Activity、Fragment、View、Service、BroadcastReceiver 等等,但是您可能需要在Hilt 不支持的類中執行依賴注入,在這種情況下可以使用 @EntryPoint 註解進行創建,Hilt 會提供相應的依賴。


Hilt 中的組件(Compenent)

使用 @Module 註解的類,需要使用 @Installin 註解來指定 module 的範圍。

例如 @InstallIn(ApplicationComponent::class) 註解的 Module 就會綁定到 Application 的生命週期上。

Hilt 提供了以下組件來綁定依賴與對應 Android 類的活動範圍

Hilt 組件 對應 Android 類活動的範圍
ApplicationComponent Application
ActivityRetainedComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View annotated with @WithFragmentBindings
ServiceComponent Service

Hilt 沒有爲 broadcast receivers 提供組件,因爲 Hilt 直接進從 ApplicationComponent 中注入 broadcast receivers。


Hilt 中組件的生命週期

Hilt 會根據相應的 Android 類生命週期自動創建和銷燬組件的實例,對應關係如下:

Hilt 提供的組件 創建對應的生命週期 結束對應的生命週期 作用範圍
ApplicationComponent Application#onCreate() Application#onDestroy() @Singleton
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy() @ActivityRetainedScope
ActivityComponent Activity#onCreate() Activity#onDestroy() @ActivityScoped
FragmentComponent Fragment#onAttach() Fragment#onDestroy() @FragmentScoped
ViewComponent View#super() View destroyed @ViewScoped
ViewWithFragmentComponent View#super() View destroyed @ViewScoped
ServiceComponent Service#onCreate() View destroyed @ViewScoped

如何使用 Hilt

buildscript {
    dependencies {
        //hilt
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
    }
}
apply plugin: 'kotlin-kapt'
apply plugin: 'com.xiaojinzi.component.plugin'

//hilt
api "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"

@HiltAndroidApp
class BaseApplication : Application() {

    override fun onCreate() {
        super.onCreate()
    }
}

到這裏準備工作就做完了


使用 Hilt 進行依賴注入

class HiltTest @Inject constructor() {

    fun hiltTest() {
        Log.e("----------->", "hiltTest: ")
    }
}
@HiltAndroidApp
class BaseApplication : Application() {

    @Inject
    lateinit var hiltTest: HiltTest

    override fun onCreate() {
        super.onCreate()
        hiltTest.hiltTest()
    }
}

Hilt 在 Android 組件中的使用

  • 如果使用 @AndroidEntryPoint 註解 Android 類,還必須註解依賴他的 Android 類;

  • 例如: 給 fragment 使用 @AndroidEntryPoint 後,則還需要給 fragmet 依賴的 Activity 依賴 @AndroidEntryPoint ,否則會出現異常

  • @AndroidEntryPoint 不能以寫在抽象類上

  • @AndroidEntryPoint 註解 僅僅支持 ComponentActivity 的子類,例如 Fragment,AppCompatActivity 等等。

@AndroidEntryPoint
class HomeNavigationActivity : BaseLayoutActivity<TestViewModel>() {

    override fun setViewModel(): Class<TestViewModel> =TestViewModel::class.java

    override fun layout(): Int {
        return R.layout.home_navigation
    }

    override fun bindView() {
    }

}
// fragment 中使用,需要本身所依賴的 activity 添加註解
@AndroidEntryPoint
class FragmentOne : BaseLayoutFragment<FragOneViewModel>() {
    
    //使用 @Inject 從組件中獲取依賴進行注入
    @Inject
    lateinit var hiltTest: HiltTest

    override fun layout(): Int {
        return R.layout.frag_one
    }
    override fun bindView(rootView: View) {
        //對象已經注入,直接調用即可
        one.text = hiltTest.hiltTest()
    }
}

Hilt 和第三方組件的使用

如果需要在項目中注入第三方依賴,可以使用 @Module 註解。使用 @Module 在註解的普通類,在其中創建第三方依賴的對象即可。

  • @Module 模塊用於向 Hilt 添加綁定,告訴 Hilt 如果提供不同類型的實例。

    使用了 @Module 的類,相當於是一個模塊,常用於創建依賴對象(如,Okhttp,Retrofit 等)。

  • 使用 @Module 的類,需要使用 #InstallIn 指定此 module 的範圍,會綁定到對應 Android 類的生命週期上

  • @Providers,常用於被 @Module 註解標記類的內部方法,並提供依賴項對象。

//對應的生命週期爲 application
@Module
@InstallIn(ApplicationComponent::class)
object TestModule {
    
    /**
     * 每次都是新的實例
     */
    @Provides
    fun bindHiltTest(): HiltTest {
        XLog.e("--------bindHiltTest----")
        return HiltTest()
    }

    /**
     * 全局複用同一個實例
     */
    @Provides
    @Singleton
    fun bindSingTest(): Test {
        XLog.e("--------bindSingTest----")
        return Test()
    }

}

使用如下:

@Inject
lateinit var hiltTest: HiltTest

@Inject
lateinit var hiltTest1: HiltTest

@Inject
lateinit var test1: Test

@Inject
lateinit var test2: Test

其中 bindSingTest 只會被調用一次,@SingLeton 相當於是一個單例


Hilt 和 ViewModel 的使用

使用之前需要在 app.build 下添加一下對 viewModel的支持

implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'
  • 通過 @ViewModelInject 註解進行構造注入。
  • SavedStateHandle 使用 @Asssisted 註解
class HomeContentViewModel @ViewModelInject  constructor(
    private val response: HomeContentRepository,
    @Assisted val  state: SavedStateHandle
) : ViewModel() {
    
    private val liveData by lazy { MutableLiveData<String>() }

    val testLiveData: LiveData<String> by lazy { liveData }
    
    fun requestBaiDu() {
        launchVmHttp {
            liveData.postValue(response.requestBaidu())
        }
    }
}
  • 通過 @Inject 進行注入,在 viewModel 中不需要手動的創建其對象
@ActivityScoped
class HomeContentRepository @Inject constructor() : BaseRepository() {

    suspend fun requestBaidu(): String {
        return LvHttp.createApi(ApiServices::class.java).baidu()
    }
}
  • 獲取 viewModel 的實例
@AndroidEntryPoint
class HomeContentActivity : AppCompatActivity(){

    //生成 ViewModel 的實例
    private val viewModel by viewModels<HomeContentViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home_content)


        viewModel.requestBaiDu()
        viewModel.testLiveData.observe(this, Observer {
            ToastUtils.show(it)
        })
}

Hilt 和 Room 的使用

這裏需要用到 @Module 註解,使用 @Module 註解的普通類,在其中提供 Room 的實例。並且使用 @InstallIn 來聲明 作用範圍。

@Module
@InstallIn(ApplicationComponent::class)
object RoomModel {

    /**
     * @Provides:常用於被 @Module 標記類的內部方法,並提供依賴對象
     * @Singleton:提供單例
     */
    @Provides
    @Singleton
    fun provideAppDataBase(application: Application): AppDataBase {
        return Room
            .databaseBuilder(application, AppDataBase::class.java, "knif.db")
            .fallbackToDestructiveMigration()
            .allowMainThreadQueries()
            .build()
    }

    @Provides
    @Singleton
    fun providerUserDao(appDataBase: AppDataBase): UserDao {
        return appDataBase.getUserDao()
    }
}

我們給 providerUserDao 使用了 @Provides 註解 和 @Singleton 註解,是爲了告訴 Hilt,當使用 UserDao 時需要執行 appDataBase.getUserDao() 。

而在調用 appDataBase.getUserDao() 時需要傳入 AppDataBase,這時就會調用上面的方法 provideAppDataBase 了,因爲這個方法也是用了 @Provides 註解。

並且這兩個方法都是單例,只會調用一次。

使用如下:

class FragmentTwo : BaseLayoutFragment<FragTwoViewModel>() {

    @Inject
    lateinit var userDao: UserDao
}

到現在爲止,就可以在任意地方獲取到 UserDao,並且不用手動的創建實例。


使用 @Binds 進行接口注入

Binds:必須註釋一個抽象函數,抽象函數的返回值是實現的接口。通過添加具有接口實現類型的唯一參數來指定實現。

首先需要一個接口,和一個實現類

interface User {
    fun getName(): String
}
class UserImpl @Inject constructor() : User {
    override fun getName(): String {
        return "345"
    }
}

接着就需要新建一個 Module。用來實現接口的注入

@Module
@InstallIn(ApplicationComponent::class)
abstract class UserModule {
    @Binds
    abstract fun getUser(userImpl: UserImpl): User
}

注意:這個 Module 是抽象的。

使用如下:

@AndroidEntryPoint
class FragmentOne : BaseLayoutFragment<FragOneViewModel>() {

    @Inject
    lateinit var user: User
}

使用 @Qualifier 提供同一接口,不同的實現

還是上面的 User 接口,有兩個不同的實現,如下:

class UserAImpl @Inject constructor() : User {
    override fun getName(): String {
        return "345"
    }
}
class UserBImpl @Inject constructor() : User {
    override fun getName(): String {
        return "Lv"
    }
}

接着定義兩個註解

@Qualifier
annotation class A

@Qualifier
annotation class B

然後修改 Module ,在 module 中用來標記相應的依賴。

@Module
@InstallIn(ApplicationComponent::class)
abstract class UserAModule {
    @A
    @Singleton
    @Binds
    abstract fun getUserA(userImpl: UserAImpl): User
}

@Module
@InstallIn(ActivityComponent::class)
abstract class UserBModule {
    @B
    @ActivityScoped
    @Binds
    abstract fun getUserB(userImpl: UserBImpl): User
}

這裏用了兩個不同的 mdule,並且對應兩個不同的 component,一個是 application,另一個是 activity

最後使用如下:

@AndroidEntryPoint
class FragmentOne : BaseLayoutFragment<FragOneViewModel>() {

    @A
    @Inject
    lateinit var userA: User

    @B
    @Inject
    lateinit var userB: User
}

遇到的問題

在使用 @AndroidEntryPoint 註解的時候。需要在 fragment 和 actvity 都使用這個註解。

但是如果 activity 和 fragment 沒在同一個module中,就會報錯。

對於組件化的項目來說,這種情況就比較難受了。。。。

查找了一些資料:

  • 主要問題之一是,通過在 Hilt 中發現模塊的方式,無法區分哪些模塊屬於應用中的組件(如果他們確實使用過 Hilt) 已經庫或其他庫中的組件

  • 另一個問題是,他將預先構建的組件層次結構變得複雜和混亂。就將你的庫中所有活動一樣,使父級成爲 ApplicationComponent 也沒有意義,因爲您沒有將組件放入 Application 。同樣,如果一個僅包含片段庫並託管在應用程序的活動中,那可能會遇到類似的情況,您希望庫片段是獨立的,單讓 FragmentComponent 對象作爲 ActivityComponent 並沒有意義。


Hilt 好處

  • 降低 Android 開發者使用依賴注入框架的上手成本
  • 內部有一套標準的組件和作用域,對範圍進行聲明後,只能使用在指定的作用域中使用這個類,並且提供聲明週期的管理,會自動釋放不在使用的對象,減少資源的過度使用,提供代碼的可重用性。
  • 使用起來簡單,告別繁瑣的 new。。。 這種流程,只需要添加註解即可。提高了代碼的可讀性,構建簡單,耦合變低,容易測試
  • 我感覺最大的好處就是管理他們的生命週期,只能在對應的範圍內進行使用。感覺非常好。

參考自:

Jetpack 新成員

Hilt-依賴注入框架上手指南

官方文檔

如有問題,還請指出,謝謝!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章