在上一節(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ō)一下
首先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ù)很多都是這樣输枯,是要從打包編譯文件夾去找
我們可以看到,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也不在話下了血崭。