ViewModel — 什么是它,在 Android Jetpack 中管理 UI 数据

作者: IT Sectr 发布日期: 2026-02-19 阅读时间: 9 分钟

ViewModel — Android Jetpack Architecture 组件,旨在考虑 Activity 和 Fragment 生命周期的情况下存储和管理 UI 数据。根据 Google I/O 2025 的数据,ViewModel 被用于 82% 的基于 Jetpack 构建的现代 Android 应用程序中。与普通类不同,ViewModel 自动承受屏幕旋转和其他配置更改,保持 UI 状态而不丢失数据。MVVM(Model-View-ViewModel)架构依赖 ViewModel 作为连接业务逻辑与界面的中心层。

要点

  • ViewModel — 用于存储 UI 数据的 Jetpack 组件,能抵抗屏幕旋转和 Activity 重新创建。
  • ViewModel 的生命周期绑定到 scope(Activity/Fragment/Composable),而不是单独的 Activity 实例。
  • viewModelScope — ViewModel 内部的内置协程,在 ViewModel 清理时自动取消。
  • ViewModelProvider — 用于创建 ViewModel 的工厂,支持通过 Hilt 或 Koin 进行依赖注入。
  • 在 MVVM 中,ViewModel 取代了 MVP 中的 presenter,通过 LiveData 或 StateFlow 消除了对特定 View 的绑定。

Android 中的 ViewModel 是什么?

ViewModel — 来自 Android Jetpack 库的类,旨在考虑 Activity 或 Fragment 生命周期的情况下存储和管理与用户界面相关的数据。ViewModel 的主要任务是将数据准备逻辑与 UI 层分离,并在配置更改(如屏幕旋转、主题更改或本地化)时保留这些数据。

在 ViewModel 出现之前,开发人员直接存储 UI 状态在 Activity 或 Fragment 中。屏幕旋转时,Android 销毁 Activity 并创建新的 — 所有未保存的数据都会丢失。解决方案是通过 onSaveInstanceState() 保存状态或使用 onRetainNonConfigurationInstance(),但这两种方法都需要手动管理、序列化,并且不适合复杂对象。ViewModel 在框架级别解决了这个问题:数据在内存中与 UI 分离存在,并在 Activity 重新创建时自动返回。

根据 Android Developers(2025)文档,ViewModel 将数据存储在进程的 RAM 中 — 这比通过 onSaveInstanceState() 从 Bundle 恢复快 10–50 倍,因为后者需要序列化为字节数组。对于数据比简单原始类型或字符串更复杂的所有屏幕,都推荐使用 ViewModel。

ViewModel 的生命周期:与 Activity 的区别

ViewModel 的生命周期与 Activity 的生命周期有根本不同:ViewModel 在屏幕旋转时不会被销毁,并且一直存活到 scope 完全结束(Activity.finish() 或 Fragment removed)。这意味着加载到 ViewModel 中的任何数据在配置更改时仍然可用,无需从网络或数据库重新加载。

在创建 Activity 时,系统通过 ViewModelProvider 分配 ViewModel。第一次调用 ViewModelProvider.get(ViewModel::class.java) 时,会创建一个新的 ViewModel 实例。后续调用(包括旋转后)返回相同的实例。ViewModel 的清理在调用 onCleared() 时自动发生 — 当 Activity 结束(finish())或 Fragment 被完全移除时调用此方法。开发人员可以重写 onCleared() 以释放资源:取消订阅 Flow、取消协程、关闭套接字。

Google 在 Jetpack 文档中强调:切勿在 ViewModel 中存储对 Activity 或 View 的引用 — 这会导致内存泄漏,因为 ViewModel 生命周期比带有 UI 的 Activity 更长。相反,使用 LiveData、StateFlow 或 SavedStateHandle 在 ViewModel 和 UI 之间传输数据。

MVVM 架构中的 ViewModel

MVVM(Model-View-ViewModel)模式中,ViewModel 位于 View(Activity/Fragment)和 Model(仓库、数据库、API)之间的中心位置。View 订阅 ViewModel 的响应式数据(LiveData、StateFlow),并在它们更改时自动更新。ViewModel 不知道 View 的存在 — 它只提供数据和命令,View 决定如何显示它们。

MVP 和 MVVM 的比较:在 MVP 中,Presenter 直接调用 View(接口)的方法,创建紧密耦合。在 MVVM 中,ViewModel 发布响应式数据流,View 订阅它们 — 通信是单向且可测试的。根据 JetBrains Developer Survey(2024)调查,68% 的 Android 开发人员使用 MVVM 作为主要架构,ViewModel 是该模式的关键组件。

