Hilt简介
Hilt是对Dagger2的二次封装,比Dagger2更好用
不同的是,Dagger利用注解使用APT去生成辅助代码,而Hilt是利用注解使用APT和ASM(字节码插桩)去生成辅助代码。
Hilt的原理就是 APT注解处理器+ ASM 字节码插庄
APT简单概述 :APT是在源代码编译时期扫描处理注解,生成或修改文件;
ASM简单概述 :ASM则直接操作已经编译好的字节码(.class文件) 改变类的行为,
ASM允许开发者在运行时动态地修改类的行为,通过插入、修改或删除字节码来改变类的功能。ASM提供了丰富的API来遍历、分析和转换类的字节码,使得开发者能够实现复杂的字节码操作。
Hilt会通过APT对依赖注入的作用类生成一个“Hilt_类名.java”类,
在 Hilt_XX 类的 onCreate 之类的方法中提供了变量初始化的入口,所有被注解的变量从这里开始一层一层被全部实例化的
比如说 MainActivity 在编译的时候就会 生成一个叫做 Hilt_MainActivity,
MainActivity 会继承 Hilt_MainActivity 类,后续
依赖注入对象实例化的工作交给Hilt_MainActivity 这个类去实现,
包括其中的inject注入
通过字节码插桩,在生成类中添加以下方法,从而实现依赖的注入。
这一步其实就 在 Dagger2 我们需要写的注入操作,但是在Hilt中做了封装,所以我们可以不需要做这一步操作
protected void inject() {
if (!injected) {
injected = true;
((MainActivity_GeneratedInjector) this.generatedComponent()).injectMainActivity(UnsafeCasts.<MainActivity>unsafeCast(this));
}
}
使用Hilt需要配置如下
- 在整个项目的根build.gradle文件加上
plugins {
...
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
- 在module的build.gradle 文件加上
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
android {
...
}
dependencies {
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
}
// Allow references to generated code
kapt {
correctErrorTypes true
}
Hilt的使用流程
也就是,2个流程
第一 把类对象通过注解规则提供给Hilt
第二 带有@HiltAndroidApp/@AndroidEntryPoint/@HiltViewModel注解的Android类 通过注解 @Inject 拿着Hilt中的类对象
也就是 带有@HiltAndroidApp/@AndroidEntryPoint/@HiltViewModel注解的Android类
它都不需要去实例化一个类对象,而是 直接通过 @Inject就可以从Hilt里拿到类对象。
目前支持类:Activity/Fragment/Application/ViewModelView/Service/BroadcastReceiver
Application(使用 @HiltAndroidApp)
ViewModel(使用 @HiltViewModel)
其他都是使用 @AndroidEntryPoint
那除了这些类呢,其他类如果想以这种形式拿到类对象如何做呢,那就还是老老实实用Dagger2的写法去使用,而不是用Hilt
Hilt的使用
- 项目中必须先自定义MyApplication,并用 @HiltAndroidApp注解标记
@HiltAndroidApp //需要自定义MyApplication并且标明此注解
class MyApplication :Application() {
override fun onCreate() {
super.onCreate()
}
}
然后在androidmanifest 文件上指定这个
android:name=".MyApplication"
- 想要拿到Hilt中的类对象的Android类必须使用注解 @AndroidEntryPoint/@HiltAndroidApp/@HiltViewModel
并且通过@Inject拿到Hilt的类实例对象
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//这里analytics在编译的时期就是不为null的也就是在编译时期就为从Hilt中拿到对象赋值
@Inject lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
目前支持Android类:Activity/Fragment/Application/ViewModelView/Service/BroadcastReceiver
Application(使用 @HiltAndroidApp)
ViewModel(使用 @HiltViewMode)
其他都是使用 @AndroidEntryPoint
由 Hilt 注入的字段不能为私有字段。尝试使用 Hilt 注入私有字段会导致编译错误,也就是在Android类中从Hilt中获取的对象不能是private,也就是上面的 analytics不能写private
- 想要成为Hilt中的类对象并且提供给Android类使用就必须遵循 成为Hilt类对象的 规则
- 当这个类就是一个普通类,是由你自己本地项目建立的类,也就是它不属于第三方库的类,那么可以通过在类的主构造函数上加上注解 @Inject 就代表你把这个类的实例对象提供给Hilt了,正是因为提供给了Hilt,那么那些带注解的Android类就可以通过注解直接到类实例对象
//InstallIn表明类对象提供到Hilt中后,类对象只能够activity使用,根据情况写此注解,如果不写,则没有限定
@InstallIn(ActivityComponent::class)
class AnalyticsAdapter @Inject constructor(){
fun test(){
Log.d("zjs", "test: ")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
analytics.test()
}
}
- 当创建的这个类对象它依赖于其他的类对象,那么其他的类也需要加入到Hilt当中也就是在类构造函数中加上 @Inject
//比如这里,从构造参数中看到,创建一个 AnalyticsAdapter类对象,必须有一个 AnalyticsService类对象,
class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService){
fun test(){
Log.d("zjs", "test: ")
}
}
//那么AnalyticsService也必须放到Hilt中去,也就是构造函数也要加上注解 @Inject
class AnalyticsService @Inject constructor(){
}
这样后续才可以继续通过 @Inject 获取 AnalyticsAdapter 实例
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var analytics: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
analytics.test()
}
}
这里再举个例子
比如这里 MainViewModule 类,构建他需要有个参数 repository:MyRepository,
@HiltViewModel
class MainViewModule @Inject constructor(var repository:MyRepository, application: Application):AndroidViewModel(application){
}
所以想用从
Hilt中获取 MainViewModule ,就必须也要把 var repository:MyRepository, 注入到Hilt中
class MyRepository @Inject constructor(@ApplicationContext val context: Context) {
}
这样就可以在activity 中从Hilt中获取 MainViewModule
@AndroidEntryPoint
class MainActivity: AppCompatActivity() {
val viewmodel by viewModels<MainViewModule >()//这个是注入获取的方式
- 当一个类他不是你本地创建的,而是属于第三方库的类,比如Handle, HandlerThread
那么你就肯定没办法通过a的形式,也就是没办法通过在类的构造函数添加@Inject的方式去把类对象添加到Hilt,因为代码是第三方库的,你怎么加?。这个时候就需要自定义一个Module,
并且用@Provides注解 提供一个 Handle,HandlerThread 等实例给Hilt
@InstallIn(ActivityComponent::class)//InstallIn表明这个Module里面的类对象提供到Hilt中后,这些类对象只能够activity使用,不写则没有限定
@Module //此注解表明此类是Module
class HandlerModule {
@Provides // 通过注解@Provides提供类对象到Hilt
fun getHandler(): Handler {
return Handler()
}
@Provides // 通过注解@Provides提供类对象到Hilt
fun getHandlerThread(
): HandlerThread {
return HandlerThread("zjs")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//可以从Hilt中直接拿 Handler 类对象实例
@Inject lateinit var handler: Handler
//可以从Hilt中直接拿 HandlerThread类对象实例
@Inject lateinit var handleThread: HandlerThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler.looper
handleThread.name
}
}
- 当你想要放入Hilt的类是一个接口的实现类,那么不仅需要 在该接口实现类构造函数上加@Inject
而且需要自定义一个Module的形式,以及用@Binds表示,接口与接口实现类之间的绑定,也就是表示使用该接口,就是调用当前接口实现类
并且这个Module必须是抽象类,里面必须有个抽象方法, 抽象方法 参数是实现类,函数返回是 接口类
比如下面有个接口实现类 AnalyticsServiceImpl ,你想要把它放到Hilt中给Android类使用
interface AnalyticsService{
fun analyticsMethods()
}
//接口实现类构造函数也要加上@Inject
class AnalyticsServiceImpl @Inject constructor() :AnalyticsService {
override fun analyticsMethods() {
Log.d("zjs", "analyticsMethods: ")
}
}
@InstallIn(ActivityComponent::class)
//需要自定义一个Module 并且这个Module必须是抽象类
@Module
abstract class AnalyticsModule {
//里面有抽象方法。并且用注解@Binds标记 让 接口 AnalyticsService 与 接口实现类 AnalyticsServiceImpl 进行绑定
//抽象函数 参数是 接口实现类,函数返回是 接口类
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
}
//那么在后续Android类使用的时候就是
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//可以从Hilt中直接拿接口实现类 analyticsServiceImpl去使用
@Inject lateinit var analyticsServiceImpl: AnalyticsServiceImpl
//也可以从Hilt中直接拿 接口analyticsService去使用,而因为绑定关系当前 analyticsService 的实现类就是 AnalyticsServiceImpl
@Inject lateinit var analyticsService: AnalyticsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
analyticsServiceImpl.analyticsMethods()
analyticsService.analyticsMethods()
}
}
ViewModel中的使用特殊
前面说了 带有@HiltAndroidApp/@AndroidEntryPoint/@HiltViewModel注解的Android类 通过注解 @Inject 拿着Hilt中的类对象**
但是在 ViewModel 中, 如果想要通过@Inject 的方法获取Hilt中的对象,
不仅需要 在ViewModel中加上 对应的 @HiltViewModel 而且还要在 构造方法中要加一个@Inject标签,才可以 通过@Inject 的方法获取Hilt中的对象不然会报错
@HiltViewModel
class MainViewModule @Inject constructor( application: Application):AndroidViewModel(application){
@Inject
lateinit var repository:MyRepository
}
并且,你不可以在activity 中想通过 @Inject的方式来获取 一个 ViewModule 实例,这种获取的方式会将ViewModel变成一个普通的类,会失去它的生命周期, 因为 ViewModel官方已经给我们注入过了,所以我们可以用便捷的方式来获取ViewModel 实例 对应的操作如下:
@AndroidEntryPoint
class MainActivity: AppCompatActivity() {
// @Inject
// lateinit var viewmodel: MyViewModel 错误的获取方式
// val viewmodel: MyViewModel by lazy {
// ViewModelProvider(this).get(MyViewModel::class.java)
// }//这个是老的方式
val viewmodel : MainViewModule by viewModels()//这个是注入获取的方式
当然了,用便捷的方式获取的话还需要另外2个依赖包
implementation "androidx.activity:activity-ktx:1.2.3"//在activity中获取
implementation 'androidx.fragment:fragment-ktx:1.3.4'//在fragment中获取
Hilt 组件
上面在为Hilt 提供对象的时候添加的注解 @InstallIn()括号内就是写着Hilt 组件
ActivityComponent就叫做Hilt组件, 代表提供给Hilt的类对象只能给activity使用,
不同Android类会对应不同过的Hilt组件,
比如写@InstallIn(FragmentComponent::class)代表提供给Hilt的类对象只能够fragment使用
也就是下面的Android类都会一个对应的Hilt组件
这里面 没有广播BroadcastReceiver,它采用的Hilt组件是跟Application一样都是SingletonComponent
也就是 使用 @InstallIn( SingletonComponent.class ) 的范围是Application类和广播类
Hilt 会按照相应 Android 类的生命周期自动创建和销毁生成的这些Hilt组件的实例
也就是说,
当使用Hilt框架时,通过@InstallIn注解来指定Hilt组件,比如ActivityComponent。这意味着在该组件中提供的类对象只能在Activity中使用,并且Hilt会根据Activity的生命周期自动创建和销毁这些组件的实例。
并且
对于被@Inject标记的对象来说,它们会根据其所属的Hilt组件的生命周期进行创建和销毁。
也就是说 当Activity被创建时,Hilt会自动创建ActivityComponent的实例,而在 Activity 中使用 @Inject标记的对象也会被创建
当Activity被销毁时,组件ActivityComponent的实例 和 @Inject标记的对象也会被销毁。
这种机制可以帮助管理对象的生命周期,并确保它们在正确的时机被创建和销毁,从而避免内存泄漏和资源浪费。 Hilt的依赖注入机制可以帮助简化Android应用的开发,并提高代码的可维护性
单例
还有相对应的作用域注解,这些注解更多用于单例使用
也就说,当不使用这些这些作用域的时候,默认情况下,在Android类中通过 @Inject从Hilt获取的对象都是一个个新的实例。
而你在为给Hitl提供对象的时候,多添加了上面的作用域注解,那么在在对应的类下,通过 @Inject从Hilt获取的对象都是同一个实例,
比如说
class AnalyticsAdapter @Inject constructor( private val service: AnalyticsService){
fun test(){
Log.d("zjs", "test: ")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var analyticsAdapter: AnalyticsAdapter
@Inject
lateinit var analyticsAdapter2: AnalyticsAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
这里 analyticsAdapter 和 analyticsAdapter2 他们是不同的实例对象。
不管这里是同一个activity 下,还是不同activity ,创建的实例都是不同的
但是当你在为给Hitl提供对象的类加上作用域的时候,比如这里加上了 @ActivityScoped
@ActivityScoped
class AnalyticsAdapter @Inject constructor(){
fun test(){
Log.d("zjs", "test: ")
}
}
这个 @ActivityScoped 的作用域是activiy ,也就是在整个项目中,所有activity 中通过
@Inject获取的对象都是一个实例
但是注意这只是限定在所有的activity,也就是只面向所有activity类拿这个对象是同一个实例。
AnalyticsAdapter 并没有限定它只能Installin在 activity,它依旧可以使用在其他Android类。
在其他Android类比如说Fragment,在Fragment来说 @Inject 定义两个 analyticsAdapter 是不同的两个对象实例
而如果希望对象在整个项目都单例的话就是,也就是在不管 activity 或 fragment等类中
@Inject 中定义的类都是一个实例,那么用的作用域就是 对应上面的表格 的 @Singleton
@Singleton
class AnalyticsAdapter @Inject constructor(){
fun test(){
Log.d("zjs", "test: ")
}
}
Hilt 自带的 限定符注解标签
Hilt 提供了一些预定义的限定符。
我们知道context 有分为application ,activity等
当你定义个 context:Context 。
可通过 @ApplicationContext 和 @ActivityContext 限定符 代表当前的context是 ApplicationContext 还是activity的
@ActivityContext private val context: Context,
@Application private val context: Context,
如果想在一些类中获取到Context,用的是 @ApplicationContext
也就是
class MyRepository @Inject constructor(@ApplicationContext val context: Context) {
}
自定义限定符
我们知道,一个接口,它会有很多个实现类,而像上面 的接口 AnalyticsService 它只有一个类 AnalyticsServiceImpl ,
并且它通过在Module 中通过@Binds注解 ,让 AnalyticsService 与 AnalyticsServiceImpl 绑定了一起,并且放到了Hilt中,
所以在Android类中 ,
写接口类型的时候 @Inject lateinit var analyticsService: AnalyticsService 就会知道
当前的 字段变量 analyticsService 的实现类是 AnalyticsServiceImpl 类。
那么如果说 AnalyticsService 还有其他多个实现类 AnalyticsServiceImpl1 AnalyticsServiceImpl2 ,他们通通跟AnalyticsService这个接口
绑定在一起,都放到Hilt中,那你在Android 类 写字段 @Inject lateinit var analyticsService: AnalyticsService 的时候,到底是希望这个 analyticsService 字段的具体实现是 AnalyticsServiceImpl1 还是 AnalyticsServiceImpl2
这个时候就需要用到自定义限定符
比如说这里的有个接口 AnalyticsService ,它有3个实现类 AnalyticsServiceImpl AnalyticsServiceImpl1 AnalyticsServiceImpl2
interface AnalyticsService{
fun analyticsMethods()
}
class AnalyticsServiceImpl @Inject constructor() :AnalyticsService {
override fun analyticsMethods() {
Log.d("zjs", "analyticsMethods: ")
}
}
class AnalyticsServiceImpl1 @Inject constructor() :AnalyticsService {
override fun analyticsMethods() {
Log.d("zjs", "analyticsMethods: 1")
}
}
class AnalyticsServiceImpl2 @Inject constructor() :AnalyticsService {
override fun analyticsMethods() {
Log.d("zjs", "analyticsMethods:2 ")
}
}
并且要把这3个接口实现类都提供到Hilt中去,所以自定义一个抽象类 Module,并且通过 @Binds注解让
AnalyticsService与AnalyticsServiceImpl绑定,
AnalyticsService与AnalyticsServiceImpl1绑定,
AnalyticsService与AnalyticsServiceImpl2绑定
以及AnalyticsServiceImpl AnalyticsServiceImpl1 AnalyticsServiceImpl2放入到Hilt中
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl1: AnalyticsServiceImpl1
): AnalyticsService
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImp2: AnalyticsServiceImpl2
): AnalyticsService
}
那在Android 类 写字段 @Inject lateinit var analyticsService: AnalyticsService 的时候 ,希望这个 analyticsService 字段的具体实现是 AnalyticsServiceImpl AnalyticsServiceImpl1 还是 AnalyticsServiceImpl2,是根据我们的标签来决定的,也就是我们的新定义的限定符来决定的,也就是我们需要新定义3个新的注解
// Qualifier 这个注解是给Hilt用的,让Hilt 能够根据不同下面定义的注解,使用不同的绑定接口实现类
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AnalyticsServiceImplAnnotation()
// Qualifier 这个注解是给Hilt用的,让Hilt 能够根据不同下面定义的注解,使用不同的绑定接口实现类
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AnalyticsServiceImplAnnotation1()
// Qualifier 这个注解是给Hilt用的,让Hilt 能够根据不同下面定义的注解,使用不同的绑定接口实现类
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AnalyticsServiceImplAnnotation2()
定义3个新的注解后,我们就可以对刚才在module类中,通过 @Binds接口个不同实现接口实现类的时候去定义他们,也就是告诉Hilt
告诉Hilt,使用哪个注解的就是当前AnalysisService接口的是具体实现类是哪个
@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {
@AnalyticsServiceImplAnnotation
@Binds
abstract fun bindAnalyticsService(
analyticsServiceImpl: AnalyticsServiceImpl
): AnalyticsService
@AnalyticsServiceImplAnnotation1
@Binds
abstract fun bindAnalyticsService1(
analyticsServiceImpl1: AnalyticsServiceImpl1
): AnalyticsService
@AnalyticsServiceImplAnnotation2
@Binds
abstract fun bindAnalyticsService2(
analyticsServiceImp2: AnalyticsServiceImpl2
): AnalyticsService
}
最后我们就可以在Android类中 通过刚定义的3个注解来区分,我们要从Hilt中获取的接口具体实现类对象是哪个
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//不同的注解,接口的实现类就不同
@AnalyticsServiceImplAnnotation
@Inject
lateinit var analyticsService: AnalyticsService
@AnalyticsServiceImplAnnotation1
@Inject
lateinit var analyticsService1: AnalyticsService
@AnalyticsServiceImplAnnotation2
@Inject
lateinit var analyticsService2: AnalyticsService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
analyticsService.analyticsMethods()
analyticsService1.analyticsMethods()
analyticsService2.analyticsMethods()
}
}
不仅是上面那种使用场景,其实类似的,当需要提供相同的类对象设置不同的接口实现类的时候也可也采用这种操作
比如说
不同的 OkHttpClient对象它可以设置不同拦截器 Interceptor,
当前有两个自定义拦截器 AuthInterceptor ,OtherInterceptor。他们都是 接口 Interceptor的实现类。
这个时候提供给Hilt的OkHttpClient对象需要 有的使用 AuthInterceptor 有的使用 OtherInterceptor
根据上面的提供给Hilt对象的规则,当类是第三的时候需要自定义Module,并且使用注解@Provides把类对象提供给Hilt
@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
这个时候提供给Hilt的OkHttpClient就有两种 一个是带authInterceptor的一种是带 otherInterceptor
那么同样的,在Android类使用的时候,该如何区分从Hilt中拿到的 OkHttpClient是 带authInterceptor的还是带 otherInterceptor的
这个时候依旧是自定义两个新的注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
并且注解定义在Module中的不同OkHttpClient对象,代表什么注解代表什么OkHttpClient对象
@Module
@InstallIn(SingletonComponent::class)
class NetworkModule {
@AuthInterceptorOkHttpClient
@Provides
fun provideAuthInterceptorOkHttpClient(
authInterceptor: AuthInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(authInterceptor)
.build()
}
@OtherInterceptorOkHttpClient
@Provides
fun provideOtherInterceptorOkHttpClient(
otherInterceptor: OtherInterceptor
): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(otherInterceptor)
.build()
}
}
最后在Android类中使用 依旧 是 通过注解去标记当前的字段是属于哪种 OkHttpClient对象
//不同的注解接口的实现类就不同
@AuthInterceptorOkHttpClient
@Inject
lateinit var okHttpClentAuth: OkHttpClient
@OtherInterceptorOkHttpClient
@Inject
lateinit var okHttpClentOther: OkHttpClient
所以说白了,限定符就是一个注解标签,注解定义在哪里,就代表着谁,注解使用哪里,就代表着使用谁