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 如何通过注解链完成依赖注入的全流程:
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 个步骤,每步对应明确的生产力提升:
- 添加依赖 —— 在模块级
build.gradle中引入hilt-navigation-compose或fragment-ktx依赖。 - 标记 ViewModel 类 —— 在类声明上方添加
@HiltViewModel注解。 - 声明构造注入 —— 在构造函数前添加
@Inject constructor,参数列表直接声明所需依赖接口。 - 使用 Hilt 组件入口 —— 在 Activity / Fragment 上添加
@AndroidEntryPoint,通过by viewModels()委托获取 ViewModel。 - 验证生成 —— 编译项目后检查
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。