[TOC]
笔记来源: 郭霖公众号文章
- Dagger 匕首
- Hilt 刀把
Hilt是刀把的意思,它把匕首最锋利的地方隐藏了起来,因为如果你用不好匕首的话反而可能会误伤自己。Hilt给你提供了一个安稳的把手,确保你可以安全简单地使用。
依赖
buildscript {
...
dependencies {
...
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}
由于Hilt还会用到Java 8的特性,所以我们还得在当前项目中启用Java 8的功能
android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
简单用法
而到了Hilt当中,你必须要自定义一个Application才行,否则Hilt将无法正常工作。
@HiltAndroidApp
class MyApplication : Application() {
}
Hilt一共支持6个入口点,分别是:
- Application
- Activity
- Fragment
- View
- Service
- BroadcastReceiver
如果我希望在Activity中进行依赖注入,那么只需要这样声明Activity即可:
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var truck: Truck
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
truck.deliver()
}
}
class Truck @Inject constructor() {
fun deliver() {
println("Truck is delivering cargo.")
}
}
带参数的依赖注入
class Truck @Inject constructor(val driver: Driver) {
fun deliver() {
println("Truck is delivering cargo. Driven by $driver")
}
}
class Driver @Inject constructor() {
}
接口的依赖注入
interface Engine {
fun start()
fun shutdown()
}
@Module
@InstallIn(ActivityComponent::class)
abstract class EngineModule {
@Binds
abstract fun bindEngine(gasEngine: GasEngine): Engine
}
class Truck @Inject constructor(val driver: Driver) {
@Inject
lateinit var engine: Engine
fun deliver() {
engine.start()
println("Truck is delivering cargo. Driven by $driver")
engine.shutdown()
}
}
给相同类型注入不同的实例
自定义注解
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindGasEngine
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class BindElectricEngine
@Module
@InstallIn(ActivityComponent::class)
abstract class EngineModule {
@BindGasEngine
@Binds
abstract fun bindGasEngine(gasEngine: GasEngine): Engine
@BindElectricEngine
@Binds
abstract fun bindElectricEngine(electricEngine: ElectricEngine): 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()
}
}
第三方类的依赖注入
@Module
@InstallIn(ApplicationComponent::class)
class NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.build()
}
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("http://example.com")
.client(okHttpClient)
.build()
}
}
作用域
Hilt一共提供了7种组件作用域注解,和刚才的7个内置组件分别是一一对应的,如下表所示。
也就是说,如果想要在全程序范围内共用某个对象的实例,那么就使用@Singleton。如果想要在某个Activity,以及它内部包含的Fragment和View中共用某个对象的实例,那么就使用@ActivityScoped。以此类推。
简单来讲,就是对某个类声明了某种作用域注解之后,这个注解的箭头所能指到的地方,都可以对该类进行依赖注入,同时在该范围内共享同一个实例。
预置Qualifier
@Singleton
class Driver @Inject constructor(@ApplicationContext val context: Context) {
}
@Singleton
class Driver @Inject constructor(@ActivityContext val context: Context) {
}
关于预置Qualifier其实还有一个隐藏的小技巧,就是对于Application和Activity这两个类型,Hilt也是给它们预置好了注入功能。也就是说,如果你的某个类依赖于Application或者Activity,不需要想办法为这两个类提供依赖注入的实例,Hilt自动就能识别它们。如下所示:
class Driver @Inject constructor(val application: Application) {
}
class Driver @Inject constructor(val activity: Activity) {
}
注意必须是Application和Activity这两个类型,即使是声明它们的子类型,编译都无法通过。
我的项目会在自定义的MyApplication中提供一些全局通用的函数,导致很多地方都是要依赖于我自己编写的MyApplication的,而MyApplication又不能被Hilt识别,这种情况要怎么办呢?
@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {
@Provides
fun provideMyApplication(application: Application): MyApplication {
return application as MyApplication
}
}
ViewModel的依赖注入
class Repository @Inject constructor() {
...
}
class MyViewModel @ViewModelInject constructor(val repository: Repository) : ViewModel() {
...
}
虽然我们在MainActivity里没有使用依赖注入功能,但是@AndroidEntryPoint这个注解仍然是不能少的。不然的话,在编译时期Hilt确实检测不出来语法上的异常,一旦到了运行时期,Hilt找不到入口点就无法执行依赖注入了。
依赖:
dependencies {
...
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha02'
}
不支持的入口点怎么办?
class MyContentProvider : ContentProvider() {
@EntryPoint
@InstallIn(ApplicationComponent::class)
interface MyEntryPoint {
fun getRetrofit(): Retrofit
}
...
}
class MyContentProvider : ContentProvider() {
...
override fun query(...): Cursor {
context?.let {
val appContext = it.applicationContext
val entryPoint = EntryPointAccessors.fromApplication(appContext, MyEntryPoint::class.java)
val retrofit = entryPoint.getRetrofit()
}
...
}
}