【Android】Hilt用法和依赖注入笔记

762 阅读3分钟

[TOC]

笔记来源: 郭霖公众号文章

  1. Dagger 匕首
  2. 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种组件类型,分别用于注入到不同的场景,如下表所示。

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()
        }
        ...
    }

}