Jetpack Hilt

Hilt 是Google 官方推出的依赖注入框架

什么是依赖注入

简单地说,一个类中使用的依赖类不是类本身创建的,而是通过构造函数或者属性方法实现的,这种实现方式就称为依赖注入。

以手机为例,手机需要插入 SIM 卡才可以正常拨打电话,也就是说,手机的通话功能依赖于 SIM 卡。接下来新建 SimCard 类和 MobilePhone 类。

不使用依赖注入的代码实现方式如下:

1
2
3
4
5
6
class SimCard {
private val TAG = "TAG_SimCard"
fun dialNumber(){
Log.d(TAG,"拨打电话")
}
}
1
2
3
4
5
6
class MobilePhone {
fun dialNumber(){
val simCard = SimCard()
simCard.dialNumber()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class TestActivity :AppCompatActivity(){
private val TAG = "TAG_TestActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

val mobilePhone = MobilePhone()
// 调用 MobilePhone 的 dialNumber 方法时,
// 首先会在 dialNumber 方法中创建 SimCard 对象,然后调用其 dialNumber 方法。
mobilePhone.dialNumber()
}
}

这种方式中 MobilePhone 类虽然依赖于 SimCard 类,但使用时依赖类是 MobilePhone 类自己创建的,所以这种方式并没有使用依赖注入。

接下来看使用依赖注入的实现方式:

1
2
3
4
5
6
class SimCard {
private val TAG = "TAG_SimCard"
fun dialNumber(){
Log.d(TAG,"拨打电话")
}
}
1
2
3
4
5
6
class MobilePhone {
// 这里为 dialNumber 方法添加了一个 SimCard 类型的对象
fun dialNumber(simCard : SimCard){
simCard.dialNumber()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
class TestActivity :AppCompatActivity(){
private val TAG = "TAG_TestActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

val mobilePhone = MobilePhone()
val simCard = SimCard()
// 使用参数调用 SimCard 类的 dialNumber 方法。
mobilePhone.dialNumber(simCard)
}
}

在 Activity 中创建了 SimCard 类的实例,并传给 MobilePhone 类的 dialNumber 方法使用,这种实现方式就是依赖注入。

在普通的实现方式中,MobilePhone 类不仅要负责自身类的功能,还要负责创建 SimCard 类,在 Activity 中创建 MobilePhone 类也是一样。使用依赖注入不仅可以提高代码的可扩展性,还可以分离依赖项。

但上述示例代码中的依赖注入只是将依赖项的创建时机放到了更上层,在实际开发中,类的依赖关系较为复杂,如果仍使用示例中的依赖注入方式就不太合适了。而 Hilt 是 Google 官方为开发者提供的可以简化使用的依赖注入框架,他是在 Dagger 的基础上开发的。


基于 Dagger 看 Hilt

Dagger 是 Square 公司开发的一个依赖注入框架。Dagger 最初版本是采用反射的方式实现的,但过多使用反射回影响程序的运行效率。由于反射方法在编译阶段是不会产生错误的,因此只有在程序运行时才可以验证反射方法是否正确。考虑到上述问题,Google 基于 Dagger 开发了 Dagger2,Dagger2 是通过注解的方式实现的,如此一来,在编译时就可以发现依赖注入使用的问题,但 Dagger2 的使用比较繁琐。

Hilt 组件是基于 Dagger 开发、专门面向 Android 开发者的依赖注入框架。他只是为依赖注入提供了更简便的实现方式,而不是实现依赖注入的唯一方式。


Hilt 的基本使用

添加依赖

根项目 build.gradle 中添加 Hilt 插件:

1
2
3
4
5
6
buildscript {
...
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
}
}

App 模块下 build.gradle 中添加依赖项:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
...
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
}

Hilt 当前支持的 Android 类及其注解与注意事项:

Android 类 注解 注意事项
Application @HiltAndroidApp 必须定义一个 Application
Activity @AndroidEntryPoint 仅支持扩展 ComponentActivity 的 Activity
Fragment @AndroidEntryPoint 仅支持扩展 androidx.Fragmen 的 Fragment
View @AndroidEntryPoint /
Service @AndroidEntryPoint /
BroadcastReceiver @AndroidEntryPoint /

每个应用程序都包含一个 Application,开发者可通过自定义 Application 来做一些基本的初始化等操作。在使用 Hilt 时,开发者必须自定义一个 Application,为其添加 @HiltAndroidApp 注解。并在 AndroidManifest.xml 配置文件中注册。

1
2
@HiltAndroidApp
class MyApplication : Application() {}

依赖注入普通对象

新建 UserManager 类:

