網站首頁 編程語言 正文
Hilt是什么
Hilt 是基于 Dagger2 的針對 Android場景定制化 的框架。
這有點像什么? RxAndroid 是 RxJava 的Android平臺定制化擴展。Andorid雖然由Java、Kotlin構成,但是它有很多平臺的特性,比如它有 Java開發 所不知道的 Context 等。
Dagger框架雖然很出名,在國外也很流行,但是在國內使用其的App少之又少,列舉一些缺點:
- 上手難,眾多Android應用框架中,Dagger必定是最難學的那一檔;
- 它就是一個復雜框架,沒有針對任何平臺,所以對于所有平臺來說會難用;
- 在Android Studio4.0版本以前,無法追蹤Dagger的依賴關系(就類比IDE無法通過快捷鍵知道一個接口有哪些實現類) 開發者不知道為啥要做依賴注入
- 對于第三點,Android Studio4.1已經支持了該功能,但是4.1有許多Bug,很多開發者都沒有升級 。
Hilt的出現解決前兩點問題,因為 Hilt 是 Dagger 針對Android平臺的場景化框架,比如Dagger需要我們手動聲明注入的地方,而Android聲明的地方不都在 onCreate()嗎,所以Hilt就幫我們做了,除此之外還做了很多事情,這樣一來,相較于Dagger,我們能用更少代碼、更容易、更輕松的配置依賴注入。
Hilt使用地方
Google認為移動端應用的架構設計,最重要的 Separation of concerns(分離關注點)。上網找解釋,其實它就是 模塊解耦。下面是Google官方推薦的Android應用架構圖:
依賴注入(DI)概念
而 Hilt 是被定義為 依賴注入框架而被發布。什么?又是依賴注入框架?不是之前已經有了一個 Dagger2 了嗎?除了 Dagger2, 還有 ButterKnife ,Kotlin甚至還有輕量級的 Koin。
什么是依賴注入?先來看下面代碼:
class MyClass {
val user = User()
}
我們在一個類 MyClass 中 聲明了一個變量 user,并初始化-------調用 User 構造函數,創建一個對象。
上面這段代碼就產生了一個依賴關系。
我們要先看懂誰依賴了誰?首先 MyClass 是我們的類,User 可以是我們自己寫的類,也可以是通過第三方Jar包或SDK里面的類。 在我們寫的 MyClass 的代碼里面,我們需要一個 User 對象來完成一些任務,所以我們創建了 User 對象,這就說明 MyClass 依賴了 User。對于 MyClass來說,User是外面之物,但是又需要依賴它。
如果上面這個 User,不是由自己創建,而是由外部創建,然后在本類只做賦值工作 ,這個過程就是 依賴注入。
有一個我們非常熟悉的設計模式,就使用了依賴注入的方法—工廠模式:
class UserFactory {
fun newUser(): User{
return User()
}
}
class MyClass {
val user = UserFactory.newUser()
}
我們的 MyClass 類需要使用 User 類,但是這次沒有自己來創建(沒有自己new出來),而是交由給 UserFactory 來創建出來,MyClass就做了最后的賦值工作。
對于 MyClass 來說,這就是一次依賴注入,和上面例子相比,把對象創建的過程交由給了別的類。
所以我們通過上面兩個例子就能知道依賴注入的本質是什么:借由外部得到對象。依賴注入框架就是這個外部
現在流行的 Dagger2、Koin框架,只是讓我們更輕松、更容易的去得到對象。
Dagger的中文翻譯是 “刀”,它就像一把刀,插進我們代碼中。那相信你也知道 ButterKnife 為什么這么取名了吧。
Hilt使用
導入
在 app 的 build.gradle 中加入:
plugins {
? ? ...
? ? id 'kotlin-kapt'
? ? id 'dagger.hilt.android.plugin'
}
dependencies {
? ? ...
? ? implementation 'com.google.dagger:hilt-android:2.28-alpha'
? ? kapt 'com.google.dagger:hilt-android-compiler:2.28-alpha'
}
在 project的 build.gradle 中加入:
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
一個簡單的例子 Hilt 需要 AndroidManifest 使用帶有 @HiltAndroidApp 注解的 Application 類,所以我們的 Application需要這樣:
@HiltAndroidApp
class HiltApp : Application() {
...
}
然后在 AndroidManifest 文件中聲明:
<application android:name=".HiltApp" ... </application>
假如我們要在 MainActivity 中注入一個 User 對象, 我們首先編寫一個 User 類,User類有兩個屬性 name 和 age 。 誒,這個時候有同學就會問了:我通過 @Inject 聲明一個User,Hilt就能給我創建一個 User 對象,那這個User對象里面的name和age是啥?答案是:編譯會報錯,因為我們自己都不知道這兩個參數是啥,Hilt怎么可能會知道,所以這兩個屬性也要通過依賴注入的方式來注入。
為了簡化這個問題,我定義一個默認的無參構造函數,反正創建之后,里面的值也是可以修改的嘛。
data class User(var name: String, var age: Int) {
// 定義一個默認的無參構造函數,并使用 @Inject 注解修飾
@Inject
constructor() : this("Rikka", 23)
}
接著我們在 MainActivity 中聲明一個 User,通過 @Inject 來修飾,并且MainActivity 需要通過 @AndroidEntryPoint 修飾:
// 1
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
// 2
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.d(TAG, "user name:${user.name} age:${user.age}")
}
}
Logcat 打印結果如下:
代碼解析:
注釋1: 為 MainActivity 修飾 @AndroidEntryPoint,該注解表明 該類為需要進行依賴注入的 Android類,是Dagger針對Android場景化的地方。當我們類中需要進行依賴注入,我們為該類加入這個注解,它會幫助創建一個單獨的 Hilt組件。它不能修飾Abstract,它只能修飾:
- ComponentActivity
- (Support)Fragment
- View
- Service
- BroadcastReceiver
注釋2:我們需要注入一個 User,所以我們給它加一個 @Inject 注解,告訴 Hilt。因為Kotlin的語法問題,這里不得不聲明 lateinit(而這里Koin的寫法更加優雅),接下來步驟大概是這樣的:
- Hilt 會去找User 這個類的構造函數,以此來創建一個對象
- Hilt 發現 有兩個個構造函數,而無參構造函數被@Inject 聲明
- Hilt 會去調用被@Inject 的構造函數,創建一個User(“Rikka”, 23) 對象
- 返回這個對象, MainActivity 實現外部幫忙創建 User對象,實現 User 的依賴注入。
Inject 的中文翻譯是 “注入、注射”,所以可以形象的認為, @Inject 修飾的變量是被外界通過針筒注入進來的。
- @Inject 可以修飾
- 構造函數 Constructors
- 變量 Fields
- 方法 Methods
構造函數是最先被注解的,然后再是變量和方法。所以它修飾構造函數和修飾變量,其實是不同的作用。但為了便于理解,我們可以把它看成是一個插眼工具,便于Hilt去尋找要注入的地方。
我們上面的 User 類是無參構造函數,這次假設我們要有參數的呢?其實就是參數也要注入嘛,這就是套娃來的,來看看我們給User新增一個 屬性:Clothes:
class Clothes @Inject constructor() {
}
class User @Inject constructor(var clothes: Clothes){
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
Log.d(TAG, "user clothes:${user.clothes}")
}
打印結果:
PS:大家不要太拘泥于有參構造函數的創建,我認為注入的作用是創建出一個對象,這個對象里面的內容可以后續再傳入,它更多的體現、或者我們需要注意的是 “分離關注點”
實現接口實例注入
因為接口沒有構造函數,所以當我們想要依賴一些接口時,該怎么辦。
我們來下面的示例,我們寫一個 Profession 接口,代表職業:
interface Profession {
fun doJob()
}
假設我們有兩個實現接口的類:醫生類和程序猿類:
class Doctor : Profession{
override fun doJob() {
Log.d("Doctor", "doctor do job")
}
}
class Programmer : Profession{
override fun doJob() {
Log.d("Programmer", "programmer do job")
}
}
這個時候我給 User 類添加一個職業的屬性,并希望它能夠自動注入:
class User @Inject constructor(var clothes: Clothes){
@Inject
lateinit var profession: Profession
}
因為 Profession 是一個接口,它有兩個實現類,所以這樣 Hilt 并不能知道我們要實例化哪個具體的實現類,所以編譯的時候就會報錯。
而 Hilt 也解決這種問題,首先我們要在每個實現類上注入構造函數:
class Doctor @Inject constructor() : Profession{
...
}
class Programmer @Inject constructor() : Profession{
...
}
接著我們需要實現一個和該接口有關的 XXXModule類,它被 @Module 修飾,這個和 Dagger 中的一樣,代表它會為接口提供一個創建實例的工廠,同時需要加上 @InstallIn 注解,用來聲明它是被安裝到哪個組件中,該注解后面會說到。 代碼如下:
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule { // 1
// 2
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
}
注釋1: 我們寫出來的類是一個抽象類,因為我們不需要具體的實現它,而且它沒有具體的命名規則,因為我們也不會在代碼中直接調用它,但是為了便于理解,我們起名一般叫 接口名 + Module。
注釋2: 我們假設該Module提供一個 Doctor 的職業,那我們需要定義一個 抽象方法 來獲取一個Doctor類。 并且 該方法需要被 @Binds 注解修飾。這樣就能被 Hilt 識別。
這樣一來,我們就實現了接口的一個實例化的注入,我們來實驗一下,在 User 中去展示它:
class User @Inject constructor(var clothes: Clothes){
@Inject
lateinit var profession: Profession
fun showMyself() {
profession.doJob()
}
}
// MainActivity
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var user: User
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
user.showMyself()
}
}
打印結果為:
可以看到我們的 Doctor 成功的注入了。
OK,我們了解了接口某一個實現類的注入 (Doctor),那假設這個時候,另外一個類需要注入接口的另一個實現類 Programmer,那我們是不是也得按照同樣的做法,在 Module類中添加呢?
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
@Binds
abstract fun bindProgrammer(programmer: Programmer): Profession
}
這個時候發現運行,編譯也會報錯:
提示我們被綁定多次了。
這是因為 Doctor 和 Programmer 都是相同類型,當他們一起被 Binds 注解,那 Hilt 不知道要去綁定哪一個。
這個時候就需要使用 @Qualifier 注解來幫助我們了,它就是為了這種 相同類型 依賴注入而產生的:
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindDoctor
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class
我們創建了新的注解 BindDoctor 和 BindProgrammer,他們都被 @Qualifier 修飾,表示他們用來作用在同種類型上, @Retention 選擇使用 BINARY類型,表明該注解保留到編譯后,但無法通過反射來得到,是比較適合的注解。
接下來,我們要將這些注解作用在被 Binds 注解的抽象方法上:
@Module
@InstallIn(ActivityComponent::class)
abstract class ProfessionModule {
@BindDoctor
@Binds
abstract fun bindDoctor(doctor: Doctor): Profession
@BindProgrammer
@Binds
abstract fun bindProgrammer(programmer: Programmer): Profession
}
最后,在 User 中聲明使用哪一個類型的注入:
class User @Inject constructor(var clothes: Clothes){
@BindProgrammer // 這次注入一個 Programmer
@Inject
lateinit var profession: Profession
fun showMyself() {
profession.doJob()
}
}
打印結果如下所示:
這下我們就實現了具體某個實例的注入啦。
實現第三方依賴注入
假設一些類不是由我們自己寫的,而是由第三方庫導入的。比如 OkHttp ,我們在使用網絡請求的時候,需要使用它,為了分離關注點,我們需要對他進行依賴注入。
但是 OkHttp 是我們不能修改的類,所以我們不能在它的構造函數上加入 @Inject, 這個時候該怎么辦呢?
Dagger 中也有類似的場景,我們需要 @Providers 來幫助我們。除此之外,我們也需要 @Module 注解來聲明一個 Module 類, 基于上面的例子,我們可以把這種 Module 類看成是一個工廠:
@Module
@InstallIn(ApplicationComponent::class)
class NetModule { // 1
// 2
@Provides
fun provideOkHttpClient(): OkHttpClient {
// 3
return OkHttpClient.Builder().build()
}
}
注釋1: 聲明一個 NetModule,提供網絡庫相關的組件,并沒有和上面例子一樣聲明為抽象函數,這是因為里面的都有具體的實現方法。
注釋2: 編寫一個 provideOkHttpClient() 方法,返回一個 OkHttpClient對象。 聲明一個 @Providers 注解,表示這個提供的依賴對象,是第三方的類或者系統類,我們因為不能直接更改其構造函數,所以得加上這個注解。
注釋3:new 一個對象,并返回。
這樣,我們就能在我們代碼中直接使用了:
@Inject
lateinit var okHttpClient:
@Providers 的本質是什么? 第三方類因為其只讀性,Hilt不能找到其構造函數,所以需要我們自己手動的創建,創建的方法被 @Providers 修飾, Hilt 找到這個方法,并提供由我們手動創建的對象。
所以 @Providers 的本質,是由我們自己創建對象, Hilt 幫我們注入。
現在大家都不會直接使用 OkHttp,而是使用 Retrofit,所以我們來提供一個 Retrofit 把:
...
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
因為 Retrofit的創建需要依賴一個 OkHttpClient 對象,所以我們需要創建一個,但是我們也可以注入一個,因為我們之前已經有 provideOkHttpClient,所以它就能提供一個實例,我們不用在擔心什么了。
Hilt 的內置組件和作用域
@InstallIn 注解
我們之前看到了 @InstallIn 這個注解,它的作用是用來表明 Module 作用的地方,它的參數時 xxxComponent格式,前面xx代表作用域。
因為 Hilt 是Dagger的Android場景化,所以它能作用的地方和我們Android息息相關,有下面幾處:
- Application ->ApplicationComponent
- ViewModel ->ActivityRetainedComponent
- Activity ->ActivityComponent
- Fragment ->FragmentComponent
- View ->ViewComponent
- Service ->ServiceComponent
- View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent
除了最后一個,別的作用域還是挺常見的。他們都要通過 @InstallIn 注入。
比如我們ProfessionModule是聲明成 @InstallIn(ActivityComponent::class),這就說明只有在 Activity 的代碼中可以使用它,而在 Service 中是不能使用的。 而 NetModule 則是聲明成 InstallIn(ApplicationComponent::class)的,這說明在全局都可以使用它提供的 OkHttpClient 和 Retrofit 對象。
使注入對象單例
像 Retrofit 、 OkHttpClient 這樣的全局都需要使用到的對象,我們希望它的作用域是全局,并且單例的。
但是 Hilt 提供的 @Inject 對象并不是單例的,每次 注入時都會重新生成一個新的實例,這就說明,假設我們要使用 Retrofit 來做網絡請求, @Providers 每次提供的都是不一樣的,這樣對性能來說很不友好,而且不符合常規的邏輯設定。
按照以往,我們可以通過給 NetModule 聲明一個 @Singleton 注解,來讓這個類實現單例,來解決這個問題。
Hilt 也有自己的解決方案,那就是使用 @xxxScope 注解,它和上面的 xxxComponent所對應,表示 在這個作用域內單例,來看看對應關系:
- Application ->ApplicationComponent ->@Singleton
- ViewModel ->ActivityRetainedComponent ->@ActivityRetainedScoped
- Activity ->ActivityComponent ->@ActivityScoped
- Fragment ->FragmentComponent ->@FragmentScoped
- View ->ViewComponent ->@ViewScoped
- Service ->ServiceComponent ->@ServiceScoped
- View Annotation with@WithFragmentBindings ->ViewWithFragemntComponent ->@ViewScoped
使用 @xxScoped 來替代 @InstallIn(xxxComponent::class) 聲明組件為單例。
因為 Application 是作用于全局,所以它的注解是 @Singleton,比較好理解。
作用域的包含關系
作用域也有自己的包含關系,比如被 @ActivityScoped聲明的組件,可以在 Fragment 或者 View 中使用,他們的具體包含關系如下圖所示:
Hilt 預置的 Qualifier
我介紹過 Hilt 是 Dagger針對Android的場景化,所以它低層做了很多事情,使得在Android上更好的使用。除了上面介紹過的那些注解外,還有很多別的東西,可以讓我們去探索,同時也了解了Dagger本身。
Context 上下文是Android 獨特的存在,它代表著 Application、Activity、Service的一些fwk層的東西。
而我們的代碼中經常會需要 Context 來創建一些東西:
class A @Inject constructor(context: Context) {
...
}
但是我們知道,它是系統類,我們無法注入 Context。那我們可以通過使用 @Providers 來創建嗎?
@Providers
fun provideContext() {
???
}
很明顯,Context 是由AMS來創建的,我們無法直接創建一個上下文出來。這個問題該如何解決呢?
答案是:我們不用解決,Hilt 為我們提供了它自己預置的注解 @ApplicationContext 和 @ActivityContext,我們直接使用,Hilt會幫我們注入上下文。
class A @Inject constructor(@ApplicaitonContext context: Context)
而現在沒有 ServiceContext,可能是用的比較少吧?
@ApplicationContext 提供的類型是 Application, 而不是我們自己的 App 自定義的 Application,加入我們要使用自己的該怎么辦呢?答案是也很簡單:
@Providers
fun provideApplicaiton(application: Application): MyApplication {
return applicaiton as MyApplication
}
直接在 Module 中提供一個,并強轉就 OK啦。
注意
ApplicationContext 的作用域是全局, 所以它修飾的類的作用只能是 @InstallIn(Applicaiton) 或 @Singleton,其他的也同理。
之前沒有用過 Dagger,因為項目不需要,且難學,問題多。 Hilt 出來之后解決了大部分的痛點,再不上車屬實就有點說不過去了。
Hilt 相較與 Dagger,肯定是更好用,更適合Android來使用。 它和 Koin的比較,只是性能上的差異,網上大部分的文章都認為 Hilt 性能更優,但是代碼量更多,在大的項目使用 Hilt 會更好,而小的項目兩者差別不會太大。具體還請開發者自己研究。
Hilt 作用是 提供依賴注入,幫助程序分離關注點,幫助搭建低耦合高內聚的框架,學習它,有利于我們學習 Android應用架構 方面的技能。
原文鏈接:https://blog.csdn.net/Androidxiaofei/article/details/129028075
相關推薦
- 2022-07-10 使用Docker安裝RabbitMQ
- 2023-01-07 Python個人博客程序開發實例信息顯示_python
- 2024-01-13 什么是B+樹?
- 2022-12-30 React?Refs轉發實現流程詳解_React
- 2022-05-20 C++實現簡單學生信息管理系統_C 語言
- 2022-10-13 詳解python-opencv?常用函數_python
- 2022-11-16 python3中requests庫重定向獲取URL_python
- 2023-07-08 git代碼回滾到某個tag
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支