在上一节中,咱们简略介绍了Dagger2的运用,其实咱们在运用Dagger2的时分,发现仍是比较繁琐的,要自己写Module、Component、Provides等等,于是Hilt的团队就和Dagger2的团队一起,规划了面向Android移动端的依靠注入框架 — Hilt

1 Hilt配置

在项目级的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的运用

首要按照常规,先写一个Module

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

假如是Dagger2的写法,需求再写一个Component,将RecordModule加载进去,那么Hilt就不要这一步,而是需求一个注解InstallIn来声明这个Module运用在哪个当地

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

在Hilt中有以下几个Component,我这儿拿几个典型说一下

Android进阶宝典 -- Hilt的运用

首要ApplicationComponent,它会存在整个App生命周期中,随着App的毁掉而毁掉,也就意味着,在App的任何方位都能够运用这个Module

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

像ActivityComponent,肯定便是存在于整个Activity生命周期中,随着Activity的毁掉而毁掉

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

其他的都类似,都是随着组件的生命周期结束而消逝。

例如咱们需求在MainActivity中注入某个类,那么需求运用@AndroidEntryPoint润饰,代表当时依靠注入的切入点

@AndroidEntryPoint
class MainActivity : AppCompatActivity()

同时,需求将当时app界说为Hilt App

@HiltAndroidApp
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

并且,在MainActivity中注入这个目标之后,也不需求像Dagger那样,去创建具体的Component目标

@Inject
@JvmField
var record:Record? = null

这样一看,Hilt是不是要比Dagger要简略许多了。

2.1 部分单例

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

假如咱们期望Record是一个单例目标,能够运用@ActivityScoped注解润饰,咱们先看下效果

@Inject
@JvmField
var record: Record? = null
@Inject
@JvmField
var record2: Record? = null

在MainActivity中声明了两个目标,咱们发现两个目标的hashcode是共同的,阐明在当时Activity中这个目标便是单例,可是跳转到下一个Activity的时分,发现拿到的目标便是一个新的目标

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

也便是说,@ActivityScoped润饰的目标仅仅部分单例,并不是大局的;那么怎么才能拿到一个大局的单例呢?其实在之前的版别中,有一个ApplicationComponent,其对应的效果域@Singleton拿到的目标便是大局单例,后来Google给移除了,我觉得Google之所以移除,可能便是推动咱们选用数据共享规划模式。

Component 效果域(部分单例)
ActivityComponent ActivityScoped
FragmentComponent FragmentScoped
ServiceComponent ServiceScoped
ViewComponent ViewScoped
ViewModelComponent ViewModelScoped

上面是收拾的运用比较频繁的Component,对应的部分单例效果域

2.2 为接口注入完成类

首要创建一个接口

interface MyCallback {
    fun execute()
}

然后创建一个完成类,这儿需求留意,构造办法中假如需求传入上下文,那么需求运用@ApplicationContext润饰,其他参数则不需求

class MyCallBackImpl : MyCallback {
    private var context: Context? = null
    @Inject
    constructor(@ApplicationContext context: Context) {
        this.context = context
    }
    override fun execute() {
        Toast.makeText(context, "完成了", Toast.LENGTH_SHORT).show()
    }
    fun clear() {
        if (context != null) {
            context = null
        }
    }
}

那么在创建module的时分,办法需求界说为笼统办法,并且需求运用@Binds来获取完成类,办法相同是笼统办法,参数为具体完成类,回来值为接口。

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

那么在运用时,就非常简略了,这儿获取到的便是MyCallBackImpl完成类

@Inject
@JvmField
var callback: MyCallback? = null

那么咱们想一个问题,假如我有多个完成类,那么怎么区别这个MyCallback到底是哪个完成类呢?相同能够运用注解来区别

class MyCallbackImpl2 : MyCallback {
    private var context: Context? = null
    @Inject
    constructor(@ApplicationContext context: Context) {
        this.context = context
    }
    override fun execute() {
        Toast.makeText(context, "完成2", Toast.LENGTH_SHORT).show()
    }
}

这样的话,就有两个笼统办法,别离回来MyCallbackImpl2和MyCallBackImpl两个完成类,那么在区别的时分,就能够经过@BindImpl和@BindImpl2两个注解区别

@Module
@InstallIn(ActivityComponent::class)
abstract class ImplModule {
    @Binds
    @BindImpl
    abstract fun getImpl(impl: MyCallBackImpl): MyCallback
    @Binds
    @BindImpl2
    abstract fun getImpl2(impl2: MyCallbackImpl2): MyCallback
}

在调用时,相同需求运用@BindImpl2或者@BindImpl来区别获取哪个完成类

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

其实@BindImpl注解很简略,便是经过@Qualifier注解来区别

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

3 Hilt原理

咱们看到在调用这个注入目标的时分,发现没有看到对应的进口,并没有DaggerComponent那么显眼,其实编译时技术许多都是这样,是要从打包编译文件夹去找

Android进阶宝典 -- Hilt的运用

咱们能够看到,hilt是自己独自的一个文件夹,其间就有生成的资源文件

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;
}

其实咱们能够看到,这种完成方式跟Dagger2其实是相同的,相同都是在内部初始化了某个类,例如MyCallbackImpl2,其context是由ApplicationContextModule供给的

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

调用injectCallback便是将MainActivity中的callback赋值,获取的便是MyCallbackImpl2完成类。

其实Hilt的内部完成原理跟Dagger2是相同,仅仅做了进一步的封装,所以假如理解了之前Dagger2的原理,比较Hilt也不在话下了。