第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 实现流程

  1. 在ViewModel类上添加@HiltViewModel注解。
  2. ViewModel构造函数中添加@Inject注解,参数为需要注入的依赖。
  3. 在Fragment中通过private val vm: MyViewModel by viewModels()获取ViewModel实例。
  4. 确保Fragment已用@AndroidEntryPoint注解(参见第3章)。
  5. 运行应用,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集成流程

@HiltViewModel 集成流程 1. App启动 2. Hilt扫描@HiltViewModel,生成工厂 3. Fragment/Activity启动 4. by viewModels() 触发工厂调用 5. Hilt注入构造函数依赖 6. ViewModel实例可用 说明 • 步骤2自动生成工厂类 • 步骤4无需手动创建factory • 依赖作用域由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在进程杀后恢复数据。