本系列为小说《逆袭西二旗》的技术讲解,用于详细说明剧情里涉及的开发细节。
ViewBinding 有什么优势

ViewBinding 是 Android 中用于简化访问布局中 View 过程的一项能力。
它避免手动多次调用 findViewById(),并以更类型安全的方式获取视图,从而减少样板代码并降低运行时错误风险。
面试一问
与 findViewById() 相比,ViewBinding 在类型安全与空安全上如何改进?
带来的收益是什么?
工作方式
在搞明白上面的问题之前,我们先来看看它的工作方式。
在项目中启用 ViewBinding 后,Android 会为每个 XML 布局文件生成一个 binding 类。该类的命名来自布局文件名:下划线会转换为驼峰,并在结尾追加 Binding。
例如,布局为 activity_main.xml 时,生成类为 ActivityMainBinding。
binding 类包含布局中各视图的引用,你可以直接访问它们,而无需做类型强转或再调用 findViewById()。
kotlin
// 在 Activity 中的使用示例
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.textView.text = "Hello, ViewBinding! 关注 RockByte 公众号呀!"
}
}
上例中,ActivityMainBinding 对应 activity_main.xml 布局。inflate() 用于创建 binding 实例;binding.root 传给 setContentView() 以设置内容视图。
优势
- Type Safety(类型安全): 直接访问视图、避免强制类型转换,减少类型不匹配带来的运行时问题。
- Cleaner Code(代码更干净): 减少
findViewById()等样板。 - Null Safety(空安全): 对可空视图更友好,降低与可选 UI 交互时出错风险。
- Performance(性能): 与 DataBinding 不同,ViewBinding 运行时额外开销很小,因为它不处理数据绑定表达式,也不做额外的绑定同步。
对比 DataBinding
DataBinding 能力更强,例如支持绑定表达式、双向数据绑定,但也更复杂并带来运行时成本。而 ViewBinding 更专注于让视图访问更简单、运行更轻。当你不需要更高级的数据驱动绑定时,它更合适。
启用 ViewBinding
在 build.gradle 中加入如下配置以启用 ViewBinding:
gradle
android {
buildFeatures {
viewBinding = true
}
}
启用后,项目中的 XML 布局会对应自动生成 binding 类。
补充提示:
在 Google 官方正式支持 ViewBinding 之前,ButterKnife 很常用:通过注解处理将 View 注入到字段,从而绕开
findViewById,并借助类似依赖注入的方式提供类型安全。它一度是 Android 生态中非常重要的库,也几乎是奠定 Jake Wharton 影响力的重要项目之一,并在 Android 开发里激发了大量实践与变化。
虽然随着 ViewBinding 被官方采纳,ButterKnife 已被标记弃用,但在当时它非常有创新性;对希望了解依赖注入模式的人来说,也仍然值得学习。
总结
ViewBinding 是一种更轻、类型更安全的视图交互方式,也能改善代码安全性。它可作为 findViewById() 的替代方案,当你不需要 DataBinding 的高级能力时,是很合适的选择。
启用 ViewBinding 能简化 UI 相关代码,并提升可维护性与运行时安全。
在使用 XML 布局时,我一定会启用 ViewBinding ,反而是 DataBinding,我几乎不用。
DataBinding 是如何工作的
既然刚学完 ViewBinding ,那么我们立即看看 DataBinding。
DataBinding 是 Android 提供的能力,让 XML 布局中的 UI 组件可以直接与数据源建立绑定。它通过减少 findViewById() 等样板,并在 UI 与数据模型间支持更实时的变化同步,为 UI 设计带来更偏"声明式"的写法。
此外,它也在 MVVM(Model-View-ViewModel)架构中扮演重要角色。
大家知道吗? MVVM 最初是由 Microsoft 作为分离 UI 逻辑与业务逻辑的模式提出的。
面试一问
DataBinding 与 ViewBinding 的关键差异是什么?如何取舍?
DataBinding 在 MVVM 中扮演什么角色?它如何帮助在 Android 开发里分离 UI 逻辑与业务逻辑?
启用 DataBinding
和 ViewBinding 类似,在 build.gradle 中加入如下配置以启用 DataBinding:
gradle
android {
buildFeatures {
dataBinding = true
}
}
工作原理
DataBinding 会对使用 <layout> 的 XML 布局生成 binding 类。该类能直接提供视图访问,并允许在 XML 中使用表达式完成数据绑定。
下面展示一个典型的 DataBinding XML 布局示例:
xml
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.rockbyte.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.age}" />
</LinearLayout>
</layout>
在这个例子里,一个 User 对象与 XML 布局绑定,并在 TextView 中显示 user.name 与 user.age。
在代码中绑定数据的示例:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val user = User("Alice", 25)
binding.user = user
}
}
data class User(val name: String, val age: Int)
这里将 user 作为布局的数据源,用于完成初始展示;如果希望后续数据变化继续驱动 UI 更新,需要使用 LiveData、ObservableField、BaseObservable 等可观察数据源。
特性
-
Two-Way Data Binding(双向数据绑定): 在 UI 与数据模型间自动保持同步,常用于表单、输入等场景。
xml<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={user.name}" /> -
Binding Expressions(绑定表达式): 在 XML 中写简单逻辑,例如条件判断等。这确实是 DataBinding 强大功能的一个展现。
xml<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.age > 18 ? `Adult` : `Minor`}" /> -
Lifecycle Awareness(生命周期感知): 当结合
LiveData并设置lifecycleOwner时,可以按生命周期状态观察数据并更新 UI。
优势
- Reduces Boilerplate(减少样板): 减少
findViewById()与大量手动更新 UI 的代码。 - Real-Time UI Updates(更实时的 UI 更新): 可观察数据变化时,UI 能自动体现。
- Declarative UI(声明式 UI): 将部分展示逻辑放到 XML 中,便于组织复杂布局。
- Improves Testability(可测试性更好): UI 与代码解耦,有利于分别测试。
缺点
- Performance Overhead(性能开销): 相比更轻量方案(如仅 ViewBinding)有额外运行时成本。
- Complexity(复杂度): 小型/简单项目可能带来不必要复杂度。
- Learning Curve(学习曲线): 需要理解绑定表达式与生命周期等问题。
总结
DataBinding 让你可以把 UI 元素在 XML 中直接绑定到数据源,减少样板,并以更偏声明式的方式管理 UI。
它支持双向绑定、表达式等能力,是动态、交互式界面的强工具。但也会带来复杂性与运行开销;当应用需要更实时、交互性更强的 UI 且希望减少手动控制时,它是很合适的选择。
进阶:ViewBinding 与 DataBinding
ViewBinding 与 DataBinding 都用于减少视图相关样板,但目标与能力集不同。下面是更细的对照说明。
ViewBinding 用于简化在布局中访问 View:为每个带 id 的视图生成更直接的引用,避免 findViewById,提升类型安全并减少重复代码。
ViewBinding 的关键特点:
- 为各 XML 布局生成
binding类。 - 提供对布局中视图的直接引用,绕开
findViewById。 - 通过编译期信息提升类型/空值方面的安全性(相对手写查找)。
- 不自带绑定表达式/数据驱动更新等高级能力。
DataBinding 更复杂、也更强,它允许把 UI 与数据源在 XML 中绑定,支持绑定表达式、可观察数据、双向数据绑定,因此也更常见于 MVVM 等场景。
DataBinding 的关键特点:
- 在 XML 中把 UI 与数据源建立绑定关系。
- 通过表达式在 UI 上表达动态行为。
- 提供双向数据绑定,让 UI 与数据更实时同步。
- 可与
LiveData、ObservableField、BaseObservable等结合做可观察的数据流。
它们之间存在如下的关键差异:
- 目的: ViewBinding 偏"更安全的取
View";DataBinding 偏"数据驱动 UI 绑定与更新"。 - 生成内容: ViewBinding 主要生成视图引用;DataBinding 还会围绕数据生成额外绑定能力(不止视图引用本身)。
- 表达式: ViewBinding 不在 XML 中执行表达式;DataBinding 支持。
- Two-Way Data Binding(双向绑定): 只有 DataBinding 提供。
- 性能: 通常 ViewBinding 更轻,因为不跑数据绑定那条解析/同步链路。
简单来说。
需要简单取引用、不依赖 findViewById 时,用 ViewBinding (更偏"简单项目/场景")。在复杂数据驱动 UI、或 MVVM 中需要与 LiveData / ObservableField / @Bindable 等结合时,用 DataBinding 更对口。
但 DataBinding 也带来额外成本;若 ViewBinding 已足够,就没必要为数据绑定多付那部分复杂度。
LiveData
LiveData 是 Jetpack 架构组件里提供的一种可观察数据容器。它具备生命周期感知能力,会关注与之关联的 LifecycleOwner(如 Activity、Fragment 或 viewLifecycleOwner)的生命周期,并倾向于只在组件处于活跃生命周期状态(如 started / resumed 等)时,才把更新投递给 UI,从而降低不合适时机更新 UI 带来的风险。
LiveData 的核心用途之一,是让 UI 能观察数据变化,并在数据变化时自动自我更新。这也是 Android 上实现更"响应式" UI 的重要工具之一。
LiveData 的一些优势包括:
- Lifecycle Awareness(生命周期感知): 在组件不活跃时减少不必要更新,有助于降低崩溃与泄漏风险等。
- Automatic Cleanup(自动清理): 与某个生命周期绑定的观察者在销毁时更易于被处理。
- Observer Pattern(观察者模式): 数据变化时,可通过观察者让 UI 自动跟随更新。
- Thread Safety(线程安全设计): 设计上也考虑到来自后台线程更新数据等需求。
以下示例展示如何在 ViewModel 中用 LiveData 管理 UI 相关数据:
kotlin
// ViewModel
class MyViewModel : ViewModel() {
private val _data = MutableLiveData<String>() // 内部可变的 MutableLiveData
val data: LiveData<String> get() = _data // 对外暴露为 LiveData,避免外部直接写入
fun updateData(newValue: String) {
_data.value = newValue // 更新 LiveData 值
}
}
// Fragment 或 Activity
class MyFragment : Fragment() {
private val viewModel: MyViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 观察 LiveData
viewModel.data.observe(viewLifecycleOwner) { updatedData ->
// 用新数据更新 UI
textView.text = updatedData
}
}
}
上例中,MyViewModel 保存数据,Fragment 通过观察 LiveData 在 updateData() 被调用时自动刷新 UI。
上述代码中,你会看到 MutableLiveData 与 LiveData,这两者的差别其实很简单:
- MutableLiveData: 通常可用
setValue()/postValue()修改;常见实践是在ViewModel中内部持有可变版本,并对外用只读LiveData暴露,避免被外部直接改写。 - LiveData(只读暴露): 更强调封装与避免外部非预期写入。
类似 MutableList 与 List 的差别。
面试一问
LiveData 如何体现生命周期感知?与 RxJava 、EventBus 相比,优势在哪里?
setValue() 与 postValue() 在 LiveData 中有什么区别?各自更适用于什么时候?
LiveData 有哪些局限?当多个 UI 事件(导航、Toast 等)需要被观察,又不希望在配置变化后被重复触发时,你通常会怎么处理?
使用场景
- UI 状态管理: 把网络/数据库等来源数据放入
LiveData,与 UI 绑定,数据变化时界面同步更新。 - 观察者模式落地: 作为发布/订阅里的一方,在值变化时通知订阅方;适合更动态的 UI/交互数据。
- One-time events(一次性事件): 例如 Toast、跨页面导航等;这类场景也常见
SingleLiveEvent等变体/封装,以避免重复触发等问题。
额外知识
LiveData 常会被拿来与 Kotlin 的 StateFlow、SharedFlow 对比,不少开发者会迁移到 Flow 或正考虑迁移。
有人会觉得 StateFlow 让 LiveData "过时了",这句话从趋势上看有一定道理。不过有一点你需要搞清楚,Flow 是平台无关的,本身并不了解 Android 生命周期,因此收集与取消需要你在生命周期边界下显式处理。
没有可靠的生命周期感知式收集,Flow 可能在 UI 不活跃时仍继续收集,从而增加资源浪费甚至泄漏等风险。相比之下,LiveData 会绑定到 LifecycleOwner 并更自动地处理取消订阅,因此在不少场景下仍然更"省心"、也更实际。
另有一种说法是"从 LiveData 迁到 Flow 就能拿掉 Android 依赖",这句话不一定站得住脚:不是所有人都有跨平台的需求。
在实践中,LiveData 往往只出现在 ViewModel 边界,是否要把网络/数据/领域层都换成更 JVM-only 的模块化架构,以及收益是否足够明确,也因人而异。
所以不必盲目追热点,当你真的需要 Flow,并且确实能拿到 Flow 的收益(更复杂数据转换、更丰富的响应式流处理等)时,再引入会更合适。
总结
LiveData 让构建 响应式 + 更生命周期安全 的 UI 状态更简单:用一种更"生命周期有感知"的方式观察数据变化,减少样板并降低在错误生命周期更新等带来的风险。它也因此成为现代 Android 开发、尤其是 MVVM 中的常用组件之一。
进阶:setValue 与 postValue
在 LiveData 中,两者都用于更新所持有的值,但它们的线程语义与使用场景不同。
setValue
setValue() 用于同步更新值,并且必须在主线程调用。当你希望立刻让观察者在本帧/同步路径里收到更新,它更合适。
kotlin
val liveData = MutableLiveData<String>()
fun updateOnMainThread() {
liveData.setValue("Updated Value") // 必须在主线程调用
}
这适合已经在主线程触发的更新(例如直接 UI 事件、生命周期回调内的同步更新等)。在后台线程调用 setValue() 通常会直接出错。
postValue
postValue() 用于在后台线程投递一个会在主线程生效的更新,从设计上更适合后台线程,避免在后台线程里手动切回主线程。
postValue() 不阻塞当前调用线程,而是把值投递到主线程去应用。
kotlin
val liveData = MutableLiveData<String>()
fun updateInBackground() {
Thread {
liveData.postValue("Updated Value") // 可在任意线程调用
}.start()
}
在涉及网络、数据库等后台工作时,postValue() 很常用,因为它不需要你显式再切主线程去更新 LiveData。
如果查看 postValue() 的内部实现,它通常会通过执行器/调度,把"在主线程上应用新值"这件事投递出去:
java
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
它会通过 mDataLock 同步、更新 mPendingData,并判断是否需要向主线程投递任务;也解释了为何短时间内多次 postValue() 可能只让观察者看到"最后那次"的合并行为(以内部实现为准)。
很多开发者可能会使用 postValue() 去投递序列。现在你知道了吧,这个做法其实有隐患。
如果下面这段运行在主线程:
kotlin
liveData.postValue("a")
liveData.setValue("b")
"b" 会立刻被设置,但之后主线程若仍处理到 postValue("a") 投递来的任务,最终值可能被覆盖为 "a"。原因是 postValue() 的异步调度与 setValue() 的同步更新交织。
关键差异
| 方面 | setValue | postValue |
|---|---|---|
| 线程 | 必须在主线程调用 | 可在任意线程调用 |
| 同步/异步 | 同步立即更新 | 异步投递到主线程再更新 |
| 使用场景 | 主线程上发起的更新(交互、生命周期等) | 后台线程/异步工作完成后要更新到 UI 侧时 |
| 观察者通知 | 同帧/同步路径上更快触发 | 主线程处理投递任务后触发(与帧调度相关) |
用法建议
- 用
setValue(): 更新已在主线程产生(用户交互、生命周期内同步结果等)。 - 用
postValue(): 在后台线程拿到结果后,需要把值安全送到主线程侧(网络、DB、长任务等)。
总之。
setValue() 与 postValue() 都用于更新 LiveData 值,但线程模型不同:前者主线程同步,后者可跨线程并以投递方式落到主线程。选对了才能既线程安全,又让数据更新对 UI 更平滑。
ViewModel 是什么

