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 实现流程

  1. 创建 ViewModel:继承 ViewModel,构造函数接收 SavedStateHandle 参数;
  2. 声明可观察状态:使用 getLiveData() 或 getStateFlow(),并传入默认值;
  3. 在 Activity / Fragment 中获取状态:通过 viewModel.state.observe(...) 或 viewModel.stateFlow.collect(...) 订阅;
  4. 更新状态:调用 savedStateHandle.set() 或直接通过 LiveData/StateFlow 的 setValue 更新;
  5. 自动保存:配置变更或进程重建时,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 等简单类型的保存与恢复流程:

SavedStateHandle 简单类型保存与恢复流程 SavedStateHandle 简单类型保存 / 恢复流程图 配置变更 / 进程重建 SavedStateHandle 从 Bundle 恢复 读取 putString / putInt / putBoolean 序列化数据 key 存在 → 返回已保存值;key 不存在 → 返回默认值 getLiveData(key, default) / getStateFlow(key, default) 返回 MutableLiveData / MutableStateFlow,绑定到 ViewModel Activity / Fragment 订阅观察 LiveData.observe / StateFlow.collect → UI 更新 用户操作 → 调用 savedStateHandle.set(key, value) 自动触发 LiveData / StateFlow 更新通知 UI ⚠️ 进程不被杀死时数据在内存;进程回收时持久化到 Bundle 重建后 SavedStateHandle 自动从 Bundle 反序列化恢复 默认值生效规则 首次创建且 key 不存在 ➔ 返回默认值 进程重建且 key 存在 ➔ 返回已保存值

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 构造注入的。