ViewModel — Android Jetpack Architecture 组件,旨在考虑 Activity 和 Fragment 生命周期的情况下存储和管理 UI 数据。根据 Google I/O 2025 的数据,ViewModel 被用于 82% 的基于 Jetpack 构建的现代 Android 应用程序中。与普通类不同,ViewModel 自动承受屏幕旋转和其他配置更改,保持 UI 状态而不丢失数据。MVVM(Model-View-ViewModel)架构依赖 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 在屏幕旋转时不会被销毁,并且一直存活到 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(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 — 在 fragment 或 Activity 中获取 ViewModel 的标准方式。默认情况下,ViewModelProvider 通过空构造函数(无参数)创建 ViewModel。如果 ViewModel 需要参数(例如仓库或应用程序上下文),则需要实现 ViewModelProvider.Factory。
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 中并绑定到其生命周期的 CoroutineScope。在 viewModelScope 中启动的所有协程在调用 onCleared() 时会自动取消,这防止了 ViewModel 被销毁后的内存泄漏和后台操作。
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 — Google 官方的 Android 依赖注入库,基于 Dagger 构建。使用 Hilt 不需要手动编写 ViewModelProvider.Factory — 只需用 @HiltViewModel 注解 ViewModel 的构造函数即可。Hilt 自动创建工厂并注入构造函数中声明的依赖项。
@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。
最简单的 ViewModel,存储一个整数计数器,在屏幕旋转时不会重置。演示了 MutableLiveData 和 LiveData 的基本使用模式。
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
}
}
使用 SavedStateHandle 在进程被系统销毁时自动保存状态的 ViewModel。SavedStateHandle 是唯一在应用程序最小化到后台并终止时保留数据的机制。
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 在进程的 RAM 中存储数据 — 无需序列化即可立即访问,适用于复杂对象(列表、Bitmap、网络响应)。onSaveInstanceState() 将数据序列化到 Bundle(从 Android 12 开始每笔交易最多 1 MB)并且仅适用于简单的原始类型、String 和 Serializable/Parcelable。ViewModel + SavedStateHandle — Google 推荐的组合:ViewModel 用于运行时数据,SavedStateHandle 用于进程被杀死时恢复。
不需要,系统在 scope 完成时自动调用 onCleared()。通过 viewModelStore.clear() 手动清理仅在测试中需要,以防止测试用例之间的泄漏。在生产代码中切勿手动调用 clear() — 这会破坏 ViewModel 的生命周期并可能导致不可预测的 UI 行为。
可以,ViewModel 在 Jetpack Compose 中通过 viewModel() 函数得到完全支持。在 Compose 中,ViewModel 在 Composable scope 级别获取,并在离开 scope 时自动清理。MVVM 的 Compose 版本称为单向数据流(UDF):ViewModel 发布 StateFlow,Composable 函数通过 collectAsState() 订阅。Reducer 方法的 Compose 变体 — 带有 ViewModel 的 MVI。
禁止存储对 Activity、Fragment、View、Context(Application 除外)的引用。这会导致内存泄漏,因为 ViewModel 生命周期比 UI 上下文更长。不要存储 View 的序列化状态(例如 RecyclerView 位置) — 使用 LayoutManager.onSaveInstanceState()。避免存储大量数据(超过 10 MB) — 进程最小化时,没有 SavedStateHandle 的数据将丢失。
ViewModel 像普通 Kotlin 类一样测试,无需模拟器:创建实例,调用方法,检查 LiveData 或 StateFlow 的状态。对于协程测试,使用 kotlinx-coroutines-test 中的 runTest 配合 TestDispatcher。对于带有 Hilt 的 ViewModel,使用 @HiltViewModelTest 和测试 fragment 中的 hiltViewModel()。根据 Google 的数据,单元测试无需仪器测试即可覆盖 ViewModel 逻辑的 80–90%。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。