在上一節(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ō)一下

首先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ù)很多都是這樣,是要從打包編譯文件夾去找

我們可以看到,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à)下了。