2. 使用viewModel()获取ViewModel实例
在 Jetpack Compose 中,viewModel() 是获取 ViewModel 实例的标准入口。但它并非凭空创建对象——实例的存活范围由 ViewModelStoreOwner 决定,而实例的构造方式则由 依赖注入容器(Hilt / Koin)接管。本章将从原理到配置,完整拆解 viewModel() 的调用链路,并给出两种主流 DI 容器的集成方案。
2.1 viewModel() 函数的工作原理
viewModel() 是 androidx.lifecycle.viewmodel.compose 包提供的 Composable 函数。它的核心职责是:从当前 ViewModelStoreOwner 的 ViewModelStore 中取回或创建目标 ViewModel 实例。完整调用链路如下:
- 定位 Owner:通过
LocalViewModelStoreOwner获取最近的 ViewModelStoreOwner(Activity / Fragment / NavBackStackEntry)。 - 查询缓存:在 ViewModelStore(本质是
HashMap<String, ViewModel>)中按 key 查找实例。 - 命中返回:如果已存在,直接返回缓存的实例,不执行任何构造逻辑。
- 缺失创建:如果不存在,通过
ViewModelProvider.Factory创建新实例,并存入 ViewModelStore。 - 绑定生命周期:当 ViewModelStoreOwner 销毁时,自动清空 Store 中的 ViewModel,触发
onCleared()。
作用域对照表:常用的 ViewModelStoreOwner 及其作用域范围
| Owner 类型 | 作用域范围 | 典型场景 |
|---|---|---|
| Activity | Activity 整个生命周期 | 全局共享状态 |
| NavBackStackEntry | 单个导航目的地的生命周期 | 页面级 ViewModel |
| NavGraphEntry | 导航图的生命周期 | 多页面共享图级别状态 |
| Fragment | Fragment 生命周期 | 传统 Fragment + Compose 混用 |
2.2 依赖注入容器的角色
业务复杂的 ViewModel 往往需要依赖 Repository、UseCase、数据源等对象。若在 ViewModel 内部手动构造这些依赖,会导致代码耦合、难以测试。依赖注入容器接管了实例化职责,让 ViewModel 只声明需求,由容器在创建时注入。
在 Compose + ViewModel 的上下文中,DI 容器通过 ViewModelProvider.Factory 接口与 viewModel() 集成。当缓存未命中时,Factory 的 create() 方法被调用,容器在此处提供已装配好的依赖。
| 对比维度 | Hilt(基于 Dagger) | Koin(基于服务定位器) |
|---|---|---|
| 处理时机 | 编译期(APT 生成代码) | 运行时(无注解处理器) |
| 配置复杂度 | 较高(需插件、kapt、Application 注解) | 较低(纯 Kotlin DSL) |
| ViewModel 注解 | @HiltViewModel | viewModel { } 函数定义 |
| Compose 获取方式 | viewModel()(自动使用 HiltViewModelFactory) | koinViewModel() 或 viewModel() + Koin 默认 Factory |
| 作用域管理 | @ViewModelScoped / @ActivityScoped | scope { } + 手动绑定 |
| 构建速度 | 较慢(注解处理) | 较快(无 APT) |
核心选择建议:大型项目选 Hilt,享受编译期校验和更严格的依赖图;中小项目或快速原型选 Koin,DSL 更轻量。
2.3 使用 Hilt 配置依赖注入
Hilt 是 Android 官方推荐的 DI 框架,与 viewModel() 深度集成。以下为完整配置步骤:
- 项目级 build.gradle 添加 Hilt 插件和 kapt 依赖。
- Application 类 标注
@HiltAndroidApp。 - ViewModel 类标注
@HiltViewModel,构造函数使用@Inject。 - Composable 页面 直接调用
viewModel(),无需手动传递 Factory。
// app/build.gradle.kts
plugins {
id("com.android.application")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}
dependencies {
implementation("com.google.dagger:hilt-android:2.48")
kapt("com.google.dagger:hilt-android-compiler:2.48")
implementation("androidx.hilt:hilt-navigation-compose:1.1.0")
}
// MyApplication.kt
@HiltAndroidApp
class MyApplication : Application()
// MyViewModel.kt
@HiltViewModel
class MyViewModel @Inject constructor(
private val repository: MyRepository
) : ViewModel() {
val state: StateFlow<UiState> = repository.observeData()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), UiState.Loading)
}
// MyScreen.kt — Compose 页面
@Composable
fun MyScreen(
viewModel: MyViewModel = viewModel()
) {
val uiState by viewModel.state.collectAsStateWithLifecycle()
// 渲染 UI
}
注意:使用 Hilt 时,
viewModel() 会自动查找 HiltViewModelFactory,前提是当前 Composable 位于 @AndroidEntryPoint 标注的 Activity / Fragment 内,或 NavBackStackEntry 的父图已注入 Hilt。若在非 Hilt 组件中调用,会抛出 IllegalStateException。
2.4 使用 Koin 配置依赖注入
Koin 以纯 Kotlin DSL 著称,无需注解处理器。与 Compose 集成方式如下:
- 添加依赖:
koin-android和koin-androidx-compose。 - 定义模块:使用
viewModel { }函数注册 ViewModel。 - 启动 Koin:在 Application 中调用
startKoin()。 - Composable 页面