第4章 ViewModel与Hilt集成:@HiltViewModel注解、ViewModelProvider注入、Fragment中获取ViewModel
4.1 原理概述
在Android架构中,ViewModel负责管理UI相关数据,并在配置更改(如屏幕旋转)后保持存活。传统ViewModel创建依赖手动工厂或AndroidViewModel的构造函数。Hilt通过@HiltViewModel注解,让ViewModel的构造函数自动接收依赖注入,无需手写工厂。
- @HiltViewModel:标记在ViewModel类上,Hilt为其生成工厂,自动注入依赖(如Repository、UseCase)。
- ViewModelProvider:Hilt扩展了ViewModelProvider的默认实现,Fragment或Activity中通过by viewModels()委托即可获取带注入的ViewModel。
- Fragment中获取:使用
by viewModels()或by activityViewModels(),Hilt自动提供依赖并管理生命周期。
核心优势:不再需要手动创建ViewModelProvider.Factory,减少模板代码,且ViewModel内部依赖可随着Hilt组件作用域灵活切换(如单例、Activity作用域)。
4.2 实现流程
- 在ViewModel类上添加
@HiltViewModel注解。 - ViewModel构造函数中添加
@Inject注解,参数为需要注入的依赖。 - 在Fragment中通过
private val vm: MyViewModel by viewModels()获取ViewModel实例。 - 确保Fragment已用
@AndroidEntryPoint注解(参见第3章)。 - 运行应用,Hilt自动解析构造函数依赖并创建ViewModel。
生命周期要点:ViewModel默认与Fragment或Activity的LifecycleOwner关联。在多Fragment单Activity架构中,如需共享数据,使用by activityViewModels()使ViewModel作用域提升到宿主Activity。
4.3 核心注解与API说明
| 注解 / API | 作用 | 使用位置 |
|---|---|---|
@HiltViewModel |
标记ViewModel由Hilt管理,自动生成工厂 | ViewModel类头部 |
@Inject |
标记构造函数(或字段),Hilt提供依赖 | ViewModel构造函数 |
by viewModels() |
Kotlin属性委托,获取当前Fragment作用域的ViewModel | Fragment中属性声明 |
by activityViewModels() |
获取宿主Activity作用域的ViewModel(共享) | Fragment中属性声明 |
ViewModelProvider (Hilt扩展) |
Hilt自动解析工厂,无需手动创建 | 内部使用,开发者不用直接调用 |
4.4 代码示例
定义ViewModel(注入Repository):
// MyViewModel.kt
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: UserRepository
) : ViewModel() {
private val _user = MutableLiveData()
val user: LiveData get() = _user
fun loadUser(userId: String) {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
Fragment中使用ViewModel:
// UserFragment.kt
@AndroidEntryPoint
class UserFragment : Fragment(R.layout.fragment_user) {
private val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.loadUser("42")
viewModel.user.observe(viewLifecycleOwner) { name ->
// 更新UI
binding.userName.text = name
}
}
}
常见陷阱:不能在非Hilt管理的Fragment(未加@AndroidEntryPoint)中使用by viewModels(),否则Hilt无法安装组件,导致运行时异常“Cannot create instance of ViewModel”。
4.5 内联SVG:ViewModel与Hilt集成流程
4.6 调试与验证
- 检查Fragment是否添加
@AndroidEntryPoint,否则Hilt不会处理ViewModel注入。 - 查看编译后生成的
Hilt_MyViewModel工厂类(位于build/generated/source/hilt/...)。 - 在Logcat中过滤“Hilt”标签,查看是否有因依赖解析失败导致的错误。
- 使用
ViewModelProvider.Factory自定义工厂的旧代码需要移除,避免冲突。
4.7 常见错误与解决方案
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| java.lang.RuntimeException: Cannot create an instance of class MyViewModel | 缺少@HiltViewModel注解或Fragment未加@AndroidEntryPoint | 添加注解并Clean/Rebuild项目 |
| java.lang.IllegalStateException: ViewModel has not been initialized | 在Fragment早期生命周期(如onAttach)中访问ViewModel | 确保在onViewCreated或之后访问 |
| Dagger/MissingBinding错误 | 构造函数依赖(如UserRepository)未提供相应Hilt绑定 | 为Repository添加@Inject构造或注册Module |
| by activityViewModels()返回不同实例 | 多个Activity宿主导致作用域不一致 | 确认共享ViewModel的Activity唯一 |
4.8 性能与稳定性优化
- 避免在ViewModel中持有Fragment/Activity引用:否则会导致内存泄漏;Hilt注入的依赖应是无状态的单例或作用域对象。
- 使用viewModelScope:在ViewModel中启动协程,当ViewModel被清除时自动取消,防止后台任务泄漏。
- 按需使用by activityViewModels():如果数据不需要跨Fragment共享,尽量使用by viewModels()缩小作用域,减少ViewModel存活时长。
- 测试时模拟Hilt组件:利用Hilt的测试库(如@HiltAndroidTest)注入Mock依赖,避免真实网络请求。
最佳实践:对于需要保存UI状态的复杂Fragment,推荐将ViewModel与Navigation组件搭配使用,利用SavedStateHandle在进程杀后恢复数据。