4. ViewModel 的 Hilt 注入基础

本章聚焦 Hilt 与 ViewModel 集成的三个核心构件:@HiltViewModel 注解、@Inject constructor 构造注入以及默认 ViewModel 工厂的自动生成机制。理解这三者的协作关系,是掌握 Hilt 在 Android 架构中高效管理 ViewModel 生命周期的关键起点。

4.1 核心原理

在传统 ViewModel 使用方式中,开发者需要手动编写 ViewModelProvider.Factory 来向 ViewModel 构造函数传递依赖。Hilt 通过编译期注解处理,将这一过程完全自动化:

  • @HiltViewModel —— 标记在 ViewModel 类上,告知 Hilt 编译处理器为该类生成专用的 ViewModel 工厂。
  • @Inject constructor —— 标记 ViewModel 的构造函数,声明该 ViewModel 所需的依赖项,Hilt 会从组件树中自动查找并注入。
  • 默认 ViewModel 工厂 —— Hilt 在编译时根据 @HiltViewModel 和 @Inject constructor 自动生成的工厂类,替代开发者手动编写的 Factory。

下图展示了 Activity/Fragment 获取 ViewModel 时,Hilt 如何通过注解链完成依赖注入的全流程:

Hilt ViewModel 注入架构图 Hilt ViewModel 注入架构 Activity / Fragment @HiltViewModel ViewModel 类 @Inject constructor 依赖对象 (Repository…) 默认 ViewModel 工厂 获取 标记 包含 注入 自动创建 💡 Hilt 通过 @HiltViewModel 在编译时生成默认 ViewModel 工厂, 开发者无需手动编写 Factory,实现 ViewModel 的自动依赖注入。

4.2 核心组件对比

下表归纳 @HiltViewModel、@Inject constructor 与默认 ViewModel 工厂在职责、使用位置和生成时机上的差异:

组件 类型 核心职责 使用 / 生成位置
@HiltViewModel Kotlin 注解 标记 ViewModel 类,触发 Hilt 编译期工厂生成 ViewModel 类声明之上
@Inject constructor Kotlin 注解 声明构造函数的依赖项,Hilt 从组件树自动注入 ViewModel 构造函数之前
默认 ViewModel 工厂 编译时生成类 自动实现 ViewModelProvider.Factory,创建带注入的 ViewModel Hilt 编译处理器在 build/generated 中生成

4.3 实现流程

在项目中启用 Hilt ViewModel 注入只需以下 5 个步骤,每步对应明确的生产力提升:

  1. 添加依赖 —— 在模块级 build.gradle 中引入 hilt-navigation-compose 或 fragment-ktx 依赖。
  2. 标记 ViewModel 类 —— 在类声明上方添加 @HiltViewModel 注解。
  3. 声明构造注入 —— 在构造函数前添加 @Inject constructor,参数列表直接声明所需依赖接口。
  4. 使用 Hilt 组件入口 —— 在 Activity / Fragment 上添加 @AndroidEntryPoint,通过 by viewModels() 委托获取 ViewModel。
  5. 验证生成 —— 编译项目后检查 build/generated/source/hilt 目录下是否出现 类名_HiltViewModel 工厂文件。

4.4 代码实现

以下示例展示一个完整的 Hilt ViewModel 注入链路,包含依赖接口、ViewModel 实现以及 Activity 侧的消费:

步骤 1:定义依赖接口

// UserRepository.kt
interface UserRepository {
    suspend fun getUser(id: String): User
}

步骤 2:提供依赖实现(Hilt Module)

// RepositoryModule.kt
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
    @Provides
    @Singleton
    fun provideUserRepository(): UserRepository {
        return UserRepositoryImpl()
    }
}

步骤 3:创建 Hilt ViewModel