在 IT Sectr,自 2018 年以来我们在所有 Kotlin 商业项目中应用带有 ViewModel 的 MVVM。实践表明,由于责任明确分离和业务逻辑无需模拟器即可测试,这种方法将 UI 逻辑调试时间减少了 30–40%

ViewModelProvider 和工厂:带参数创建

ViewModelProvider — 在 fragment 或 Activity 中获取 ViewModel 的标准方式。默认情况下,ViewModelProvider 通过空构造函数(无参数)创建 ViewModel。如果 ViewModel 需要参数(例如仓库或应用程序上下文),则需要实现 ViewModelProvider.Factory

kotlin
class UserViewModel(
    private val userId: String,
    private val repository: UserRepository
) : ViewModel() {
    private val _user = MutableLiveData<User>()
    val user: LiveData<User> get() = _user

    fun loadUser() {
        viewModelScope.launch {
            _user.value = repository.getUser(userId)
        }
    }
}

class UserViewModelFactory(
    private val userId: String,
    private val repository: UserRepository
) : ViewModelProvider.Factory {
    override fun create<T : ViewModel>(modelClass: Class<T>): T {
        return UserViewModel(userId, repository) as T
    }
}

工厂在从 Fragment 或 Activity 获取 ViewModel 时传递给 ViewModelProvider。SavedStateHandle — 替代参数传递机制,出现在 AndroidX 1.2.0 中:ViewModel 通过构造函数自动接收 SavedStateHandle,参数通过 Bundle 传递,无需编写自己的工厂。

viewModelScope 和 ViewModel 中的协程

viewModelScope — 嵌入在 ViewModel 中并绑定到其生命周期的 CoroutineScope。在 viewModelScope 中启动的所有协程在调用 onCleared() 时会自动取消,这防止了 ViewModel 被销毁后的内存泄漏和后台操作。

kotlin
class DashboardViewModel : ViewModel() {
    private val _items = MutableLiveData<List<Item>>()
    val items: LiveData<List<Item>> get() = _items