Jetpack ViewModel 是 Android 架构组件中的关键能力之一,用于在与生命周期更匹配的边界内保存与管理 UI 相关数据。
这个架构组件有多重要?AndroidX 甚至在跨平台方向上也保留了 ViewModel。
它把 UI 侧逻辑与业务侧逻辑更清晰地分层,并尽量让数据在配置变化(如屏幕旋转)中得以保留,从而帮助应用更稳、更易于维护。
ViewModel 的核心目标之一,是在配置变化期间保留与 UI 强相关的数据:例如用户旋转设备导致 Activity / Fragment 重建时,ViewModel 仍可能保留关键状态,避免你重复拉取/重复计算。
kotlin
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// 暴露屏幕 UI 状态
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// 处理业务逻辑
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
这个例子说明:即使 Activity 因配置变化重建,状态仍可能由 ViewModel 维持。
面试一问
ViewModel 如何在配置变化中"持久化"UI 相关数据?与 onSaveInstanceState() 等保存方式的核心差异是什么?
ViewModelStoreOwner 的用途是什么?同一 Activity 下多个 Fragment 间如何共享 ViewModel?
在 ViewModel 内用 StateFlow 或 LiveData 管理 UI 状态,各自优势与可能踩坑分别是什么?
特性
- Lifecycle Awareness(生命周期作用域):
ViewModel通常与某个Activity/Fragment的作用域绑定,并在对应 UI 组件不再需要时被清理(如用户离开页面)。 - Persistence Across Configuration Changes(跨配置变化保留状态): 与会在配置变化中销毁/重建的
Activity/Fragment不同,ViewModel往往更能保留其内部状态,减少丢数据/重复重取。 - Separation of Concerns(关注点分离): 把与 UI 展示强相关的状态/逻辑与业务逻辑分层,让 UI 侧观察/订阅
ViewModel输出,更利于用响应式思路组织代码。
我们来看一下它的创建方式和使用示例。
在 ComponentActivity 上,可以用 Kotlin 的 by viewModels() 委托来创建 ViewModel(来自 activity-ktx 等 Jetpack 扩展能力),让代码更干净:
kotlin
class DiceRollActivity : AppCompatActivity() {
// 使用 `by viewModels()` Kotlin 属性委托
// 来自 activity-ktx
private val viewModel: DiceRollViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 系统第一次调用该 Activity 的 onCreate 时创建 ViewModel。
// 配置变化导致 Activity 重建时,会拿到同一份 DiceRollViewModel 实例。
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// 更新 UI
}
}
}
}
}
ViewModel 实例会绑定在 ViewModelStoreOwner 上:这个 owner 可以是 Activity、Fragment、Navigation 图、图中某个 destination,甚至是自定义 owner。Jetpack 也提供了很丰富的"作用域化取 ViewModel"的方式。
图解 ViewModel

