- 多種 Hilt 注解協(xié)同工作并生成代碼的方式。
- 當(dāng) Hilt 配合 Gradle 使用,Hilt Gradle 插件如何在幕后工作以改善整體體驗(yàn)。
多種 Hilt 注解協(xié)同工作并生成代碼的方式
Hilt 使用注解處理器生成代碼。對(duì)注解的處理發(fā)生在編譯器將源文件轉(zhuǎn)換為 Java 字節(jié)碼期間。顧名思義,注解處理器作用于源文件中的注解。注解處理器通常會(huì)檢查注解,并根據(jù)注解類型來(lái)執(zhí)行不同的任務(wù),例如代碼檢查或生成新文件。 在 Hilt 中,三個(gè)最重要的注解就是:@AndroidEntryPoint、@InstallIn 以及 @HiltAndroidApp。
-
@AndroidEntryPoint
https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html -
@InstallIn
https://dagger.dev/api/latest/dagger/hilt/InstallIn.html -
@HiltAndroidApp
https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html
@AndroidEntryPoint
AndroidEntryPoint 在您的 Android 類中啟用字段注入,例如 Activity、Fragment、View 以及 Service。
-
AndroidEntryPoint
https://dagger.dev/api/latest/dagger/hilt/android/AndroidEntryPoint.html
如下示例所示,通過向 PlayActivity 添加 AndroidEntryPoint 注解,即可輕松將 MusicPlayer 注入到我們的 Activity 中。
class PlayActivity : AppCompatActivity() {
lateinit var player: MusicPlayer
// ...
}
如果您使用 Gradle,您可能熟悉上文所述的簡(jiǎn)化語(yǔ)法。但這并不是真實(shí)的語(yǔ)法,而是 Hilt Gradle 插件為您提供的語(yǔ)法糖。接下來(lái)我們將探討更多關(guān)于 Gradle 插件的內(nèi)容,在此之前,我們先來(lái)看看這個(gè)例子在沒有語(yǔ)法糖的情況下應(yīng)該是什么樣子的。
class PlayActivity : Hilt_PlayActivity() {
lateinit var player: MusicPlayer
// ...
}
現(xiàn)在,我們看到原始基類 AppCompatActivity 是 AndroidEntryPoint 注解的真實(shí)入?yún)ⅰ?/span>PlayActivity 實(shí)際上繼承了生成的類 Hilt_PlayActivity,該類由 Hilt 注解處理器生成,并包含所有執(zhí)行注入操作需要的邏輯。針對(duì)上述內(nèi)容生成的基類,其代碼簡(jiǎn)化示例如下:
class Hilt_PlayActivity : AppCompatActivity {
override fun onCreate() {
inject()
super.onCreate()
}
private fun inject() {
EntryPoints.get(this, PlayActivity_Injector::class).inject(this as PlayActivity);
}
}
-
AppCompatActivity
https://developer.android.google.cn/reference/androidx/appcompat/app/AppCompatActivity
在示例中,生成的類繼承自 AppCompatActivity。然而,通常情況下生成的類會(huì)繼承傳入 AndroidEntryPoint 注解的類。這使得注入操作可以在任何您需要的基類中執(zhí)行。
生成類的主要目的是處理注入操作。為了避免字段在注入之前被意外訪問,有必要盡可能早地執(zhí)行注入操作。因此,對(duì)于 Activity,注入操作在 onCreate 中被執(zhí)行。
在 inject 方法中,我們首先需要一個(gè)注入器的實(shí)例——PlayActivity_Injector。在 Hilt 中,對(duì)于 Activity,注入器是一個(gè)入口點(diǎn),我們可以使用 EntryPoints 工具類獲得一個(gè)注入器的實(shí)例。
您可能想到了,PlayActivity_Injector 也是由 Hilt 注解處理器生成的。格式如下:
interface PlayActivity_Injector {
fun inject(activity: PlayActivity)
}
生成的注入器是一個(gè)被裝載到 ActivityComponent 的 Hilt 入口點(diǎn)。它僅包含一個(gè)讓我們注入 PlayActivity 實(shí)例的方法。如果您曾在 Android 應(yīng)用中使用過 Dagger (不通過 Hilt),您可能會(huì)熟悉這些直接在組件上編寫的注入方法。
@InstallIn
InstallIn 用于表明模塊或者入口點(diǎn)應(yīng)該被裝載到哪個(gè)組件中。在如下示例中,我們將 MusicDataBaseModule 裝載到 SingletonComponent 中:@Module
@InstallIn(SingletonComponent::class)
object MusicDatabaseModule {
// ...
}
-
InstallIn
https://dagger.dev/api/latest/dagger/hilt/InstallIn.html -
SingletonComponent
https://dagger.dev/api/latest/dagger/hilt/components/SingletonComponent.html
通過 InstallIn,應(yīng)用中任何傳遞依賴項(xiàng)內(nèi)都可以提供模塊和入口點(diǎn)。然而,部分情況下我們需要收集所有由InstallIn 注解提供的內(nèi)容以獲取每個(gè)組件的完整模塊和入口點(diǎn)。 Hilt 在特定的包下生成了元數(shù)據(jù)注解,以便更輕松地收集和發(fā)現(xiàn)這些由 InstallIn 注解所提供的內(nèi)容。生成的注解格式如下:
package hilt_metadata
@Generated("dagger.hilt.InstallInProcessor")
@Metadata(my.database.MusicDatabaseModule::class)
classMusicDatabaseModule_Metadata{}
通過將元數(shù)據(jù)放進(jìn)特定的包下,Hilt 注解處理器可以輕松地在您應(yīng)用中所有的傳遞依賴項(xiàng)中找到生成的元數(shù)據(jù)。至此,我們可以使用元數(shù)據(jù)注解中所包含的信息來(lái)找到由 InstallIn 注解所提供內(nèi)容的自身引用。在本示例中指的是 MusicDatabaseModule。
HiltAndroidApp
最后,HiltAndroidApp 注解可以讓您的 Android Application 類啟用注入。此處,您可以將其視為與 AndroidEntryPoint 注解完全相同。第一步,開發(fā)者僅需在 Application 類上添加 @HiltAndroidApp 注解。class MusicApp : Application {
lateinit var store: MusicStore
}
-
HiltAndroidApp
https://dagger.dev/api/latest/dagger/hilt/android/HiltAndroidApp.html
然而,HiltAndroidApp 還有另外一個(gè)重要的作用——生成 Dagger 組件。
當(dāng) Hilt 注解處理器遇到 @HiltAndroidApp 注解時(shí),會(huì)在包裝類中生成一些列組件,該包裝類與 Application 類同名,前綴為 HiltComponents_。如果您之前使用過 Dagger,這些組件就是添加了 @Component 和 @Subcomponent 注解的類,而在 Dagger 中通常需要您手動(dòng)編寫。
為了生成這些組件,Hilt 在上述元數(shù)據(jù)包中查找所有被添加 @InstallIn 注解的類。添加了 @InstallIn 注解的模塊被放置在相應(yīng)組件聲明的模塊列表中。添加了 @InstallIn 注解的入口點(diǎn)被放置在聲明相應(yīng)組件的父類型的位置。
從這里開始,Dagger 處理器接管并根據(jù) @Component 和 @Subcomponent 注解生成組件的具體實(shí)現(xiàn)。如果您曾使用過 Dagger (不通過 Hilt),那么大概率您已經(jīng)直接處理了這些類。但是,Hilt 對(duì)開發(fā)者隱藏了這種復(fù)雜操作。這是一篇關(guān)于 Hilt 的文章,我們就不詳細(xì)介紹 Dagger 生成的代碼了。如果您有興趣,詳情請(qǐng)查閱:
-
Ron Shapiro 和 David Baker 的演講:
https://www.youtube.com/watch?v=wCvXe2LsN5o
-
Dagger codegen 101 的備忘單:
https://medium.com/androiddevelopers/dagger-code-generation-cheat-sheets-6b4fa2da4e7a
Hilt Gradle 插件
現(xiàn)在您已經(jīng)了解了 Hilt 中代碼生成的工作原理,接下來(lái)讓我們看看 Hilt Gradle 插件。Hilt Gradle 插件執(zhí)行很多有用的任務(wù),包括字節(jié)碼改寫和類路徑聚合。-
Hilt Gradle 插件
https://dagger.dev/hilt/gradle-setup#hilt-gradle-plugin
字節(jié)碼改寫
顧名思義,字節(jié)碼改寫就是改寫字節(jié)碼的過程。與注解處理只能生成新代碼不同,字節(jié)碼改寫可以修改現(xiàn)有代碼。如果謹(jǐn)慎使用,這將是非常強(qiáng)大的功能。
為了說(shuō)明我們?yōu)楹卧?Hilt 中使用字節(jié)碼改寫,讓我們回到 @AndroidEntryPoint。
雖然繼承 Hilt_PlayActivity 基類在實(shí)踐中有效,但它可能會(huì)導(dǎo)致 IDE 報(bào)錯(cuò)。由于生成的類在您成功編譯代碼后才存在,因此您經(jīng)常會(huì)在 IDE 中看到紅色波浪線。此外,您將無(wú)法享有諸如方法重載這種自動(dòng)補(bǔ)全的能力,并且您將無(wú)法訪問基類中的方法。 失去這些功能不僅會(huì)降低您的編碼速度,而且這些紅色波浪線也會(huì)極大程度地分散您的注意力。 Hilt Android 插件通過在您的類上添加 AndroidEntryPoint 注解來(lái)啟動(dòng)字節(jié)碼改寫。啟用 Hilt Android 插件后,您只需要在類上添加 @AndroidEntryPoint 注解,同時(shí)您可以使其繼承普通的基類。class PlayActivity : Hilt_PlayActivity {
override fun onCreate(…) {
val welcome = findViewById(R.id.welcome)
}
}
@AndroidEntryPoint
class PlayActivity : AppCompatActivity { // <-- 無(wú)需引用生成的基類
override fun onCreate(…) {
val welcome = findViewById(R.id.welcome)
}
}
由于此語(yǔ)法無(wú)需引用生成的基類,所以不會(huì)引起 IDE 報(bào)錯(cuò)。在字節(jié)碼改寫期間,Hilt Gradle 插件會(huì)將您的基類替換為 Hilt_PlayActivity。由于此過程直接操作字節(jié)碼,對(duì)開發(fā)者是不可見的。
然而,字節(jié)碼改寫仍有一些缺點(diǎn):
-
該插件必須修改底層字節(jié)碼,而不是源代碼,這容易出錯(cuò)。
-
因?yàn)樵诟膶懖僮鲿r(shí)字節(jié)碼已經(jīng)被編譯,所以問題通常出現(xiàn)在運(yùn)行時(shí)而不是編譯時(shí)。
-
改寫操作使調(diào)試變得復(fù)雜,因?yàn)楫?dāng)出現(xiàn)問題時(shí),源文件可能并不代表當(dāng)前正在執(zhí)行的字節(jié)碼。
由于這些原因,Hilt 嘗試盡可能減少依賴字節(jié)碼改寫。
類路徑聚合
最后,讓我們看看 Hilt Gradle 插件的另一個(gè)有用功能: 類路徑聚合。要了解什么是類路徑聚合,以及為什么需要它,讓我們看另一個(gè)示例。
在本示例中 :app 依賴一個(gè)獨(dú)立的 Gradle 模塊 :database,:app 和 :database 都提供了被 InstallIn 注解的模塊。
如您所見,Hilt 會(huì)在特定的 hilt_metadata 包下生成元數(shù)據(jù),在生成組件時(shí),會(huì)用它們查找所有被添加 @InstallIn 注解的模塊。
不使用類路徑聚合的處理對(duì)于單層依賴關(guān)系仍然可以正常工作,現(xiàn)在讓我們看看當(dāng)添加另一個(gè) Gradle 模塊 :cache 作為 :database 的依賴項(xiàng)時(shí)會(huì)發(fā)生什么。
當(dāng) :cache 被編譯時(shí),雖然它會(huì)生成元數(shù)據(jù),但在編譯 :app 時(shí)該元數(shù)據(jù)無(wú)法使用,因?yàn)樗且粋€(gè)傳遞依賴項(xiàng)。因此,Hilt 無(wú)法知曉 CacheModule,它會(huì)意外地從生成的組件中排除。 當(dāng)然,您可以使用 api 而不是 implementation 聲明 :cache 的依賴關(guān)系,從而在技術(shù)層面解決這個(gè)問題,但不推薦這樣做。使用 api 不僅會(huì)讓增量構(gòu)建變得更糟糕,還把維護(hù)工作也變成一場(chǎng)噩夢(mèng)。 這就是 Hilt Gradle 插件發(fā)揮作用的地方。
即使使用 implementation,Hilt Gradle 插件也可以自動(dòng)從 :app 的傳遞依賴項(xiàng)中聚合所有的類。
此外,與直接使用 api 相比,Hilt Gradle 插件還具有許多優(yōu)點(diǎn)。
首先,對(duì)比在整個(gè)應(yīng)用中手動(dòng)使用 api 依賴關(guān)系,類路徑聚合更不容易出錯(cuò)并且不需要維護(hù)。您可以像往常一樣簡(jiǎn)單地使用 implementation,其余的將由 Hilt Gradle 插件處理。
其次,Hilt Gradle 插件僅在應(yīng)用級(jí)別聚合類,因此與使用 api 不同,項(xiàng)目中庫(kù)的編譯不受影響。
最后,類路徑聚合為您的依賴項(xiàng)提供了更好的封裝,因?yàn)椴豢赡茉谠次募幸馔庖眠@些類,并且它們不會(huì)出現(xiàn)在代碼補(bǔ)全提示中。
總結(jié)
本文我們揭示了各種 Hilt 注解協(xié)同工作以生成代碼的方式。 我們還關(guān)注了 Hilt Gradle 插件,并了解它是如何在幕后使用字節(jié)碼改寫和類路徑聚合,讓 Hilt 的使用變得更安全、更輕松。-
處理器
+關(guān)注
關(guān)注
68文章
19843瀏覽量
234079 -
API
+關(guān)注
關(guān)注
2文章
1566瀏覽量
63710 -
代碼
+關(guān)注
關(guān)注
30文章
4892瀏覽量
70423
原文標(biāo)題:Hilt 工作原理 | MAD Skills
文章出處:【微信號(hào):Google_Developers,微信公眾號(hào):谷歌開發(fā)者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
LDO的工作原理、特性、應(yīng)用場(chǎng)景詳解
氣體傳感器工作原理及分類
深入探討DeepSeek大模型的核心技術(shù)

鎖存器的工作原理與作用
干簧管傳感器可以檢測(cè)什么,干簧管傳感器工作原理
無(wú)線收發(fā)器工作原理,無(wú)線收發(fā)器怎么使用
相位測(cè)量?jī)x工作原理,相位測(cè)量?jī)x怎么使用
深入探討Linux系統(tǒng)中的動(dòng)態(tài)鏈接庫(kù)機(jī)制

得瑞領(lǐng)新亮相IDC中國(guó)年度峰會(huì),深入探討NVMe SSD助力金融行業(yè)數(shù)字化轉(zhuǎn)型

光耦合器的工作原理

LLC電源轉(zhuǎn)換器的工作頻率
SPWM逆變器的基本工作原理

SIM卡座帶卡托的工作原理及使用特點(diǎn)

深入剖析測(cè)速傳感器:工作原理、種類及應(yīng)用領(lǐng)域
深入探討Linux的進(jìn)程調(diào)度器

評(píng)論