3. 基本用法——保存简单类型
3.1 原理概述
ViewModel 借助 SavedStateHandle 在配置变更(如屏幕旋转)时保存和恢复简单类型数据。SavedStateHandle 内部维护一个 Map<String, Object> 键值存储,进程未被杀死前数据保留在内存中;进程被系统回收后,数据会序列化到 Bundle,通过 onSaveInstanceState 持久化到磁盘,重建时再反序列化恢复。
核心机制:SavedStateHandle 在 Activity 重建时自动附加到 ViewModel,开发者只需通过
getLiveData() 或 getStateFlow() 声明可观察状态,框架自动完成保存与恢复。
3.2 支持的数据范围
| 数据类型 | 是否支持 | 底层序列化方式 | 典型使用场景 |
|---|---|---|---|
| String | ✅ 完全支持 | Bundle.putString / getString | 搜索关键词、表单输入、标签 |
| Int / Integer | ✅ 完全支持 | Bundle.putInt / getInt | 计数器、分页页码、选中索引 |
| Boolean | ✅ 完全支持 | Bundle.putBoolean / getBoolean | 开关状态、展开/折叠、是否已读 |
| Float / Double | ✅ 完全支持 | Bundle.putFloat / getFloat | 滑块值、评分、滚动偏移 |
| Long | ✅ 完全支持 | Bundle.putLong / getLong | 时间戳、ID、累计时长 |
| Serializable / Parcelable | ⚠️ 支持但需注意大小 | Bundle.putSerializable / putParcelable | 复杂对象(不建议用于大对象) |
3.3 实现流程
- 创建 ViewModel:继承
ViewModel,构造函数接收SavedStateHandle参数; - 声明可观察状态:使用
getLiveData()或getStateFlow(),并传入默认值; - 在 Activity / Fragment 中获取状态:通过
viewModel.state.observe(...)或viewModel.stateFlow.collect(...)订阅; - 更新状态:调用
savedStateHandle.set()或直接通过 LiveData/StateFlow 的 setValue 更新; - 自动保存:配置变更或进程重建时,SavedStateHandle 自动将最新值持久化。
关键约束:只有在 ViewModel 构造函数中通过
SavedStateHandle 参数注入的方式,才能享受自动保存能力。手动创建 SavedStateHandle 对象不会与 Activity 生命周期挂钩。
3.4 getLiveData 与 getStateFlow 方法详解
| 方法 | 返回类型 | 生命周期感知 | 背压支持 | 适合场景 |
|---|---|---|---|---|
getLiveData(key, default) |
MutableLiveData<T> | ✅ 自动管理 | ❌ 不支持 | Java / 传统 Android 开发 |
getStateFlow(key, default) |
MutableStateFlow<T> | ✅ 需配合 repeatOnLifecycle |
✅ 天然支持 | Kotlin / 协程 / 响应式流 |
实践建议:对于简单的计数器、开关、输入框等 UI 级状态,优先使用
getStateFlow() + repeatOnLifecycle,代码更简洁且能与协程深度整合。如果项目仍以 LiveData 为主,getLiveData() 是完全兼容的选择。
3.5 默认值设置规则
默认值在首次创建 SavedStateHandle 且对应 key 不存在时生效。规则如下:
- 默认值通过方法的第二个参数传入:
getLiveData("count", 0); - 若 SavedStateHandle 中已存在该 key(如进程重建后),默认值不会覆盖已恢复的值;
- 默认值类型必须与泛型参数一致,否则编译报错或运行时 ClassCastException。
3.6 完整代码示例
CountViewModel.kt
class CountViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 使用 getLiveData — 默认值为 0
val countLiveData: LiveData<Int> =
savedStateHandle.getLiveData("count", 0)
// 使用 getStateFlow — 默认值为 "默认文本"
val textStateFlow: StateFlow<String> =
savedStateHandle.getStateFlow("text", "默认文本")
// Boolean 状态 — 默认值为 false
val isEnabledLiveData: LiveData<Boolean> =
savedStateHandle.getLiveData("enabled", false)
fun increment() {
val current = savedStateHandle.get("count") ?: 0
savedStateHandle.set("count", current + 1)
}
fun updateText(newText: String) {
savedStateHandle.set("text", newText)
}
fun toggleEnabled() {
val current = savedStateHandle.get("enabled") ?: false
savedStateHandle.set("enabled", !current)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel: CountViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// LiveData 观察
viewModel.countLiveData.observe(this) { count ->
findViewById<TextView>(R.id.tvCount).text = count.toString()
}
viewModel.isEnabledLiveData.observe(this) { enabled ->
findViewById<Button>(R.id.btnAction).isEnabled = enabled
}
// StateFlow 观察(需配合 lifecycleScope)
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.textStateFlow.collect { text ->
findViewById<EditText>(R.id.etInput).setText(text)
}
}
}
findViewById<Button>(R.id.btnIncrement).setOnClickListener {
viewModel.increment()
}
findViewById<Button>(R.id.btnToggle).setOnClickListener {
viewModel.toggleEnabled()
}
}
}
3.7 内部流程可视化
下图展示了 SavedStateHandle 在配置变更和进程重建时对 String / Int / Boolean 等简单类型的保存与恢复流程:
3.8 调试技巧
- 验证恢复是否生效:在开发者选项中开启“不保留活动”,切换后台再返回,观察状态是否正确恢复;
- 检查 SavedStateHandle 内容:在 ViewModel 中添加日志,输出
savedStateHandle.keys()查看当前所有 key 和对应值; - 默认值触发测试:首次安装启动时观察默认值是否展示,确认
getLiveData/getStateFlow的第二个参数已正确传递。
3.9 常见错误与避坑
| 错误模式 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| 忘记在构造函数声明 SavedStateHandle | ViewModel 无法获取 SavedStateHandle | 未使用 SavedStateHandle 参数注入 |
显式添加 constructor(savedStateHandle: SavedStateHandle) |
| 默认值类型与泛型不匹配 | 运行时 ClassCastException 或编译失败 | 传递了错误类型的默认值 | 保持泛型参数与默认值类型严格一致 |
| 手动创建 SavedStateHandle | 数据无法在进程重建后恢复 | 手动创建的对象不与生命周期绑定 | 始终通过 ViewModelProvider 自动注入 |
| 在 Activity 重建后未重新订阅 | UI 状态空白或显示旧值 | 观察者未在 onCreate 重新绑定 |
在 onCreate 中执行 observe / collect |
使用 getStateFlow 但未配合 repeatOnLifecycle |
协程泄漏或无法感知生命周期 | 缺少生命周期安全收集 | 使用 repeatOnLifecycle(STARTED) 包裹 |
3.10 性能与稳定性建议
- 避免在 SavedStateHandle 中保存大对象:序列化/反序列化大量数据会拖慢 Activity 重建速度,建议仅保存 String、Int、Boolean 等轻量状态;
- 不要频繁更新状态:每次
set()都会触发 LiveData / StateFlow 通知,若在循环或高频事件中更新,考虑加入防抖或去重逻辑; - 默认值使用常量:避免每次创建 ViewModel 时动态生成默认值对象,减少不必要的内存分配;
- 日志埋点监控恢复成功率:在
SavedStateHandle.keys()不为空时输出恢复日志,便于定位异常状态。
最佳实践总结:对于 String、Int、Boolean 等简单类型,
getStateFlow(key, default) + repeatOnLifecycle 的组合是最优选择,代码量少、类型安全、天然支持协程。默认值设置时务必与泛型类型一致,并确保 ViewModel 是通过 SavedStateHandle 构造注入的。