Android進(jìn)階寶典 -- Hilt的使用

在上一節(jié)中,我們簡(jiǎn)單介紹了Dagger2的使用聊记,其實(shí)我們?cè)谑褂肈agger2的時(shí)候,發(fā)現(xiàn)還是比較繁瑣的恢暖,要自己寫Module排监、Component、Provides等等杰捂,于是Hilt的團(tuán)隊(duì)就和Dagger2的團(tuán)隊(duì)一起舆床,設(shè)計(jì)了面向Android移動(dòng)端的依賴注入框架 -- Hilt

1 Hilt配置

在項(xiàng)目級(jí)的build.gradle中,引入支持hilt的插件,注意官方文檔中的2.28-alpha版本可能有文件挨队,建議使用下面的版本

classpath "com.google.dagger:hilt-android-gradle-plugin:2.43.2"

app的build.gradle中引入插件

id 'dagger.hilt.android.plugin'

引入依賴

implementation "com.google.dagger:hilt-android:2.43.2"
kapt "com.google.dagger:hilt-android-compiler:2.43.2"

2 Hilt的使用

首先按照慣例谷暮,先寫一個(gè)Module

@Module
class RecordModule {
    @Provides
    fun providerRecord():Record{
        return Record()
    }
}

如果是Dagger2的寫法,需要再寫一個(gè)Component盛垦,將RecordModule加載進(jìn)去湿弦,那么Hilt就不要這一步,而是需要一個(gè)注解InstallIn來(lái)聲明這個(gè)Module使用在哪個(gè)地方

@InstallIn(ApplicationComponent::class)
@Module
class RecordModule {
    @Provides
    fun providerRecord(): Record {
        return Record()
    }
}

在Hilt中有以下幾個(gè)Component腾夯,我這里拿幾個(gè)典型說(shuō)一下

image.png

首先ApplicationComponent颊埃,它會(huì)存在整個(gè)App生命周期中,隨著App的銷毀而銷毀蝶俱,也就意味著班利,在App的任何位置都可以使用這個(gè)Module

//A Hilt component that has the lifetime of the application
@Singleton
@DefineComponent
public interface ApplicationComponent {}

像ActivityComponent,肯定就是存在于整個(gè)Activity生命周期中跷乐,隨著Activity的銷毀而銷毀

//A Hilt component that has the lifetime of the activity.
@ActivityScoped
@DefineComponent(parent = ActivityRetainedComponent.class)
public interface ActivityComponent {}

其他的都類似肥败,都是隨著組件的生命周期結(jié)束而消逝。

例如我們需要在MainActivity中注入某個(gè)類愕提,那么需要使用@AndroidEntryPoint修飾馒稍,代表當(dāng)前依賴注入的切入點(diǎn)

@AndroidEntryPoint
class MainActivity : AppCompatActivity()

同時(shí),需要將當(dāng)前app定義為Hilt App