1
2
3
4
5
6
class UserManager {
val TAG = "TAG_UserManager"
fun getUserToken(){
Log.d(TAG,"获取用户 token")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TestActivity :AppCompatActivity(){
private val TAG = "TAG_TestActivity"

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

// 这里的问题是:
// Activity 不仅负责 UI 的显示,还创建了 UserManager 类。
// 如果 Activity 的依赖类过多,会导致 Activity 臃肿且难以维护。
val userManager = UserManager()
userManager.getUserToken()
}
}

Hilt 通过为被依赖类的构造函数添加 @Inject 注解,来告知 Hilt 应如何提供该类的实例。

1
2
3
4
5
6
class UserManager @Inject constructor(){
val TAG = "TAG_UserManager"
fun getUserToken(){
Log.d(TAG,"获取用户 token")
}
}
1
2
3
4
5
6
7
8
9
10
11
12
@AndroidEntryPoint
class TestActivity :AppCompatActivity(){
@Inject
lateinit var userManager: UserManager

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

userManager.getUserToken()
}
}

上述代码首先为 Activity 添加 @AndroidEntryPoint 注解,声明一个延迟初始化的变量(由 Hilt 注入的字段不能为私有类型,否则会在编译阶段产生错误),并添加 @Inject 注解。这样 Activity 就通过依赖注入获取到了 UserManager 类的实例。

如果要注入的对象存在参数,如上述的 MobilePhone 类需要依赖 SimCard 类,可通过构造函数注入:

1
2
3
4
5
6
class SimCard @Inject constructor() {
private val TAG = "TAG_SimCard"
fun dialNumber(){
Log.d(TAG,"拨打电话")
}
}
1
2
3
4
5
class MobilePhone @Inject constructor(val simCard: SimCard) {
fun dialNumber(){
simCard.dialNumber()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@AndroidEntryPoint
class TestActivity :AppCompatActivity(){
private val TAG = "TAG_TestActivity"

@Inject
lateinit var mobilePhone: MobilePhone

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

mobilePhone.dialNumber()
}
}

依赖注入第三方组件

以 OkHttp 的实现为例,发起网络请求时,会创建 OkHttpClient 实例,但由于 OkHttpClient 类是第三方库的类,因为会导致无法直接添加注解,这里首先新建一个 NetWorkUtil 类,并添加 @Module 与 @InstallIn 注解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Module
@InstallIn(ActivityComponent::class)
class NetWorkUtil {
/**
* 使用 @Provides 注解提供获取方法
*
*/
@Provides
fun getOkHttpClient():OkHttpClient{
var okHttpClient = OkHttpClient.Builder()
.connectTimeout(10,TimeUnit.SECONDS)
.build()
return okHttpClient
}

/**
* 应对这种泛型貌似会报错:
* 错误: @Provides methods may not have type parameters
*/
// @Singleton
// @Provides
// fun <T> createService(
// clazz: Class<T>,
// baseApi: String = BaseApi.MAIN_API
// ): T? {...}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@AndroidEntryPoint
class TestActivity :AppCompatActivity(){
private val TAG = "TAG_TestActivity"

@Inject
lateinit var okHttpClient: OkHttpClient

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)

...
okHttpClient.newCall(request).enqueue(object : Callback{
...
})
}
}

这样就不需要在 Activity 中创建 OkHttpClient 对象了。

@Module 注解表示这是一个用于提供依赖注入实例的模块。@InstallIn 注解表示要装载到哪个模块中,这里使用 ActivityComponent 表示要装载到 Activity 组件中,所以可在 Activity、Fragment 以及 View 中使用 NetWorkUtil 模块。如果还想在这三个组件之外使用 NetWorkUtil 模块,则需要装载到其他组件中。Hilt 组件类型与注入场景以及生命周期的对应关系:

组件名称 注入场景 生命周期
ApplicationComponent Application Application#onCreate()~Application#onDestroy()
ActivityRetainedComponent ViewModel Activity#onCreate()~Activity#onDestroy()
ActivityComponent Activity Activity#onCreate()~Activity#onDestroy()
FragmentComponent Fragment Fragment#onAttach()~Fragment#onDestroy()
ViewComponent View View#supper()~视图销毁
ViewWithFragmentComponent @WithFragmentBindings 注解的 View View#supper()~视图销毁
ServiceComponent Service Service#onCreate()~Service#onDestroy()

如果想在应用全局中使用 NetWorkUtil 模块,则将 InstallIn 注解属性值修改为 ApplicationComponent::class 即可。

一般情况下都会将 OkHttpClient 对象设置为单例模式,即全局只有一个 OkHttpClient 对象,则需要将 InstallIn 属性值设置为 ApplicationComponent::class,并且为 getOkHttpClient 方法添加 @Singleton 注解:

1
2
3
4
5
6
7
8
9
10
11
12
@Module
@InstallIn(ApplicationComponent::class)
class NetWorkUtil {
@Singleton
@Provides
fun getOkHttpClient():OkHttpClient{
var okHttpClient = OkHttpClient.Builder()
.connectTimeout(10,TimeUnit.SECONDS)
.build()
return okHttpClient
}
}

当 Hilt 升级到 2.31 以及之后的版本,会发现已经不支持 ApplicationComponent 了,找不到该类,原因是:

  • ApplicationComponent 在 Dagger 2.30版本中已经标记过时了
  • ApplicationComponent 在 Dagger 2.31版本中就已经被移除了

解决办法:使用SingletonComponent代替ApplicationComponent,修改后的代码如下:

1
2
3
4
5
@Module
@InstallIn(SingletonComponent::class)
class NetWorkUtil {
...
}

