2. 使用viewModel()获取ViewModel实例

在 Jetpack Compose 中,viewModel() 是获取 ViewModel 实例的标准入口。但它并非凭空创建对象——实例的存活范围由 ViewModelStoreOwner 决定,而实例的构造方式则由 依赖注入容器(Hilt / Koin)接管。本章将从原理到配置,完整拆解 viewModel() 的调用链路,并给出两种主流 DI 容器的集成方案。

viewModel() 获取流程与依赖注入容器配置 viewModel() 获取流程与依赖注入容器配置 Composable viewModel() ViewModel StoreOwner ViewModel Store ViewModelProvider .Factory 依赖注入容器 Hilt / Koin ViewModel 实例 ▸ 创建阶段(缓存未命中) 说明 当 ViewModelStore 中不存在目标实例时,走创建路径:ViewModelProvider.Factory 接收 DI 容器提供的依赖,构造 ViewModel 实例。 若缓存命中则直接返回已有实例,绕过 Factory 与 DI 容器。作用域由 ViewModelStoreOwner 全程把控。

2.1 viewModel() 函数的工作原理

viewModel() 是 androidx.lifecycle.viewmodel.compose 包提供的 Composable 函数。它的核心职责是:从当前 ViewModelStoreOwner 的 ViewModelStore 中取回或创建目标 ViewModel 实例。完整调用链路如下:

  1. 定位 Owner:通过 LocalViewModelStoreOwner 获取最近的 ViewModelStoreOwner(Activity / Fragment / NavBackStackEntry)。
  2. 查询缓存:在 ViewModelStore(本质是 HashMap<String, ViewModel>)中按 key 查找实例。
  3. 命中返回:如果已存在,直接返回缓存的实例,不执行任何构造逻辑。
  4. 缺失创建:如果不存在,通过 ViewModelProvider.Factory 创建新实例,并存入 ViewModelStore。
  5. 绑定生命周期:当 ViewModelStoreOwner 销毁时,自动清空 Store 中的 ViewModel,触发 onCleared()。
作用域对照表:常用的 ViewModelStoreOwner 及其作用域范围
Owner 类型作用域范围典型场景
ActivityActivity 整个生命周期全局共享状态
NavBackStackEntry单个导航目的地的生命周期页面级 ViewModel
NavGraphEntry导航图的生命周期多页面共享图级别状态
FragmentFragment 生命周期传统 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 注解@HiltViewModelviewModel { } 函数定义
Compose 获取方式viewModel()(自动使用 HiltViewModelFactory)koinViewModel() 或 viewModel() + Koin 默认 Factory
作用域管理@ViewModelScoped / @ActivityScopedscope { } + 手动绑定
构建速度较慢(注解处理)较快(无 APT)
核心选择建议:大型项目选 Hilt,享受编译期校验和更严格的依赖图;中小项目或快速原型选 Koin,DSL 更轻量。

2.3 使用 Hilt 配置依赖注入

Hilt 是 Android 官方推荐的 DI 框架,与 viewModel() 深度集成。以下为完整配置步骤:

  1. 项目级 build.gradle 添加 Hilt 插件和 kapt 依赖。
  2. Application 类 标注 @HiltAndroidApp。
  3. ViewModel 类标注 @HiltViewModel,构造函数使用 @Inject。
  4. 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 集成方式如下:

  1. 添加依赖:koin-android 和 koin-androidx-compose。
  2. 定义模块:使用 viewModel { } 函数注册 ViewModel。
  3. 启动 Koin:在 Application 中调用 startKoin()。
  4. Composable 页面