Android Jetpack 系列(八)ViewModel 视图模型
但在 Kotlin 中,Jetpack 已经为常见场景(如 Activity、Fragment、Compose)提供了更简洁的扩展函数:by viewModels() (Compose 版是 viewModel())。在应用开发中,随着业务逻辑的复杂化,我们往往需要管理越来越多的数据状态,例如网络请求结果、界面输入内容、滚动位置、用户交互状态等。MVVM 架构中引入了 ViewModel,它从视图
1. 背景
在应用开发中,随着业务逻辑的复杂化,我们往往需要管理越来越多的数据状态,例如网络请求结果、界面输入内容、滚动位置、用户交互状态等。这些数据通常会随着 Activity 或 Fragment 的生命周期而变化,如果处理不当,就容易出现数据丢失、界面闪烁、甚至内存泄漏等问题。
为了解决这些痛点,我们先简单回顾一下常见的应用架构演进过程。
MVC(Model-View-Controller)
MVC 是最早期的一种架构设计,View 负责界面展示,Model 负责数据处理,Controller 负责协调二者。
但在 Android 中,Activity/Fragment 通常既充当 View,又承担 Controller 的部分逻辑,结果导致代码臃肿,UI 与业务逻辑高度耦合。
MVP(Model-View-Presenter)
MVP 在 Android 中得到了广泛应用,Presenter 负责业务逻辑,View 只负责渲染界面。
虽然一定程度上解决了 Activity 的臃肿问题,但 Presenter 与 View 仍存在强引用关系,生命周期绑定困难。
例如当 Activity 重建(屏幕旋转)时,Presenter 需要重新创建、重新绑定,状态恢复变得繁琐。
MVVM(Model-View-ViewModel)
MVVM 架构中引入了 ViewModel,它从视图层中抽离出“可感知生命周期的状态持有者”,通过数据驱动(通常搭配 LiveData)来自动更新 UI。这让 UI 逻辑更轻,数据与生命周期解耦,成为现代 Android 推荐的架构模式之一。
2. 了解 ViewModel
2.1 ViewModel 是什么
ViewModel 是 Android Jetpack 架构组件中的核心之一,它是一个专为保存和管理界面状态(UI State) 而设计的类。它与 Activity 或 Fragment 的生命周期绑定,但可以在配置变化(如屏幕旋转)时保持数据不丢失。
简单来说:ViewModel = UI 状态缓存器 + 业务逻辑中枢。
2.2 ViewModel 的作用与优势
1. 状态持久性,保存界面相关数据,避免因配置变化导致丢失。
2. 业务逻辑集中,将业务逻辑与界面解耦,Activity 仅负责展示,提升架构清晰度与可测试性。
3. 生命周期感知,不用持有 View 引用,可与 LiveData 搭配实现数据驱动 UI
3. 实现 ViewModel
下面通过一个简单的示例来演示如何在项目中使用 ViewModel。
3.1. 定义 ViewModel 类
class CounterViewModel : ViewModel() {
private var _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increase() {
_count.value = (_count.value ?: 0) + 1
}
}
这里我们定义了一个 CounterViewModel,用于保存计数数据。
它内部持有一个 MutableLiveData,对外只暴露不可变的 LiveData,体现“数据内部可改、外部只读”的设计原则。
3.2. 在 Activity 中使用
3.2.1. 观察数据变化
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textCount = findViewById<TextView>(R.id.text_count)
val buttonAdd = findViewById<Button>(R.id.button_add)
viewModel = ViewModelProvider(this)[CounterViewModel::class.java]
// 观察 ViewModel 中的 count 变化
viewModel.count.observe(this) { value ->
textCount.text = "Count: $value"
}
buttonAdd.setOnClickListener {
viewModel.increase()
}
}
}
ViewModel 本身只负责数据管理,不直接更新界面。
通过与 LiveData 搭配,UI 层可以观察数据变化,实现自动刷新。
3.2.3. 结合 DataBinding 使用 ViewModel
如果你在项目中启用了 DataBinding,可以直接在 XML 中绑定 ViewModel:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.zyx.viewmodel.demo.CounterViewModel" />
</data>
<LinearLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="120dp"
tools:context=".MainActivity">
<TextView
android:id="@+id/text_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{`Count:` + String.valueOf(viewModel.count)}"
android:textSize="20sp" />
<Button
android:id="@+id/button_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{() -> viewModel.increase()}"
android:text="增加计数" />
</LinearLayout>
</layout>
Activity 绑定:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: CounterViewModel
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[CounterViewModel::class.java]
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
binding.viewModel = viewModel
}
}
说明:
binding.lifecycleOwner = this 让绑定自动响应 LiveData 更新。
结合 ViewModel + LiveData + DataBinding,可以实现响应式 UI。
4. ViewModel 的作用域
4.1. 如何创建 ViewModel 实例
上面示例中 Activity 中通过: ViewModelProvider(this)[xxx::class.java] 便创建并获取 ViewModel 实例。这里的 this 是 Activity 本身,它是一个 ViewModelStoreOwner 对象,接着中括号[ ] 对应其 get 方法。
但在 Kotlin 中,Jetpack 已经为常见场景(如 Activity、Fragment、Compose)提供了更简洁的扩展函数:by viewModels() (Compose 版是 viewModel())。 系统会自动根据当前的 ViewModelStore 创建并管理 ViewModel 实例。
class MainActivity : AppCompatActivity() {
// private lateinit var viewModel: CounterViewModel
private val viewModel: CounterViewModel by viewModels ()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// viewModel = ViewModelProvider(this)[CounterViewModel::class.java]
}
}
这些扩展函数底层仍是通过 ViewModelProvider 实现的,只是使用了 Kotlin 的委托语法进行封装。
by viewModels() 扩展函数分别定义在 androidx.activity.ComponentActivity(Activity 场景)与 androidx.fragment.app.Fragment(Fragment 场景)中,若找不到类,可手动添加依赖:
dependencies {
implementation "androidx.activity:activity-ktx:1.9.2"
implementation "androidx.fragment:fragment-ktx:1.8.2"
}
4.2. 理解作用域
ViewModel 的生命周期依附于实现了 ViewModelStoreOwner 的组件(如 Activity 或 Fragment),而非其视图。
作用域决定了:
- 数据能保存多久;
- 是否能在多个界面共享 ViewModel 实例。
4.3. ViewModelStoreOwner
ViewModelStoreOwner 是一个接口,定义了一个组件是否能“拥有” ViewModel。Activity 和 Fragment 都实现了它,因此都可以作为 ViewModel 的持有者。
通常情况下,我们获取的 ViewModel 都限定在最近的 ViewModelStoreOwner 上。
例如:
- 在 Activity 中使用 by viewModels(),则作用域限定为该 Activity;
- 在 Fragment 中使用 by viewModels(),则作用域限定为该 Fragment。
有时我们希望手动指定作用域,比如让子 Fragment 共享父 Fragment 的 ViewModel。
这时可以使用 ownerProducer 参数来决定作用域的归属。
class MyFragment : Fragment() {
// ViewModel 作用域限定为父 Fragment
val viewModel: MyViewModel by viewModels(
ownerProducer = { requireParentFragment() }
)
}
同理,如果要从 Fragment 获取作用域限定为 Activity 的 ViewModel,可以使用 by activityViewModels():
class MyFragment : Fragment() {
// ViewModel 作用域限定为宿主 Activity
val viewModel: MyViewModel by activityViewModels()
}
这是一种常见的:跨 Fragment 共享 ViewMode 的方式,例如在多个页面之间共享用户选择或搜索条件。
5. ViewModel 的生命周期
5.1. 生命周期关系
ViewModel 的生命周期与其宿主(即 ViewModelStoreOwner)绑定。
对 Activity 而言:当 Activity 销毁时,ViewModel 才会被清除;
对 Fragment 而言:当 Fragment 从 FragmentManager 分离且不再返回时,ViewModel 才会被清除。
下图说明了activity 经历屏幕旋转而后结束时所处的各种生命周期状态。该图还在关联的 activity 生命周期的旁边显示了 ViewModel 的生命周期。此图表说明了 activity 的各种状态。这些基本状态同样适用于 fragment 的生命周期。

