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。。。 這種流程,只需要添加註解即可。提高了代碼的可讀性,構建簡單,耦合變低,容易測試
- 我感覺最大的好處就是管理他們的生命週期,只能在對應的範圍內進行使用。感覺非常好。
參考自:
官方文檔
如有問題,還請指出,謝謝!!