该图列出了 Jetpack 中与 ViewModel 相关的 API、返回实例的作用域,以及使用示例。并提示可将 ViewModel 挂到 ViewModelStoreOwner 上,owner 可以是 Activity、Fragment、Navigation 图/destination 或自定义 owner。
在 "Get an instance in Jetpack Compose" 中(lifecycle-viewmodel-compose)包含 viewModel()(取最近 ViewModelStoreOwner)与 viewModel(vmStoreOwner)(指定任意 ViewModelStoreOwner,示例含 Navigation backStackEntry / 父级图路由等)等要点;在 "Compose + Navigation + Hilt "(hilt-navigation-compose)中强调与 Hilt + Navigation 同用时可用 hiltViewModel()(并配套示例)。
在 "Core ViewModel API"(lifecycle-viewmodel-ktx)展示 ViewModelProvider 等。
在 "Get an instance in an Activity"(activity-ktx)展示 by viewModels()。
在 "Get an instance in a Fragment"(fragment-ktx)展示 by viewModels()、带 ownerProducer 的变体、以及 by activityViewModels();在 "Fragment + Navigation"(navigation-fragment)展示 by navGraphViewModels;在 "Fragment + Navigation + Hilt"(hilt-navigation-fragment)展示 by hiltNavGraphViewModels,并说明 Hilt + Navigation 场景下应优先使用这些 API,而非简单 navGraphViewModels;不挂在 NavGraph 的实例则仍可用 viewModels() / activityViewModels()。
该图内容较多,但是并不复杂,各位不必一次性看完。
总结
Jetpack ViewModel 用于管理 UI 相关数据,并尽量在配置变化中连续保留(例如屏幕旋转时不必因重建就把状态全丢回初始)。它是生命周期有感知的一类状态容器/协调者,也更常与 MVVM 等架构模式一起使用:通过保留状态与更清晰的边界,让状态管理与开发体验都更顺。
进阶:ViewModel 的生命周期
ViewModel 的生命周期与其 ViewModelStoreOwner 关联;这个 owner 可以是 Activity、Fragment,或其他与生命周期相关的承载者。
只要 ViewModelStoreOwner 仍在作用域中,ViewModel 就仍然存在,让数据与状态在配置变化(如屏幕旋转)中得以保留。
这种设计让 ViewModel 成为管理 UI 相关数据、并在此类变化中保持状态的重要组件。
例如,对 Activity 来说,ViewModel 往往持续到该 Activity 被真正结束并清理对应的 ViewModelStore。于是数据与状态也能够在配置变化期间被保留下来。

