依赖注入(DI),旨在为代码解耦,谷歌基于Dagger1开发出了Dagger2,然后专门为Android量身打造了Hilt,Hilt最明显的特征就是:1. 简单。2. 提供了Android专属的API。
1、引入
//根build.gradle文件
plugins {
id 'com.google.dagger.hilt.android' version '2.44' apply false
}
//app的build.gradle文件
plugins {
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}
//开启java8特性
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
//导入依赖
dependencies {
...
implementation "com.google.dagger:hilt-android:2.44"
kapt "com.google.dagger:hilt-compiler:2.44"
...
}
2、Hilt入口点
Hilt一共支持6个入口点
入口点 | 对应注解 |
---|---|
Application | @HiltAndroidApp |
Activity | @AndroidEntryPoint |
Fragment | @AndroidEntryPoint |
View | @AndroidEntryPoint |
Service | @AndroidEntryPoint |
BroadcastReceive | @AndroidEntryPoint |
除了Application的入口点对应注解为@HiltAndroidApp,其他五大入口点的对应注解均为@AndroidEntryPoint
使用示例
@HiltAndroidApp
class MyApplication : Application() {
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
3、无参构类造的使用
//在无参构造类添加注解@Inject,表明该类被Hilt托管,由Hilt来实例化
class Truck @Inject constructor() {
fun deliver() {
println("Truck is delivering cargo.")
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
//然后在其他类中就可以使用了,这里先声明一个延迟Trunk变量,然后在上面添加一个@Inject表示由Hilt来实例化
@Inject
lateinit var truck: Truck
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//代码中就可以正常调用Trunk类中的方法
truck.deliver()
}
}
4、有参构造类的使用
//可以看到我们这里的Trunk类中构造方法有了一个Driver类型的参数
class Truck @Inject constructor(val driver: Driver) {
fun deliver() {
println("Truck is delivering cargo. Driven by $driver")
}
}
//所以我们这里需要在Driver类中添加一个@Inject注解,表示Driver类被Hilt托管,由Hilt来实例化
class Driver @Inject constructor() {
}
//然后Trunk就可以就跟前面的无参构造一样使用了
5、接口依赖注入
//我们这里定义了一个Engine接口,并且其中有两个方法
interface Engine {
fun start()
fun shutdown()
}
//然后定义了两个类GasEngine和ElectricEngine分别来实现Engine接口
class GasEngine @Inject constructor() : Engine {
override fun start() {
println("Gas engine start.")
}
override fun shutdown() {
println("Gas engine shutdown.")
}
}
class ElectricEngine @Inject constructor() : Engine {
override fun start() {
println("Electric engine start.")
}
override fun shutdown() {
println("Electric engine shutdown.")
}
}
//由于Engine是一个接口,当我们声明这个接口类型时,Hilt不知道我们需要什么样的具体类型,所以我们还需要进行下一步操作指明具体的对象类型
//这里定义一个抽象类,命名为EngineModule,意思为Engine接口提供具体的对象
//@Module注解,表示这一个用于提供依赖注入实例的模块。
//@InstallIn(ActivityComponent::class),就是把这个模块安装到Activity组件当中,即Activity以及Activity中包含的Fragment和View也可以使用,其他地方不能使用
@Module
@InstallIn(ActivityComponent::class)
abstract class EngineModule {
@BindGasEngine//下面声明的注解,个人理解方便其他地方使用bindGasEngine这个方法来获取具体对象
@Binds//这个方法绑定具体的对象类型,方法参数中传入什么类型,就返回什么类型,注意GasEngine必须继承/实现Engine
abstract fun bindGasEngine(gasEngine: GasEngine): Engine
@BindElectricEngine
@Binds
abstract fun bindElectricEngine(electricEngine: ElectricEngine): Engine
}
//在EngineModule这个抽象类文件中声明以下注解,然后将对应注解添加到上面对应方法上面,表示这个注解与具体的对象类型进行绑定
@Qualifier//给相同类型的类或接口注入不同的实例,限定符
@Retention(AnnotationRetention.BINARY)
annotation class BindGasEngine
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindElectricEngine
//然后具体在Truck类中使用Engine类型的对象
class Truck @Inject constructor(val driver: Driver) {
@BindGasEngine//通过我们前面定义的注解显示声明我们需要的具体类型的对象
@Inject
lateinit var gasEngine: Engine
@BindElectricEngine//通过我们前面定义的注解显示声明我们需要的具体类型的对象
@Inject
lateinit var electricEngine: Engine
fun deliver() {
gasEngine.start()//正常使用
electricEngine.start()//正常使用
println("Truck is delivering cargo. Driven by $driver")
gasEngine.shutdown()
electricEngine.shutdown()
}
}
6、第三方类的依赖注入
1、直接使用OkHttpClient
例如我们想要使用OkHttp来访问网络资源,所以需要去构建一个OkHttpClient实例
//创建一个NetworkModule类用来提供OkHttpClient实例
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
//然后定义一个provideOkHttpClient方法用来提供OkHttpClient实例,这个方法上添加@Provides注解,表示Hilt来托管这个方法来实例化OkHttpClient对象
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
}
//使用
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
...
}
2、直接使用Retrofit,通过Retrofit间接使用OkHttpClient
@Module
@InstallIn(ActivityComponent::class)
class NetworkModule {
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
//由于上述定义了如何通过Hilt来获取OkHttpClient类型的对象,所以这里参数Hilt自动帮我们注入了
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com/")
.client(okHttpClient)
.build()
}
}
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var retrofit: Retrofit
...
}
7、Hilt的组件作用域
对于可以从中执行字段注入的每个 Android 类(6个入口),都有一个关联的 Hilt 组件,可以在 @InstallIn
注解中引用该组件。每个 Hilt 组件负责将其绑定注入相应的 Android 类
使用方式,例:@InstallIn(ActivityComponent::class)
Hilt 组件 | 注入器面向的对象 | Android类对应的作用域 |
---|---|---|
SingletonComponent | Application | @Singleton |
ActivityRetainedComponent | 不适用 | @ActivityRetainedScoped |
ViewModelComponent | ViewModel | @ViewModelScoped |
ActivityComponent | Activity | @ActivityScoped |
FragmentComponent | Fragment | @FragmentScoped |
ViewComponent | View | @ViewScoped |
ViewWithFragmentComponent | 带有 @WithFragmentBindings 注解的 View | @ViewScoped |
ServiceComponent | Service | @ServiceScoped |
注意:将绑定的作用域限定为某个组件的成本可能很高,因为提供的对象在该组件被销毁之前一直保留在内存中。请在应用中尽量少用限定作用域的绑定。如果绑定的内部状态要求在某一作用域内使用同一实例,绑定需要同步,或者绑定的创建成本很高,那么将绑定的作用域限定为某个组件是一种恰当的做法。