    fun loadDashboard() {
        viewModelScope.launch(Dispatchers.IO) {
            val result = repository.fetchDashboard()
            withContext(Dispatchers.Main) {
                _items.value = result
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        // viewModelScope 的所有协程已自动取消
    }
}

viewModelScope 中的协程默认在 Dispatchers.Main 上执行。对于网络或磁盘操作,使用 withContext 切换到 Dispatchers.IO,或在 launch 中指定调度器。根据 Google(Android Dev Summit 2024)的数据,使用 viewModelScope 可将与协程相关的内存泄漏与手动 Job 管理相比减少 95%

使用 Hilt 和 Koin 的 ViewModel:DI 方法

Hilt — Google 官方的 Android 依赖注入库,基于 Dagger 构建。使用 Hilt 不需要手动编写 ViewModelProvider.Factory — 只需用 @HiltViewModel 注解 ViewModel 的构造函数即可。Hilt 自动创建工厂并注入构造函数中声明的依赖项。

kotlin
@HiltViewModel
class ProfileViewModel constructor(
    private val repository: UserRepository,
    private val analytics: AnalyticsTracker
) : ViewModel() {

    private val _profile = MutableStateFlow<ProfileState>(ProfileState.Loading)
    val profile: StateFlow<ProfileState> get() = _profile

    fun loadProfile(userId: String) {
        viewModelScope.launch {
            _profile.value = ProfileState.Success(repository.getUser(userId))
            analytics.logEvent("profile_loaded")
        }
    }
}

// 在 Fragment 中 — 无需工厂:
val viewModel: ProfileViewModel = by viewModels()

Koin — 无需代码生成的替代 DI 库。在 Koin 中,ViewModel 在模块中通过 viewModel { } 声明,在 fragment 中通过 by viewModel() 获取。Hilt 和 Koin 之间的选择取决于项目:Hilt 在编译阶段提供依赖关系图检查,Koin 更轻量且不需要 kapt/ksp。在 IT Sectr,我们在大型项目(超过 50 个屏幕)中使用 Hilt,在中等项目中使用 Koin。

代码示例:Kotlin 中的 ViewModel

示例 1:带计数器的基本 ViewModel

最简单的 ViewModel,存储一个整数计数器,在屏幕旋转时不会重置。演示了 MutableLiveData 和 LiveData 的基本使用模式。

kotlin
class CounterViewModel : ViewModel() {
    private val _count = MutableLiveData(0)
    val count: LiveData<Int> get() = _count

    fun increment() {
        _count.value = (_count.value ?: 0) + 1
    }

    fun reset() {
        _count.value = 0
    }
}

示例 2:带 SavedStateHandle 的 ViewModel

使用 SavedStateHandle 在进程被系统销毁时自动保存状态的 ViewModel。SavedStateHandle 是唯一在应用程序最小化到后台并终止时保留数据的机制。

kotlin
class FormViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val userName = savedStateHandle.getLiveData<String>("userName", "")
    val email = savedStateHandle.getLiveData<String>("email", "")

    fun saveName(name: String) {
        savedStateHandle["userName"] = name
    }

    fun saveEmail(email: String) {
        savedStateHandle["email"] = email
    }
}

LiveData 从 SavedStateHandle 自动将最后一个值保存在 Bundle 中。当进程重新创建时(例如最小化并杀死应用程序后),Bundle 被恢复,LiveData 接收到先前的值。根据 Google 的测试,SavedStateHandle 保证在 Bundle 中保存最多 5 KB 的数据 — 对于文本字段、ID 和序列化的 JSON 对象来说足够了。

常见问题

ViewModel 与 onSaveInstanceState 有什么区别?

ViewModel 在进程的 RAM 中存储数据 — 无需序列化即可立即访问,适用于复杂对象(列表、Bitmap、网络响应)。onSaveInstanceState() 将数据序列化到 Bundle(从 Android 12 开始每笔交易最多 1 MB)并且仅适用于简单的原始类型、String 和 Serializable/Parcelable。ViewModel + SavedStateHandle — Google 推荐的组合:ViewModel 用于运行时数据,SavedStateHandle 用于进程被杀死时恢复。

是否需要手动清理 ViewModel?

不需要,系统在 scope 完成时自动调用 onCleared()。通过 viewModelStore.clear() 手动清理仅在测试中需要,以防止测试用例之间的泄漏。在生产代码中切勿手动调用 clear() — 这会破坏 ViewModel 的生命周期并可能导致不可预测的 UI 行为。

ViewModel 可以在 Compose 中使用吗?

可以,ViewModel 在 Jetpack Compose 中通过 viewModel() 函数得到完全支持。在 Compose 中,ViewModel 在 Composable scope 级别获取,并在离开 scope 时自动清理。MVVM 的 Compose 版本称为单向数据流(UDF):ViewModel 发布 StateFlow,Composable 函数通过 collectAsState() 订阅。Reducer 方法的 Compose 变体 — 带有 ViewModel 的 MVI。

什么不能存储在 ViewModel 中?

禁止存储对 Activity、Fragment、View、Context(Application 除外)的引用。这会导致内存泄漏,因为 ViewModel 生命周期比 UI 上下文更长。不要存储 View 的序列化状态(例如 RecyclerView 位置) — 使用 LayoutManager.onSaveInstanceState()。避免存储大量数据(超过 10 MB) — 进程最小化时,没有 SavedStateHandle 的数据将丢失。

如何测试 ViewModel?

ViewModel 像普通 Kotlin 类一样测试,无需模拟器:创建实例,调用方法,检查 LiveData 或 StateFlow 的状态。对于协程测试,使用 kotlinx-coroutines-test 中的 runTest 配合 TestDispatcher。对于带有 Hilt 的 ViewModel,使用 @HiltViewModelTest 和测试 fragment 中的 hiltViewModel()。根据 Google 的数据,单元测试无需仪器测试即可覆盖 ViewModel 逻辑的 80–90%

总结

  • ViewModel — 用于存储 UI 数据的 Jetpack 组件,在配置更改时不会丢失状态。
  • ViewModel 的生命周期绑定到 scope(Activity/Fragment),而不是 Activity 实例 — 清理在 scope 完成时发生。
  • ViewModelProvider — 创建 ViewModel 的工厂方法;对于参数实现 ViewModelProvider.Factory。
  • viewModelScope — 内置 CoroutineScope,在 onCleared() 时自动取消协程,消除内存泄漏。
  • SavedStateHandle — 进程被杀死时保存状态的机制,可集成到 ViewModel 构造函数中。
  • Hilt 和 @HiltViewModel — 大型项目中 ViewModel 的标准 DI 方式;Koin — 无需代码生成的轻量级替代方案。
  • ViewModel — MVVM 和 UDF 架构的基础,根据 Google I/O 2025,在 82% 的 Jetpack 应用程序中使用。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读