Android Hilt 实现依赖注入的自动化管理

更新日期: 2023-05-24 阅读次数: 5348 字数: 1033 分类: Android

Android Sunflower Demo 中看到这样一段代码:

import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class PlantListViewModel @Inject internal constructor(
    plantRepository: PlantRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ...
}

我不明白 HiltViewModel 的作用是什么?而且更奇怪的是,在 Fragment 中引用 PlantListViewModel 时,并没有传递 repository 参数,但是居然能编译通过,并且运行正常。

@AndroidEntryPoint
class PlantListFragment : Fragment() {
    private val viewModel: PlantListViewModel by viewModels()
    // ...
}

查了一下,才知道原来 Hilt 是用来做自动依赖注入管理的。

依赖注入

英文为 Dependency Injection,简写为 DI。

Sunflower Demo 项目目录下,有一个专门的 di 目录,里面有两个文件。

> ls di/
DatabaseModule.kt*  NetworkModule.kt*

使用依赖注入的优点

  • 减少模板代码
  • 提高代码可读性
  • 代码低耦合,方便单元测试
  • 自动释放不再使用的对象

Hilt

中文翻译为,刀把,刀柄。有点意思。

给我的直观感受是,确实方便了全局变量的管理。例如,之前要:

  • 在 Application 子类中定义一堆 database, repository 的全局变量
  • 逐个 activity,fragment 中对这些全局变量进行引用,为 ViewModel 进行初始化

重复劳动,且麻烦。有了 Hilt 这种自动化的依赖注入管理方案,能极大地提高开发效率,及代码可读性。

但是,看起来,对 Android 开发初学者来说学习成本并不低。因为 Hilt 引入的概念和注解非常多,而且需要经历过原始写法的折磨才能体会。

@Inject

@Inject 既可以用来注解构造方法,也可以注解字段。

几种常见的用法:

1、constructor injection

@HiltViewModel
class PlantListViewModel @Inject internal constructor(
    plantRepository: PlantRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

2、field injection:

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {
  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

注意:这种用法 field 不能是 private 类型;同时要加上 lateinit,即稍后初始化。

以 ViewModel 依赖注入 Repository 为例

如果不使用 Hilt 这种方式,就需要:

  • 实例化 Application 全局的 database,以初始化表结构等
  • 实例化 Application 全局的 Repository
  • 然后在 Fragment 中将 Repository 传递给 ViewModel

操作繁琐。

看看 sunflower Demo 是如何实现的。

Fragment 的定义:

@AndroidEntryPoint
class PlantListFragment : Fragment() {

    private val viewModel: PlantListViewModel by viewModels()

注:viewModel 没有加任何的初始化参数。

ViewModel 的定义:

@HiltViewModel
class PlantListViewModel @Inject internal constructor(
    plantRepository: PlantRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

注:@ViewModelScoped 与 @HiltViewModel 的区别是什么?

Repository 的定义:

@Singleton
class PlantRepository @Inject constructor(private val plantDao: PlantDao) {

注:@Singleton 保证只有一份 Repository 的实例;Singleton 的作用域是整个 Application (默认没有作用域,每次需要时都新建一个实例)。 所有的作用域 Scope 参见:https://developer.android.com/training/dependency-injection/hilt-android#component-scopes

Dao 的定义:

@Dao
interface PlantDao {

注:Dao 文件中并没有任何特殊处理,但实际上看一下 sunflower 的 di/DatabaseModule.kt 中的代码:

@InstallIn(SingletonComponent::class)
@Module
class DatabaseModule {
    @Singleton
    @Provides
    fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase {
        return AppDatabase.getInstance(context)
    }

    @Provides
    fun providePlantDao(appDatabase: AppDatabase): PlantDao {
        return appDatabase.plantDao()
    }

    @Provides
    fun provideGardenPlantingDao(appDatabase: AppDatabase): GardenPlantingDao {
        return appDatabase.gardenPlantingDao()
    }
}

注:

  • Dao 是 interface 无法通过 constructor-inject,需要在 hilt module 中 provide。
  • database 由于是 builder pattern 提供的,所以也只能在 module 中 provide。
  • SingletonComponent 对应 Singleton,映射关系同样参考上面链接中的所有作用域表格。

为何数据库操作、网络操作需要使用 Module

Sometimes a type cannot be constructor-injected. This can happen for multiple reasons. For example, you cannot constructor-inject an interface. You also cannot constructor-inject a type that you do not own, such as a class from an external library. In these cases, you can provide Hilt with binding information by using Hilt modules.

Interfaces are not the only case where you cannot constructor-inject a type. Constructor injection is also not possible if you don't own the class because it comes from an external library (classes like Retrofit, OkHttpClient, or Room databases), or if instances must be created with the builder pattern.

操作步骤

参考:

  • https://developer.android.com/training/dependency-injection/hilt-android#component-scopes
  • https://developer.android.com/codelabs/android-hilt#0

具体步骤:

  • 添加 gradle 依赖
  • Application 类添加 @HiltAndroidApp 注解
  • Fragment 及其依赖的 Activity 添加 @AndroidEntryPoint 注解
  • 再参照 sunflower 的 ViewModel 实现

代码配置细节可以参考我整理的一个实际案例 Android Hilt 将 SQLDelight database 依赖注入 ViewModel

参考

  • Dependency injection with Hilt https://developer.android.com/training/dependency-injection/hilt-android
  • Guide to app architecture https://developer.android.com/jetpack/guide#recommended-app-arch
  • https://mp.weixin.qq.com/s/ML1Tee8J08NHSQ3bJ0-rNg
  • https://developer.android.com/codelabs/android-hilt#6

tags: Dependency Injection hilt

关于作者 🌱

我是来自山东烟台的一名开发者,有敢兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式

谈笑风生

chesonsir

刚好看到这个地方