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

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

引入依賴(lài)

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

2 Hilt的使用

首先按照慣例,先寫(xiě)一個(gè)Module

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

如果是Dagger2的寫(xiě)法,需要再寫(xiě)一個(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的銷(xiāo)毀而銷(xiāo)毀,也就意味著,在App的任何位置都可以使用這個(gè)Module

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

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

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

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

例如我們需要在MainActivity中注入某個(gè)類(lèi),那么需要使用@AndroidEntryPoint修飾,代表當(dāng)前依賴(lài)注入的切入點(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給移除了,我覺(jué)得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)類(lèi)

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

interface MyCallback {
    fun execute()
}

然后創(chuàng)建一個(gè)實(shí)現(xiàn)類(lèi),這里需要注意,構(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)類(lèi),方法同樣是抽象方法,參數(shù)為具體實(shí)現(xiàn)類(lèi),返回值為接口。

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

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

@Inject
@JvmField
var callback: MyCallback? = null

那么大家想一個(gè)問(wèn)題,如果我有多個(gè)實(shí)現(xiàn)類(lèi),那么如何區(qū)分這個(gè)MyCallback到底是哪個(gè)實(shí)現(xiàn)類(lèi)呢?同樣可以使用注解來(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()
    }
}

這樣的話(huà),就有兩個(gè)抽象方法,分別返回MyCallbackImpl2和MyCallBackImpl兩個(gè)實(shí)現(xiàn)類(lèi),那么在區(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)類(lèi)

@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)沒(méi)有看到對(duì)應(yīng)的入口,并沒(méi)有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è)類(lèi),例如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)類(lèi)。

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容