这样在任意地方调用 getOkHttpClient 方法都只会创建一个 OkHttpClient 对象。@Singleton 注解是 Application 组件类的作用域,Hilt 只为绑定作用域中的组件实例创建一次作用域绑定,并对该绑定的所有请求共享同一实例。各组件对应作用域的关系:

组件名称 作用域
ApplicationComponent @Singleton
ActivityRetainedComponent @ActivityRetainedScoped
ActivityComponent @ActivityScoped
FragmentComponent @FragmentScoped
ViewComponent @ViewScoped
ViewWithFragmentComponent @ViewScoped
ServiceComponent @ServiceScoped

需要注意的是,绑定的作用域必须与其安装的组件的作用域一致,否则在运行程序时会发生异常。

当在程序中声明两个提供 OkHttpClient 实例的方法时,在使用的时候 Hilt 并不知道要依赖注入哪个实例,这时运行程序会出现编译错误。需要用到 Qualifier 注解来解决这个问题,Qualifier 注解的作用就是为相同类型的类注入不同的实例,新建 QualifierConfig 文件:

QualifierConfig.kt
1
2
3
4
5
6
7
8
9
10
/**
* [@Retention] 用于声明注解的作用范围,这里表示注解在编译后将会被保留。
*/
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OkHttpClientStandard

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherOkHttpClient

定义两个注解类,完成定义后,将注解类使用在提供 OkHttpClient 实例的方法上即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Module
@InstallIn(ApplicationComponent::class)
class NetWorkUtil {
/**
* 设置超时时间为 10 秒钟
*/
@Singleton
@OkHttpClientStandard
@Provides
fun getOkHttpClient():OkHttpClient{
var okHttpClient = OkHttpClient.Builder()
.connectTimeout(10,TimeUnit.SECONDS)
.build()
return okHttpClient
}

/**
* 设置超时时间为 20 秒钟
*/
@OtherOkHttpClient
@Provides
fun getOtherOkHttpClient():OkHttpClient{
var okHttpClient = OkHttpClient.Builder()
.connectTimeout(20,TimeUnit.SECONDS)
.build()
return okHttpClient
}
}

在 Activity 中使用两个 OkHttpClient 实例的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
@AndroidEntryPoint
class TestActivity :AppCompatActivity(){
@OkHttpClientStandard
@Inject
lateinit var okHttpClient: OkHttpClient

@OtherOkHttpClient
@Inject
lateinit var otherOkHttpClient: OkHttpClient

...
}

依赖注入架构组件

Hilt 集成了 Jetpack 组件,当前 Hilt 仅支持 ViewModel 组件和 WorkManager 组件,这里以 ViewModel 组件为例来看如何使用 Hilt 依赖注入。

首先在 build.gradle 中添加 Hilt 的扩展依赖:

1
2
3
4
dependencies {
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
}

新建一个 ViewModel:

1
class TestViewModel @ViewModelInject constructor():ViewModel() {}
1
2
3
4
5
6
7
8
9
10
11
12
13
@AndroidEntryPoint
class TestActivity :AppCompatActivity(){
// 这里可以通过和使用依赖注入之前一样的方式来获取 ViewModel 对象,
// 这都是 Hilt 自动为开发者处理好的。
val testViewModel by viewModels<TestViewModel>()

// 也可以使用依赖注入普通类的方式注入 ViewModel 的对象
// 这样就实现了 Hilt 依赖注入 ViewModel 的功能,但是这种方式改变了 ViewModel 的正常获取方式,所以并不建议使用。
// @Inject
// lateinit var testViewModel : TestViewModel

...
}

原理解析

以上述依赖注入 UserManager 对象为例,在为对象声明 @Inject 注解后,系统会为我们生成 UserManager_Factory 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 
public final class UserManager_Factory implements Factory<UserManager> {
@Override
public UserManager get() {
return newInstance();
}

public static UserManager_Factory create() {
return InstanceHolder.INSTANCE;
}

public static UserManager newInstance() {
return new UserManager();
}

private static final class InstanceHolder {
private static final UserManager_Factory INSTANCE = new UserManager_Factory();
}
}

UserManager_Factory 类实现了 Factory<UserManager> 接口,Factory<T>Provider<T> 的子类。

上述代码中为 TestActivity 声明了 @AndroidEntryPoint 注解,系统会自动生成基类 Hilt_TestActivity,在其 onCreate 方法中调用了 inject():

1
2
3
4
5
6
@CallSuper
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
inject();
super.onCreate(savedInstanceState);
}
1
2
3
protected void inject() {
((TestActivity_GeneratedInjector) generatedComponent()).injectTestActivity(UnsafeCasts.<TestActivity>unsafeCast(this));
}

inject 方法会调用 injectTestActivity 方法,injectTestActivity 方法是 TestActivity_GeneratedInjector 的接口方法:

1
2
3
public interface TestActivity_GeneratedInjector {
void injectTestActivity(TestActivity testActivity);
}

备注

参考资料

developers Hilt

《Android Jetpack开发 原理解析与应用实战》

欢迎关注微信公众号:非也缘也