4. 自定义 SavedStateProvider:创建与注册
SavedStateHandle 默认支持 基本类型、Bundle 可序列化对象 以及 Parcelable 数据。当需要持久化复杂业务对象(如嵌套的领域模型、大列表或自定义序列化格式)时,默认机制可能不够灵活或效率低下。本章讲解如何通过 自定义 SavedStateProvider 接管特定键的序列化逻辑,实现细粒度的数据持久化控制。
4.1 原理:Provider 注册链
每个 SavedStateHandle 内部维护一个 SavedStateProvider 列表(即 components)。当系统调用 saveState() 时,按注册顺序依次调用每个 Provider,将返回的 Bundle 合并到最终的保存状态中。自定义 Provider 只需实现 SavedStateRegistry.SavedStateProvider 接口:
public interface SavedStateProvider {
@NonNull
Bundle saveState();
}
核心流程:
- 通过
SavedStateRegistryController获取SavedStateRegistry。 - 调用
registry.registerSavedStateProvider(key, provider)将自定义 Provider 绑定到指定键。 - 保存时,系统遍历所有注册的 Provider,将返回的 Bundle 放入根状态 Bundle 中。
- 恢复时,系统根据键从根 Bundle 中取出子 Bundle,交给对应的 ViewModel 或组件处理。
4.2 实现流程(四步法)
- 定义序列化/反序列化逻辑:实现
SavedStateProvider,在saveState()中把复杂对象转为Bundle。 - 注册 Provider:在 ViewModel 构造器或
init块中通过SavedStateHandle获取SavedStateRegistry并注册。 - 注入到 ViewModel:将自定义 Provider 返回的数据通过
LiveData或StateFlow暴露给 UI。 - 测试与验证:模拟进程杀死/重建,确认数据完整恢复。
4.3 代码示例:持久化购物车列表
假设购物车项为 CartItem(包含 productId、name、quantity、price),我们需要持久化整个 List<CartItem>。默认 SavedStateHandle 不支持直接存 List,因此自定义 Provider:
// 1. 自定义 Provider
class CartProvider(private val cartList: MutableList<CartItem>) : SavedStateProvider {
override fun saveState(): Bundle {
val bundle = Bundle()
val json = Gson().toJson(cartList) // 使用 Gson 序列化
bundle.putString("cart_json", json)
return bundle
}
fun restoreFromBundle(bundle: Bundle?) {
val json = bundle?.getString("cart_json") ?: return
val items = Gson().fromJson(json, Array<CartItem>::class.java)
cartList.clear()
items?.forEach { cartList.add(it) }
}
}
// 2. ViewModel 中使用
class CartViewModel(private val handle: SavedStateHandle) : ViewModel() {
private val cartList = mutableListOf<CartItem>()
private val cartProvider = CartProvider(cartList)
init {
// 注册 Provider
val registry = handle.savedStateRegistry()
registry.registerSavedStateProvider("cart", cartProvider)
// 尝试恢复已有数据
val savedBundle = handle.get<Bundle>("cart")
cartProvider.restoreFromBundle(savedBundle)
}
// 对外暴露只读列表
val cartItems: LiveData<List<CartItem>> = MutableLiveData(cartList.toList())
fun addItem(item: CartItem) {
cartList.add(item)
// 通知更新(实际项目中使用 postValue)
(cartItems as MutableLiveData).value = cartList.toList()
}
}
4.4 注册 Provider 的三种方式对比
| 注册方式 | 说明 | 适用场景 |
|---|---|---|
SavedStateHandle.savedStateRegistry() |
通过 ViewModel 的 handle 直接获取 Registry | ViewModel 内部自包含数据 |
SavedStateRegistryController |
在 Activity/Fragment 中手动控制注册 | 跨组件共享 Provider |
AbstractSavedStateViewModelFactory |
自定义 Factory 注入已注册的 Provider | 依赖注入 + 测试 |
4.5 处理复杂对象序列化的策略
- 嵌套对象:递归调用
Bundle.putBundle(),每个子对象对应一个子 Bundle。 - 大列表:使用 JSON 或 ProtoBuf 序列化后存入
putString(注意字符串长度限制,一般 8KB 以内可接受)。 - 二进制数据:用
putByteArray,配合Kryo或FlatBuffers降低体积。 - 时间/日期:统一转为
Long(毫秒时间戳)存入。 - 枚举:用
putString(name)或putInt(ordinal),两端约定。
注意:Bundle 本身对大小有限制(约 1 MB 内,Android 10+ 放宽)。若数据超过阈值,应考虑 Room 存储 + 仅在切后台时保存关键索引,而非全量序列化。
4.6 调试与验证技巧
- 打印保存状态:在
saveState()中Log.d输出序列化后的内容。 - 模拟进程杀死:使用
adb shell am kill <package>或 Android Studio 的 “Force Stop” + 重新打开。检查 ViewModel 恢复后的数据是否完整。 - 检查注册冲突:同一键名注册多个 Provider 会导致后注册者覆盖前者。使用
registry.isRegistered("key")在注册前确认。
4.7 性能与稳定性优化
- 避免在主线程序列化:尤其大列表选用 JSON 或 ProtoBuf 时,建议在
Dispatchers.IO进行序列化,然后将 Bundle 缓存。 - 懒加载恢复:Provider 的
restoreFromBundle改为在第一次访问数据时才反序列化,减少启动耗时。 - Provider 生命周期管理:在 ViewModel 的
onCleared()中移除注册的 Provider,防止内存泄漏。 - 版本兼容:序列化时写入版本号字段,反序列化时检查版本,支持向后兼容。
最佳实践:对于频繁变动的数据(如列表编辑状态),使用
SavedStateHandle.set() 替代手动 Provider 反而更简单;仅当默认序列化不满足需求(如需要 Gson/Room 等自定义转换)时才定义 Provider。4.8 常见错误排查
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
| 进程重启后数据丢失 | Provider 未注册或注册位置不对 | 确保在 init 块或 onCreate 之前注册 |
| 类型转换异常(Bundle 中存的是 String,但取出变成 null) | 序列化时丢失类型信息,或键名拼错 | 使用常量定义键名,并在 save/restore 两端统一 |
| 反复注册导致性能下降 | 每次配置改变都重新创建 Provider | 使用 lifecycleScope 或单例 Provider 缓存 |
| Bundle 过大导致 TransactionTooLargeException | 序列化了大体积数据 | 改用 Room 持久化,仅在 Provider 中保存 ID 或索引 |
4.9 部署建议
在正式项目中使用自定义 SavedStateProvider 时,建议将 Provider 与 ViewModel 分离,封装为独立的序列化策略类。推荐结构:
// 序列化策略接口
interface StateSerializer<T> {
fun serialize(data: T): Bundle
fun deserialize(bundle: Bundle): T
}
// 具体实现:购物车序列化器
class CartSerializer : StateSerializer<List<CartItem>> {
override fun serialize(data: List<CartItem>): Bundle { /* 使用 Gson */ }
override fun deserialize(bundle: Bundle): List<CartItem> { /* 反序列化 */ }
}
// ViewModel 中组合
class CartViewModel(handle: SavedStateHandle) : ViewModel() {
private val serializer = CartSerializer()
// ... 注册 Provider 时委托给 serializer
}
通过依赖注入(如 Hilt)可以提供测试替身,方便单元测试。