@HiltAndroidApp
class MyApp : Application() {

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

而且浅侨,在MainActivity中注入這個(gè)對(duì)象之后纽谒,也不需要像Dagger那樣,去創(chuàng)建具體的Component對(duì)象

@Inject
@JvmField
var record:Record? = null

這樣一看如输,Hilt是不是要比Dagger要簡(jiǎn)單許多了鼓黔。

2.1 局部單例

@InstallIn(ActivityComponent::class)
@Module
class RecordModule {
    @Provides
    @ActivityScoped
    fun providerRecord(): Record {
        return Record()
    }
}

如果我們希望Record是一個(gè)單例對(duì)象,可以使用@ActivityScoped注解修飾不见,我們先看下效果

@Inject
@JvmField
var record: Record? = null

@Inject
@JvmField
var record2: Record? = null

在MainActivity中聲明了兩個(gè)對(duì)象澳化,我們發(fā)現(xiàn)兩個(gè)對(duì)象的hashcode是一致的,說(shuō)明在當(dāng)前Activity中這個(gè)對(duì)象就是單例稳吮,但是跳轉(zhuǎn)到下一個(gè)Activity的時(shí)候缎谷,發(fā)現(xiàn)拿到的對(duì)象就是一個(gè)新的對(duì)象

2022-09-11 20:52:19.583 1860-1860/com.lay.image_process E/TAG: record 83544912record2 83544912
2022-09-11 20:53:11.071 1860-1860/com.lay.image_process E/TAG: record 163680212

也就是說(shuō),@ActivityScoped修飾的對(duì)象只是局部單例灶似,并不是全局的列林;那么如何才能拿到一個(gè)全局的單例呢?其實(shí)在之前的版本中酪惭,有一個(gè)ApplicationComponent希痴,其對(duì)應(yīng)的作用域@Singleton拿到的對(duì)象就是全局單例,后來(lái)Google給移除了春感,我覺得Google之所以移除砌创,可能就是推動(dòng)大家采用數(shù)據(jù)共享設(shè)計(jì)模式虏缸。

Component 作用域(局部單例)
ActivityComponent ActivityScoped
FragmentComponent FragmentScoped
ServiceComponent ServiceScoped
ViewComponent ViewScoped
ViewModelComponent ViewModelScoped

上面是整理的使用比較頻繁的Component,對(duì)應(yīng)的局部單例作用域

2.2 為接口注入實(shí)現(xiàn)類

首先創(chuàng)建一個(gè)接口

interface MyCallback {
    fun execute()
}

然后創(chuàng)建一個(gè)實(shí)現(xiàn)類嫩实,這里需要注意寇钉,構(gòu)造方法中如果需要傳入上下文,那么需要使用@ApplicationContext修飾舶赔,其他參數(shù)則不需要

class MyCallBackImpl : MyCallback {

    private var context: Context? = null

    @Inject
    constructor(@ApplicationContext context: Context) {
        this.context = context
    }

    override fun execute() {
        Toast.makeText(context, "實(shí)現(xiàn)了", Toast.LENGTH_SHORT).show()
    }

    fun clear() {
        if (context != null) {
            context = null
        }
    }
}

那么在創(chuàng)建module的時(shí)候,方法需要定義為抽象方法谦秧,而且需要使用@Binds來(lái)獲取實(shí)現(xiàn)類竟纳,方法同樣是抽象方法,參數(shù)為具體實(shí)現(xiàn)類疚鲤,返回值為接口锥累。

@Module
@InstallIn(ActivityComponent::class)
abstract class ImplModule {
    @Binds
    abstract fun getImpl(impl: MyCallBackImpl): MyCallback
}

那么在使用時(shí),就非常簡(jiǎn)單了集歇,這里獲取到的就是MyCallBackImpl實(shí)現(xiàn)類

@Inject
@JvmField
var callback: MyCallback? = null

那么大家想一個(gè)問題桶略,如果我有多個(gè)實(shí)現(xiàn)類,那么如何區(qū)分這個(gè)MyCallback到底是哪個(gè)實(shí)現(xiàn)類呢诲宇?同樣可以使用注解來(lái)區(qū)分

class MyCallbackImpl2 : MyCallback {

    private var context: Context? = null

    @Inject
    constructor(@ApplicationContext context: Context) {
        this.context = context
    }

    override fun execute() {
        Toast.makeText(context, "實(shí)現(xiàn)2", Toast.LENGTH_SHORT).show()
    }
}

這樣的話际歼,就有兩個(gè)抽象方法,分別返回MyCallbackImpl2和MyCallBackImpl兩個(gè)實(shí)現(xiàn)類姑蓝,那么在區(qū)分的時(shí)候鹅心,就可以通過(guò)@BindImpl和@BindImpl2兩個(gè)注解區(qū)分

@Module
@InstallIn(ActivityComponent::class)
abstract class ImplModule {
    @Binds
    @BindImpl
    abstract fun getImpl(impl: MyCallBackImpl): MyCallback

