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”) 即可获得恢复后的状态。
1. ViewModel 构造函数接收 SavedStateHandle 参数。
2. Activity 因配置变更/进程重建,系统触发 onSaveInstanceState → SavedStateRegistry 调用 SaveStateProvider → 序列化 handle 内所有可存活的键值对到 Bundle。
3. 重建时:系统使用 Bundle 构造新的 SavedStateHandle,ViewModelProvider.Factory 通过 SavedStateHandleProvider 注入给 ViewModel。
4. ViewModel 读取 handle.get(“key”) 即可获得恢复后的状态。
2.2 与 Bundle 的区别
| 维度 | Bundle | SavedStateHandle |
|---|---|---|
| 保存时机 | 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)。
- 二进制大对象(图片/视频)→ 应使用临时文件或 Room。
- 敏感信息(密码/ )→ Bundle 可能被序列化到磁盘,应使用 EncryptedSharedPreferences。
- 需要长期保存的业务数据 → 应使用持久化存储(Room/DataStore)。
2.4 常见陷阱与避坑指南
- 忽略构造函数参数声明 — ViewModel 构造函数必须显式包含
SavedStateHandle参数,否则框架无法正常注入。
class MyViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val count = savedStateHandle.getLiveData<Int>("count", 0) } - 错误使用 key 冲突 — 多个 ViewModel 使用相同 key 可能导致数据覆盖。建议使用 唯一前缀 或枚举定义 key 常量。
安全做法: 在 companion object 中定义 key 常量,如const val KEY_USER_NAME = "user_name" - 存储非序列化对象 — 对象必须实现
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 - 依赖 ViewModel 的 onCleared 进行状态保存 — SavedStateHandle 的保存发生在
onDestroy之前,不要在 onCleared() 中再保存一次,否则可能写入已序列化的旧数据。 - 遗漏 ViewModelStoreOwner — 如果 Activity/Fragment 未正确实现 ViewModelStoreOwner(通常默认实现),SavedStateRegistry 可能无法正确绑定,导致恢复失败。使用
ComponentActivity或Fragment即可。 - 跨进程/死后的持久化误导 — SavedStateHandle 不保证数据在应用被用户强制杀死后保存。如需跨应用重启持久化,必须使用 Room 数据库 或 SharedPreferences。
- 过多数据导致 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。