通常在系统首次调用 activity 对象的 onCreate() 方法时请求 ViewModel。系统可能会在 activity 的整个生命周期内多次调用 onCreate(),如在旋转设备屏幕时。ViewModel 存在的时间范围是从你首次请求 ViewModel 直到 activity 完成并销毁。
当 Activity 被真正销毁(例如用户退出页面、进程被杀)时,系统才会调用 ViewModel 的 onCleared() 方法:
override fun onCleared() {
super.onCleared()
Log.d("CounterViewModel", "onCleared called")
}
可以在这里释放资源、取消协程或关闭流。
5.2. 协程作用域 viewModelScope
ViewModel 自带一个 viewModelScope 协程作用域,完整路径是:androidx.lifecycle.viewModelScope,它的生命周期与 ViewModel 的生命周期同步,会在 ViewModel 被销毁时也自动取消,从而避免内存泄漏情况:
class CounterViewModel : ViewModel() {
……
fun loadData() {
viewModelScope.launch {
val result = repository.getData()
_count.value = result.size
}
}
}
这让 ViewModel 天然适合与 Kotlin 协程结合使用,异步逻辑更加安全简洁。
如果你想使用自定义作用域(而不是 androidx.lifecycle.viewModelScope)使测试更简单,ViewModel 可以在其构造函数中接收 CoroutineScope 作为依赖项。如果 ViewModelStoreOwner 在 ViewModel 的生命周期结束时清除 ViewModel,ViewModel 也会取消 CoroutineScope。
class CounterViewModel(private val viewModelScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)) : ViewModel() {
private var _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increase() {
_count.value = (_count.value ?: 0) + 1
}
fun loadData() {
viewModelScope.launch {
val result = repository.getData()
_count.value = result.size
}
}
override fun onCleared() {
viewModelScope.cancel()
}
}
从 Lifecycle 版本 2.5 及更高版本开始,你可以将一个或多个 Closeable 对象传递给在清除 ViewModel 实例时自动关闭的 ViewModel 构造函数。
class CloseableCoroutineScope(context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
open class BaseViewModel(protected val viewModelScope: CoroutineScope = CloseableCoroutineScope()) : ViewModel(viewModelScope as Closeable?) {
init {
if (viewModelScope is Closeable) {
// 可关闭 ViewModelScope,在 CloseableCoroutineScope 内的 close 方法内已执行了 coroutineContext.cancel(),所以不再需要在 onCleared 生命周期去 cancel
super.addCloseable(viewModelScope)
}
}
// /**
// * ViewModel 销毁,如果用 addCloseable 就不需要了 viewModelScope.cancel()
// */
// override fun onCleared() {
// super.onCleared()
// viewModelScope.cancel()
// }
}
class CounterViewModel() : BaseViewModel() {
private var _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increase() {
_count.value = (_count.value ?: 0) + 1
}
fun loadData() {
viewModelScope.launch {
val result = repository.getData()
_count.value = result.size
}
}
}
6. 创建具有参数的 ViewModel
在实际项目中,很多 ViewModel 不只是简单地保存界面状态,还可能需要依赖仓库(Repository)或者其他外部参数,这些依赖往往涉及数据访问层、网络层或系统服务。此时,就需要通过自定义 ViewModelProvider.Factory 接口,实现替换 create(Class<T>, CreationExtras) 方法来创建带参数的 ViewModel 实例。
6.1. 传入 Application 参数
以下示例展示了如何创建 ViewModel 实例时,传入 Application 对象作为参数:
class CounterViewModel(val application: Application) : ViewModel() {
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val application = checkNotNull(extras[APPLICATION_KEY])
return CounterViewModel(application) as T
}
}
}
……
}
或者,使用 ViewModel 工厂 DSL 借助更惯用的 Kotlin API 创建工厂:
class CounterViewModel(val application: Application) : ViewModel() {
companion object {
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = checkNotNull(this[APPLICATION_KEY])
CounterViewModel(application)
}
}
}
……
}
Activity中创建 ViewModel 对象:
private val viewModel: CounterViewModel by viewModels { CounterViewModel.Factory }
或者使用传统的创建 ViewModelProvider 对象时传入 ViewModelProvider.Factory 对象作为参数:
private val viewModel = ViewModelProvider(this, CounterViewModel.Factory)[CounterViewModel::class.java]
6.2. 传入自定义参数
你可以通过创建自定义键,通过 CreationExtras 将参数传递给 ViewModel。在此示例中,ViewModel 定义了一个自定义键,并在 ViewModelProvider.Factory 中使用该键。
class CounterViewModel(val application: Application, val repository: CounterRepository) : ViewModel() {
companion object {
val MY_REPOSITORY_KEY = object : CreationExtras.Key<CounterRepository> {}
val Factory: ViewModelProvider.Factory = viewModelFactory {
initializer {
val application = checkNotNull(this[APPLICATION_KEY])
val repository = this[MY_REPOSITORY_KEY] as CounterRepository
CounterViewModel(application, repository)
}
}
}
……
}
Activity中创建 ViewModel 实例并传入参数:
private val repository = CounterRepository()
val creationExtras = MutableCreationExtras().apply {
set(CounterViewModel.MY_REPOSITORY_KEY, repository)
}
private val viewModel = ViewModelProvider(this.viewModelStore, CounterViewModel.Factory, creationExtras)[CounterViewModel::class.java]
7. ViewModel 的已保存状态模块
ViewModel 能自动在配置更改(例如横竖屏切换)时保留数据。但是如果系统因为资源不足而杀掉应用的进程,ViewModel 里的数据就会被清空。
7.1. SavedStateHandle 是什么
SavedStateHandle 是一个自动保存的键值映射表,可在进程被系统终止后恢复上次状态。
savedStateHandle["query"] = "apple"
val lastQuery = savedStateHandle["query"] ?: ""
val lastQuery2 = savedStateHandle.get<String>("query")
这些数据会在系统终止进程后重新创建 ViewModel 时 自动恢复回来。
注意:
它保存的数据是「任务堆栈(task stack)」级别的。
如果你强制停止应用、从「最近任务」移除应用,或重启设备,保存的状态都会丢失。
7.1.1. 常用操作
SavedStateHandle 的常用操作有:
|
方法 |
作用 |
|
set(key, value) |
保存值 |
|
get(key) |
读取值 |
|
contains(key) |
检查是否存在某键 |
|
remove(key) |
删除指定键的值 |
|
keys() |
返回所有键名 |
7.1.2. 支持的数据类型
SavedStateHandle 内部是基于 Bundle 实现的,因此支持所有 Bundle 可序列化的类型。
|
支持类型 |
数组支持 |
|
Double / Int / Long / String / Float / Short / Byte / Char / CharSequence |
是 |
|
Parcelable / Serializable |
是 |
|
SparseArray / Binder / Bundle / ArrayList |
|
|
Size / SizeF(API 21+) |
对于自定义类,建议使用 @Parcelize 注解实现 Parcelable。
如果对象既不是 Parcelable 也不是 Serializable,可以自己注册保存逻辑:
savedStateHandle.setSavedStateProvider("key") {
Bundle().apply {
putString("path", file.absolutePath)
}
}
系统保存状态时会自动调用这个 provider 的 saveState() 方法。
重新创建后,你可以通过 savedStateHandle.get<Bundle>("key") 恢复。
完整示例(保存临时文件路径):
private fun File.saveTempFile() = bundleOf("path", absolutePath)
private fun Bundle.restoreTempFile() = getString("path")?.let { File(it) }
class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private var tempFile: File? = savedStateHandle.get<Bundle>("temp_file")?.restoreTempFile()
init {
savedStateHandle.setSavedStateProvider("temp_file") {
tempFile?.saveTempFile() ?: Bundle()
}
}
fun createOrGetTempFile(): File {
return tempFile ?: File.createTempFile("temp", null).also { tempFile = it }
}
}
7.2.在 ViewModel 中使用
从 Fragment 1.2.0 / Activity 1.1.0 起,只要你声明了这样的构造函数:
class CounterViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { ... }
系统就会自动帮你注入一个 SavedStateHandle 对象。
然后常一样创建 ViewModel
class MainActivity : AppCompatActivity() {
private val viewModel: CounterViewModel by viewModels ()
}
默认的 ViewModelProvider.Factory 能自动为具有 SavedStateHandle 构造函数的 ViewModel 注入实例。
如果你需要创建具有参数的 ViewModel,要自定义 ViewModelProvider.Factory,那么可以用 CreationExtras.createSavedStateHandle() 来获取 SavedStateHandle 实例,然后将其传入:
class CounterViewModel(private val application: Application, private val savedStateHandle: SavedStateHandle) : BaseViewModel() {
companion object {
val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
val application = checkNotNull(extras[APPLICATION_KEY])
val savedStateHandle = extras.createSavedStateHandle()
return CounterViewModel(application, savedStateHandle) as T
}
}
}
……
}
7.3. 结合可观察数据(LiveData / StateFlow)
SavedStateHandle 不仅能存取普通值,还能直接生成可观察数据流。这在和 UI 双向绑定时非常方便。
LiveData 版本
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val filteredData: LiveData<List<String>> =
savedStateHandle.getLiveData<String>("query").switchMap { query ->
repository.getFilteredData(query)
}
fun setQuery(query: String) {
savedStateHandle["query"] = query
}
}
这样,即使系统杀掉进程,用户重新打开时输入的查询条件也会恢复。
StateFlow 版本(Lifecycle 2.5.0+)
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val filteredData: StateFlow<List<String>> =
savedStateHandle.getStateFlow("query", "")
.flatMapLatest { query ->
repository.getFilteredData(query)
}
fun setQuery(query: String) {
savedStateHandle["query"] = query
}
}
与 LiveData 不同,StateFlow 更适合配合 collect() 使用,尤其是在 Compose 或协程场景中。
Compose 版本(实验性)
Lifecycle 2.5.0+ 支持将 SavedStateHandle 与 Compose 的 rememberSaveable 互通:
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
var filteredData: List<String> by savedStateHandle.saveable {
mutableStateOf(emptyList())
}
fun setQuery(query: String) {
withMutableSnapshot {
filteredData += query
}
}
}
8. 总结
ViewModel 是 MVVM 架构的核心之一,它解决了传统 Android 开发中:
- 界面状态易丢失;
- 业务逻辑分散;
- Activity/Fragment 过度臃肿等问题。
通过 ViewModel,我们可以轻松实现:
- 状态持久;
- 生命周期安全;
- 架构清晰
- 数据驱动 UI。
配合 LiveData、StateFlow、DataBinding 或 Compose,即可构建出稳定、简洁、可维护的现代 Android 应用。
更多详细的 ViewModel 介绍,请访问 Android 开发者官网。
为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐


所有评论(0)