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

发布时间: 2021-02-16 13:44:40 作者: 大象笔记

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

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

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

重复劳动,且麻烦。有了 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 这种方式,就需要:

操作繁琐。

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

注:

为何数据库操作、网络操作需要使用 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.

操作步骤

参考:

具体步骤:

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

参考

我是一名山东烟台的开发者,联系作者