上图概览了 ViewModel 在 Activity 生命周期中的存续区间:它展示 ViewModel 如何随 Activity 的生命周期事件被创建并保留,即便 Activity 被暂时销毁后重建,实例仍可能延续。虽然示例是 Activity,但同样适用于 Fragment 以及作为 ViewModelStoreOwner 的其它承载者。
从图中看,左侧是 Activity 生命周期与关键节点:从 Activity created 开始,经 onCreate → onStart → onResume;Activity rotated 时经历 onPause → onStop → onDestroy,随后重建的 Activity 继续 onCreate → onStart → onResume;在更靠下的位置有 finish() 触发,并最终 onPause → onStop → onDestroy 以进入 Finished。右侧/中部是贯穿的 ViewModel Scope 区域,覆盖初次创建、旋转与重建;清理点以 onCleared() 为标志。
在 ViewModelStoreOwner 第一次被创建时,ViewModel 会被初始化。只要 owner 仍在内存中,通常会保留同一份 ViewModel 实例。
若发生配置变化(如旋转设备),owner 可能重建,但已存在的 ViewModel 往往被复用,避免重新加载或重新初始化数据。这种复用通常带来更好的性能与更顺滑的体验。
最后,当 ViewModelStoreOwner 被永久销毁时(例如 Activity 被 finish、或 Fragment 从父级中移除且预期不再回来),ViewModel 会被清理。
此时会调用其 onCleared(),用于取消协程、释放长期持有的资源等,以降低内存泄漏风险。ViewModel 的生命周期因此有助于在状态续存 与资源管理之间取得平衡。
进阶:ViewModel 为什么会被保留
在 Android 中,Jetpack ViewModel 被设计为能挺过屏幕旋转、系统语言/区域变更等配置变化。
为某个 UI 组件(如 Activity/Fragment)创建 ViewModel 时,它会绑定到该组件提供的 ViewModelStoreOwner:对 Activity 来说,这个 owner 通常由 ComponentActivity 表示;对 Fragment 来说则是 Fragment 自身。
更底层的关键是 ViewModelStore:它会在配置变化中被保留,使 ViewModel 在界面重建时仍有机会保留其内部数据,而不必每次从零开始。
在 Jetpack 实现里,androidx.activity.ComponentActivity 与 androidx.fragment.app.Fragment 都实现了 ViewModelStoreOwner:让 Activity/Fragment 拥有自己的 ViewModelStore,以在配置变化中持续持有 ViewModel 实例。ViewModelStore 内部以 Map(key 为 String)管理实例,如:
kotlin
public open class ViewModelStore {
private val map = mutableMapOf<String, ViewModel>()
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun put(key: String, viewModel: ViewModel) {
val oldViewModel = map.put(key, viewModel)
oldViewModel?.clear()
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public operator fun get(key: String): ViewModel? {
return map[key]
}
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public fun keys(): Set<String> {
return HashSet(map.keys)
}
public fun clear() {
for (vm in map.values) {
vm.clear()
}
map.clear()
}
}
ComponentActivity 还会观察自身生命周期:在 ON_DESTROY 时,会清理 Context 相关帮助器,并且若不是配置变化导致的销毁,会清理 ViewModelStore,以释放 ViewModel 实例:
java
getLifecycle().addObserver(new LifecycleEventObserver() {
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_DESTROY) {
// 清理可用 context
mContextAwareHelper.clearAvailableContext();
// 并清理 ViewModelStore
if (!isChangingConfigurations()) {
getViewModelStore().clear();
}
mReportFullyDrawnExecutor.activityDestroyed();
}
}
});
总之。
ViewModel 之所以能在配置变化中"活下来",与 ViewModelStore 的持有方式密切相关:由绑定生命周期的 Android 组件管理。
在 Compose Navigation 中,ViewModelStore 也会与导航路由/作用域产生关联,以按导航作用域保留状态。你当然也能手动管 ViewModelStore,但配置变化下正确"找回/复用"往往更绕,一般不建议自研乱改。
进阶:Jetpack 与 Microsoft
根据 Android 官方文档的表述,Jetpack 的 ViewModel 是一个生命周期有感知的组件,适合作为 业务逻辑 / 屏级状态 的承载体:对 UI 暴露状态,并封装与界面展示强相关的业务逻辑。
它的强项之一,是把状态缓存/延续在配置变化(如旋转、Activity 重建等)中,让 UI 不必无意义地重复取数/重复计算。总体上,它更"Android 化"、更围绕生命周期来组织状态。
而 MVVM(Model-View-ViewModel) 最初由 Microsoft 系统阐述时,其中的 ViewModel 更偏"View 与 Model 的桥梁":实现可绑定到 View 的属性/命令,并通过通知机制把状态变化告知 View。
它要协调 View 对 Model 的访问,并尽量把业务逻辑从直接操控 UI 中抽离。与 Jetpack ViewModel 相比,MVVM 的 ViewModel 更强调数据绑定与被动 UI 这套机制,而不是 Android 上"过配置不丢"的那条线。