    @Binds
    @BindImpl2
    abstract fun getImpl2(impl2: MyCallbackImpl2): MyCallback
}

在調(diào)用時(shí),同樣需要使用@BindImpl2或者@BindImpl來(lái)區(qū)分獲取哪個(gè)實(shí)現(xiàn)類

@Inject
@JvmField
@BindImpl2
var callback: MyCallback? = null

其實(shí)@BindImpl注解很簡(jiǎn)單纺荧,就是通過(guò)@Qualifier注解來(lái)區(qū)分

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class BindImpl2 {
}

3 Hilt原理

大家看到在調(diào)用這個(gè)注入對(duì)象的時(shí)候旭愧,發(fā)現(xiàn)沒有看到對(duì)應(yīng)的入口,并沒有DaggerComponent那么顯眼宙暇,其實(shí)編譯時(shí)技術(shù)很多都是這樣输枯,是要從打包編譯文件夾去找


image.png

我們可以看到,hilt是自己?jiǎn)为?dú)的一個(gè)文件夾占贫,其中就有生成的資源文件

private MyCallbackImpl2 myCallbackImpl2() {
  return new MyCallbackImpl2(ApplicationContextModule_ProvideContextFactory.provideContext(singletonCImpl.applicationContextModule));
}

private MainActivity injectMainActivity3(MainActivity instance) {
  MainActivity_MembersInjector.injectRecord(instance, providerRecordProvider.get());
  MainActivity_MembersInjector.injectRecord2(instance, providerRecordProvider.get());
  MainActivity_MembersInjector.injectCallback(instance, myCallbackImpl2());
  return instance;
}

其實(shí)我們可以看到桃熄,這種實(shí)現(xiàn)方式跟Dagger2其實(shí)是一樣的,同樣都是在內(nèi)部初始化了某個(gè)類靶剑,例如MyCallbackImpl2蜻拨,其context是由ApplicationContextModule提供的

@InjectedFieldSignature("com.lay.image_process.MainActivity.callback")
@BindImpl2
public static void injectCallback(MainActivity instance, MyCallback callback) {
  instance.callback = callback;
}

調(diào)用injectCallback就是將MainActivity中的callback賦值,獲取的就是MyCallbackImpl2實(shí)現(xiàn)類桩引。

其實(shí)Hilt的內(nèi)部實(shí)現(xiàn)原理跟Dagger2是一樣缎讼,只是做了進(jìn)一步的封裝,所以如果理解了之前Dagger2的原理坑匠,相比Hilt也不在話下了血崭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子夹纫,更是在濱河造成了極大的恐慌咽瓷,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舰讹,死亡現(xiàn)場(chǎng)離奇詭異茅姜,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)月匣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門钻洒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锄开,你說(shuō)我怎么就攤上這事素标。” “怎么了萍悴?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵头遭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我癣诱,道長(zhǎng)计维,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任撕予,我火速辦了婚禮享潜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗅蔬。我一直安慰自己剑按,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布澜术。 她就那樣靜靜地躺著艺蝴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸟废。 梳的紋絲不亂的頭發(fā)上猜敢,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音盒延,去河邊找鬼缩擂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛添寺,可吹牛的內(nèi)容都是我干的胯盯。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼计露,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼博脑!你這毒婦竟也來(lái)了憎乙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤叉趣,失蹤者是張志新(化名)和其女友劉穎泞边,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗杉,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阵谚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了烟具。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椭蹄。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖净赴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情罩润,我是刑警寧澤玖翅,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站割以,受9級(jí)特大地震影響金度,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜严沥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一猜极、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧消玄,春花似錦跟伏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至兔跌,卻和暖如春勘高,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背坟桅。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工华望, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人仅乓。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓赖舟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親夸楣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子建蹄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容