2. SavedStateHandle 核心机制:原理、与 Bundle 的区别、何时使用、常见陷阱

2.1 原理

SavedStateHandle 是 Android Architecture Components 在 ViewModel 中提供的 进程存活周期级状态存储 组件。它不依赖 ViewModel 自身的 onCleared() 生命周期,而是与 Activity/Fragment 的 onSaveInstanceState 挂钩,将状态序列化并通过 Bundle 写入系统的 SavedStateRegistry。

当进程因配置变更、后台回收或系统主动销毁而被重建时,SavedStateHandle 从保存的 Bundle 中恢复数据,确保 ViewModel 内的状态不会丢失。其内部维护了一个 MutableMap<String, Object>,通过 SavedStateHandle.SAVE_AND_HANDLE 机制实现写时同步。

核心流程:
1. ViewModel 构造函数接收 SavedStateHandle 参数。
2. Activity 因配置变更/进程重建,系统触发 onSaveInstanceState → SavedStateRegistry 调用 SaveStateProvider → 序列化 handle 内所有可存活的键值对到 Bundle。
3. 重建时:系统使用 Bundle 构造新的 SavedStateHandle,ViewModelProvider.Factory 通过 SavedStateHandleProvider 注入给 ViewModel。
4. ViewModel 读取 handle.get(“key”) 即可获得恢复后的状态。
SavedStateHandle 工作原理流程图 SavedStateHandle 数据流与恢复流程 Activity 重建触发 onSaveInstanceState SavedStateRegistry SavedStateHandle 序列化 Bundle 存储(磁盘/内存) 进程重建 / 重新创建 ViewModelProvider 注入 恢复 SavedStateHandle ViewModel 读取 state 说明:SavedStateHandle 与 Bundle 生命周期一致,但可保留任意可序列化对象(需实现 Parcelable/Serializable) 注意:大型对象(如 Bitmap)不推荐存入,会导致 onSaveInstanceState 超时或 TransactionTooLargeException

2.2 与 Bundle 的区别

维度BundleSavedStateHandle
保存时机onSaveInstanceState 手动调用自动与 SavedStateRegistry 同步
恢复方式onCreate(Bundle) 手动读取构造函数注入,自动恢复
生命周期与 Activity/Fragment 实例绑定与 ViewModel 生命周期绑定(可跨越配置变更)
数据持久仅存一次,进程重建后可用同 Bundle 存储,但可通过 SavedStateHandle 保持多配置变更
线程安全主线程操作内部使用主线程,但 getLiveData() 返回 LiveData 可观察
键值操作直接 putXXX/getXXX提供 get/set + LiveData 包装
容量限制受 Binder 事务大小限制(~1MB)同 Bundle 限制,大对象同样有风险
建议: 对于需要跨配置变更且体积较小的 UI 状态(文本输入、选中项、加载状态),优先使用 SavedStateHandle。对于大量数据或需要持久化的关键业务数据,应使用 Room 或 DataStore。

2.3 何时使用 SavedStateHandle

  • 文本输入框内容 — 用户输入未提交,屏幕旋转后恢复。
  • 列表滚动位置 — RecyclerView 的 scrollState。
  • 底部导航栏选中项 — 避免每次重建都跳回首选项。
  • 网络请求状态(Loading/Success/Error)— 配合 LiveData 保持 UI 状态一致性。
  • 表单数据 — 多步骤表单,用户填写后离开再返回。
  • 对话框显示状态 — 避免重复弹窗。
不适用场景:
- 二进制大对象(图片/视频)→ 应使用临时文件或 Room。
- 敏感信息(密码/ )→ Bundle 可能被序列化到磁盘,应使用 EncryptedSharedPreferences。
- 需要长期保存的业务数据 → 应使用持久化存储(Room/DataStore)。

2.4 常见陷阱与避坑指南

  1. 忽略构造函数参数声明 — ViewModel 构造函数必须显式包含 SavedStateHandle 参数,否则框架无法正常注入。
    class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
        val count = savedStateHandle.getLiveData<Int>("count", 0)
    }
  2. 错误使用 key 冲突 — 多个 ViewModel 使用相同 key 可能导致数据覆盖。建议使用 唯一前缀 或枚举定义 key 常量。
    安全做法: 在 companion object 中定义 key 常量,如 const val KEY_USER_NAME = "user_name"
  3. 存储非序列化对象 — 对象必须实现 Parcelable 或 Serializable(推荐 Parcelable 性能更好)。
    data class User(val id: Int, val name: String) : Parcelable {
        // 实现 describeContents() 和 writeToParcel()
    }
    若类未实现 Parcelable/Serializable,运行时抛出 java.lang.RuntimeException: Parcelable encountered IOException writing serializable object
  4. 依赖 ViewModel 的 onCleared 进行状态保存 — SavedStateHandle 的保存发生在 onDestroy 之前,不要在 onCleared() 中再保存一次,否则可能写入已序列化的旧数据。
  5. 遗漏 ViewModelStoreOwner — 如果 Activity/Fragment 未正确实现 ViewModelStoreOwner(通常默认实现),SavedStateRegistry 可能无法正确绑定,导致恢复失败。使用 ComponentActivity 或 Fragment 即可。
  6. 跨进程/死后的持久化误导 — SavedStateHandle 不保证数据在应用被用户强制杀死后保存。如需跨应用重启持久化,必须使用 Room 数据库 或 SharedPreferences。
  7. 过多数据导致 TransactionTooLargeException — 单个 Bundle 序列化后总大小不要超过 500KB(系统默认约 1MB 但很危险)。
    调试方法:在 onSaveInstanceState 中打印 Bundle 的 size 进行监控。

2.5 实现流程总结

  • 步骤1: 在 ViewModel 构造函数中声明 SavedStateHandle 参数。
  • 步骤2: 使用 savedStateHandle.getLiveData<T>(key, defaultValue) 创建可观察状态。
  • 步骤3: 在 Activity/Fragment 中通过 ViewModelProvider(this).get(MyViewModel::class.java) 正常获取实例,框架自动注入 SavedStateHandle。
  • 步骤4: 当 Activity 被重建时,系统自动从 Bundle 恢复 SavedStateHandle 的内容,LiveData 会立即回调到新 UI。
  • 步骤5: 如需手动保存额外数据,调用 savedStateHandle.set("key", value);注意对于复杂对象使用 Parcelable。
最佳实践: 将 SavedStateHandle 视为仅在进程重建时需要恢复的 轻量级 UI 状态缓存,不要用于存储业务逻辑核心数据。始终在 ViewModel 内通过 getLiveData 暴露数据,避免直接操作 Bundle。