什么是IOC
IOC是Inversion of Control的缩写,翻译为控制反转,是面向对象编程中的一种设计原则,可以用来降低代码之间的耦合度。
因此IOC中最常见的方式叫做依赖注入(Dependency Injection,简称DI),所谓依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。
类里面声明的变量叫做依赖,通常依赖需要自己创建对象,自己进行管理。依赖注入,就是IOC容器在运行期间,让外部帮你初始化你的依赖,然后注入到你声明的变量上。
让外部初始就算依赖注入,那么工厂模式,Builder模式也是依赖注入。那么Hilt就是使用了注解的方式,让依赖注入变得更方便。
为什么要用依赖注入
如果一个对象需要被共享,或者可能在多个类中被使用,为了解耦,你就可以使用以来注入来初始化它。
Hilt 是什么
Hilt 是 Android 的依赖注入库,其实是基于 Dagger 。可以说 Hilt 是专门为 Andorid 打造的。
Hilt 创建了一组标准的 组件和作用域。这些组件会自动集成到 Android 程序中的生命周期中。在使用的时候可以指定使用的范围,事情作用在对应的生命周期当中。
Hilt支持的Android组件
引入 Hilt
- 在项目的build.gradle中添加引用
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.42'
}
}
- 在App的build.gradle中添加plugin的引用和依赖:
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
dependencies {
implementation "com.google.dagger:hilt-android:2.42"
kapt "com.google.dagger:hilt-android-compiler:2.42"
}
常用注解
Hilt常用注解主要是:@HiltAndroidApp、@AndroidEntryPoint、@Inject、@Module、@InstallIn、@Binds、@Provides、@EntryPoint 等等。
- Hlit初始化
所有使用Hilt的项目都需要包含一个带有@HiltAndroidApp注释的Application类.
@HiltAndroidApp会触发Hilt代码生成,生成的代码包含应用的一个基类,该基类充当应用级依赖项容器。
@HiltAndroidApp
class MyApplication: Application() {
override fun onCreate() {
super.onCreate()
}
}
只有 Application 这个入口点是使用 @HiltAndroidApp 注解来声明的。其他的所有入口点,都是用 @AndroidEntryPoint 注解来声明的。
Hilt为何要增加@HiltAndroidApp注解
1:将ApplicationContextModule添加至应用组件中,获取应用组件applicationContext
2:自动实现了依赖注入,免去了类似Dagger的手动调用
将依赖项注入Android类 @AndroidEntryPoint
Hilt目前支持以下Android类:
Application(通过使用 @HiltAndroidApp)
Activity
Fragment
View
Service
BroadcastReceiver
为什么需要在使用依赖注入的组件上添加AndroidEntryPoint注解?因为Hilt需要找到使用了该注解的类,自动找到合适的位置,比如反射activity的oncreate方法中,进行初始化方法调用。
@Inject
如需从组件获取依赖项,可以使用@Inject注释执行字段注入。
class Truck @Inject constructor(val driver: Driver) {
fun deliver() {
Log.i("minfo", "Truck is delivering cargo driver by $driver")
}
}
class Driver @Inject constructor() {
}
MainActivity中使用注入truck对象,并调用deliver()方法。
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var truck: Truck
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
truck.deliver()
}
}
Hilt模块 @Module
有时,类型不能通过构造函数注入。例如,您不能通过构造函数注入接口。此外,您也不能通过构造函数注入不归您所有的类型,如来自外部库的类。在这些情况下,您可以使用Hilt @Module向Hilt提供绑定信息。比如我们常用于创建依赖类的对象(例如第三方库OkHttp、Retrofit等等),使用@Module注解的类,需要使用@InstallIn注解指定module的范围。
Hilt内置组件和组件作用域
InstallIn,就是安装到的意思。那么@InstallIn(ActivityComponent::class),就是把这个模块安装到Activity组件当中。
Hilt一共内置了7种组件类型,分别用于注入到不同的场景,如下表所示。
@Singleton
Hilt会为每次的依赖注入行为都创建不同的实例。这种默认行为在很多时候确实是非常不合理的,比如我们提供的Retrofit和OkHttpClient的实例,理论上它们全局只需要一份就可以了,每次都创建不同的实例明显是一种不必要的浪费。
如后面写到的NetworkModule内部创建OkHttpClient、Retrofit使用了注解@Singleton。
@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {
}
@Binds 注入接口实例
接口是无法通过构造方法进行注入的,需要通过在Hilt 模块内创建一个带有@Binds注释的抽象函数进行注入。@Binds 注释会告知Hilt在需要提供接口的实例时要使用哪种实现。
示例:
//需要注入接口
interface Engine {
fun start()
fun shutdown()
}
//接口实现类
class GasEngine @Inject constructor() : Engine {
override fun start() {
Log.i("minfo", "GasEngine start")
}
override fun shutdown() {
Log.i("minfo", "GasEngine shotdown")
}
}
@Module
@InstallIn(ActivityComponent::class)
abstract class EnginModule {
@Binds
abstract fun bindEngin(gasEngine: GasEngine): Engine //指定子类和实现类
//@Binds 注释会告知Hilt在需要提供接口的实例时要使用哪种实现。
//@Binds告知 需要提供 Engin接口的实例是有该抽象方法的参数类型对象提供,所以GasEngine需要实现Engine接口
}
在Truck类中注入engine接口:
class Truck @Inject constructor(val driver: Driver) {
@Inject
lateinit var engine: Engine
fun deliver() {
engine.start()
Log.i("minfo", "Truck is delivering cargo driver by $driver")
engine.shutdown()
}
}
@Provides 注入实例
第三方库如:Retrofit、OkHttpClient 或 Room数据库等都是无法通过构造函数注入,我们去改不了三方库的代码,在里面加入@Inject,这时就需要使用@Provides注入实例,在Provides注解的方法中,给出具体的实现代码。
@Module
@InstallIn(SingletonComponent::class)
// 把NetworkModule绑定到HiltApp 的生命周期。
object NetworkModule {
@Provides
@Singleton
fun getOkhttpClient() : OkHttpClient {
return OkHttpClient.Builder().build()
}
@Provides
@Singleton
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://www.wanandroid.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
}
@Provides
@Singleton
fun getService(): ApiService {
return createRetrofit(getOkhttpClient()).create(ApiService::class.java)
}
}
如上方式,类上添加@InstallIn(SingletonComponent::class) 以及提供对象添加@Singleton注解,就是提供单例依赖注入的写法。如果需要每次使用初始化,就去掉这两个注解。
其他生命周期,@InstallIn可将注解改为:ActivityComponent/FragmentComponent/ViewComponent/ActivityRetainedComponent的生命周期。
使用注入对象
直接在activity中进行注入:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var truck: Truck
@Inject
lateinit var apiService: ApiService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
truck.deliver()
}
}
Hilt 中的预定义限定符
如果有个我们想要依赖注入的类,它又是依赖于Context的,这个情况要如何解决呢?
Hilt提供了一些预定义的限定符。例如,由于我们可能需要来自应用或Activity的Context类,因此Hilt提供了@ApplicationContext和 @ActivityContext 限定符。
@Singleton
class Driver @Inject constructor(val context: Context) {
}
现在你编译一下项目一定会报错,原因也很简单,Driver类无法被依赖注入了,因为Hilt不知道要如何提供Context这个参数。其实只需要在Context参数前加上一个@ApplicationContext注解,代码就能编译通过了。
@Singleton
class Driver @Inject constructor(@ApplicationContext val context: Context) {
}
@Singleton
class Driver @Inject constructor(@ActivityContext val context: Context) {
}
Hilt在ViewModel的使用
@ActivityRetainedScoped
class MyViewModel @Inject constructor() : ViewModel() {
fun loadData() {
viewModelScope.launch {
}
}
}
在activity中使用viewmodel
@Inject
lateinit var viewModel: MyViewModel
@EntryPoint注解
Hilt支持最常见的Android类Application、Activity、Fragment、View、Service、BroadcastReceiver 等等,但是我们可能需要在Hilt 不支持的类中执行依赖注入,在这种情况下可以使用@EntryPoint注解进行创建,Hilt会提供相应的依赖。
原理篇
@HiltAndroidApp注解做了什么事
首先会创建一个包含很多接口和抽象类的 java 文件 MyApplication_HiltComponents,用来规范好所有的类关系和行为。
然后会为 Application 创建一个注射器 MyApplication_GeneratedInjector,负责将这个 Application 注入。
然后再创建一个 Application 的父类 Hilt_Application,用来替换掉源代码的继承关系,以此实现将 Hilt 插入到正常代码逻辑中,实现 Hilt 的功能。原有application对象的父类就变成了Hilt_Application。
@AndroidEntryPoint注解了MainActivity之后Hilt帮我们做了什么?
1.Hilt自动帮我们生成了一个继承自AppCompatActivity的名称为Hilt_MainActivity.java类。
然后让原本的Mainactivity继承于Hilt_MainActivity,Hilt_MainActivity会在onCreate方法中调用自己的inject方法进行注入,将声明依赖进行赋值。
Hilt_MainActivity类中:
protected void onCreate(@Nullable Bundle savedInstanceState) {
inject();
super.onCreate(savedInstanceState);
}
- 对使用了@Inject注解的依赖,生成了XXX_Factory类,类中是对该对象的创建方法。
参考:
https://juejin.cn/post/7111986248603926541
https://blog.csdn.net/guolin_blog/article/details/109787732
https://www.bilibili.com/video/BV1nK4y1v7u9/
https://blog.csdn.net/xx23x/article/details/121636223
Github参考demo:
https://github.com/running-libo/HiltUse/tree/master
https://github.com/running-libo/MviFlowHilt