从图中可以看到,三个框为 View、ViewModel、Model。View 到 ViewModel 的实线箭头标为 Data Binding and Commands ;ViewModel 到 Model 的实线箭头标为 ViewModel updates the model ;Model 到 ViewModel、ViewModel 到 View 的虚线箭头标为 Send notifications。
在根本层面,二者同名但范式不同:Jetpack ViewModel 更强调 Android 上的生命周期有感知状态 与跨配置续存;MVVM 模式里的 ViewModel 更强调 绑定/命令/解耦 以让 View 更被动。
名字相似不代表"只用它就等于 MVVM "。要更接近 MVVM 的原始目标,你往往还需要额外的数据绑定/命令体系,让 UI 能被动地响应 ViewModel 提供的数据,并把业务逻辑与 UI 层更清晰地分层,从而提高可测性与可维护性。
简单来说,ViewModel + Flow/LiveData 可以承担 MVVM 中 ViewModel 的一部分职责,但二者不能简单画等号。
总的来说。
二者范围与落点不同:Jetpack ViewModel 更服务 Android 上"UI 相关状态在生命周期/配置变化下如何更稳地组织";MVVM 的 ViewModel 更服务"Model-View-ViewModel 之间如何用绑定把 UI 做声明式/被动化"。
在 Jetpack Compose 中,更常见的是直接从 ViewModel 观察流/状态来驱动 UI;在传统 XML 上,要更贴近经典 MVVM 的被动 UI,常还需要 DataBinding 等能力配合。
对于开发者来讲,你仍应按架构与 UI 技术栈选择更合适的组合。