// MainViewModel.kt
@HiltViewModel
class MainViewModel @Inject constructor(
    private val userRepository: UserRepository
) : ViewModel() {

    private val _userState = MutableStateFlow<UiState<User>>(UiState.Loading)
    val userState: StateFlow<UiState<User>> = _userState.asStateFlow()

    fun loadUser(id: String) {
        viewModelScope.launch {
            try {
                val user = userRepository.getUser(id)
                _userState.value = UiState.Success(user)
            } catch (e: Exception) {
                _userState.value = UiState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

步骤 4:在 Activity 中使用

// MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

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

        viewModel.loadUser("user_001")
        viewModel.userState.observe(this) { state ->
            // 更新 UI
        }
    }
}
💡 最佳实践: ViewModel 的构造函数参数应当只包含业务层依赖(Repository、UseCase 等),不要传入 Context、View 或生命周期相关对象。所有 Android 框架依赖应通过 Hilt 的 @ApplicationContext 或 @ActivityContext 限定符获取。

4.5 调试与验证

Hilt 的编译期特性使得大部分问题可以在构建阶段暴露。以下三种方法可快速验证注入链路是否正常:

  • 编译期检查 —— 运行 ./gradlew assembleDebug,若 Hilt 注解处理失败会直接输出错误日志,定位到缺失绑定的依赖。
  • 查看生成源码 —— 在 build/generated/source/hilt/debug/.../ 目录下找到 MainViewModel_HiltViewModel 类,确认其中包含正确的工厂逻辑。
  • 运行时日志 —— 在 ViewModel 的 init 块中添加日志输出,确认依赖对象非 null:
@HiltViewModel
class DebugViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() {
    init {
        Log.d("HiltInjection", "repository = $repository")
        // 输出: repository = com.example.app.data.UserRepositoryImpl@xxxx
    }
}
⚠️ 常见陷阱: 如果运行时抛出 java.lang.RuntimeException: Cannot create an instance of class ...ViewModel,说明 Hilt 未能正确生成工厂。优先检查:① ViewModel 类是否添加了 @HiltViewModel;② 构造函数是否使用了 @Inject constructor;③ 依赖项是否在 Hilt 组件中提供了 @Provides 或 @Binds。

4.6 常见错误与解决方案

错误现象 根本原因 解决方案
编译失败:@HiltViewModel 未找到 缺少 hilt-navigation-compose 或 hilt-lifecycle-viewmodel 依赖 在 build.gradle 中添加 implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
运行时:Cannot create an instance of class @HiltViewModel 或 @Inject constructor 缺失,或依赖未在 Hilt 中绑定 确认 ViewModel 上同时存在两个注解;检查所有依赖是否都有 @Provides/@Binds
编译警告:HiltViewModel 类不应包含 @Inject 字段 在 ViewModel 中混用了字段注入和构造注入 统一使用 @Inject constructor,移除类内部的 @Inject 字段
Activity 中 by viewModels() 报错 Activity 未添加 @AndroidEntryPoint 注解 在 Activity 类上添加 @AndroidEntryPoint

4.7 性能与稳定性优化

Hilt 的编译期注入对运行时性能几乎无影响,但在大型模块项目中仍需关注以下要点:

  • 依赖粒度控制 —— ViewModel 构造函数参数不要超过 4~5 个。过多依赖暗示该类承担了过多职责,应拆分为多个 ViewModel 或使用 UseCase 聚合。
  • 作用域绑定 —— 为 Repository 等重量级对象使用 @Singleton 或 @ViewModelScoped,避免每次创建 ViewModel 时都重新实例化。
  • 编译速度 —— 在 gradle.properties 中开启 kapt.use.worker.api=true 和 kapt.incremental.apt=true(若使用 KAPT),或迁移至 KSP 以缩短注解处理时间。
  • 避免循环依赖 —— Hilt 在编译期会检测循环依赖并报错。如果遇到 Dependency cycle detected,重构双向依赖为单向接口 + 回调或使用 @LazyInject 延迟注入。
📊 性能指标参考: 在 30+ 个模块的项目中,使用 Hilt ViewModel 注入相比手工编写 Factory,编译时间增加约 3%~5%,但运行时 ViewModel 创建时间降低约 40%(工厂查找由哈希表改为直接类引用)。对于用户感知而言,冷启动时首次 ViewModel 创建平均耗时 < 1ms。

4.8 小结