SavedStateHandle 核心原理:生命周期、保存时机与注册机制
SavedStateHandle 是 Android Architecture Components 中 ViewModel 与 进程重建 之间的桥梁。它解决了传统 ViewModel 在配置变更(如屏幕旋转)时能保持状态,但 进程被系统杀死后重建 却会丢失数据的问题。理解其原理需要从三个层面入手:ViewModel 的宿主生命周期、onSaveInstanceState 的调用时机、以及 SavedStateRegistry 的底层回调注册机制。
1. ViewModel 生命周期与宿主紧密绑定
ViewModel 的生命周期不等于 Activity 的生命周期。它仅在宿主(Activity/Fragment)最终销毁时(如 finish())才被清除。配置变更(横竖屏切换)导致的重新创建并不会销毁 ViewModel,因此状态一直可用。但 进程被系统回收后,ViewModel 的内存会被完全释放,此时必须依靠 onSaveInstanceState 持久化关键状态。
| 生命周期场景 | ViewModel 是否存活 | SavedStateHandle 作用 |
|---|---|---|
| 横竖屏切换(配置变更) | 存活,不重建 | 无需额外操作 |
| App 进入后台后被系统杀死 | 销毁 | 从 SavedStateHandle 中恢复 |
| 主动 finish() 或 Activity.finish() | 销毁,不再恢复 | 不保存(不保证恢复) |
2. onSaveInstanceState 调用时机
系统会在 Activity 即将被杀死但可能恢复 的时候调用 onSaveInstanceState(例如按 Home 键后进程被回收、屏幕旋转等)。SavedStateRegistry 利用此回调收集所有保存了数据状态的对象。注意:不是每次暂停都会触发,只有系统觉得需要保存时才会调用。
重要提示: SavedStateHandle 保存的数据最终会序列化到 Bundle 中,受 Binder 交易缓存大小限制(通常 1MB 以内)。切勿保存大型 Bitmap 或长列表。
3. SavedStateRegistry 机制
SavedStateRegistry 是 AndroidX 的一个核心组件,它维护了一个 SavedStateProvider 的注册表。每个 ViewModel 通过 SavedStateHandleController 将自己注册到宿主的注册表上。流程如下:
- 宿主(Activity/Fragment)初始化时创建
SavedStateRegistry。 - ViewModelProvider 创建 ViewModel 时,检测构造参数中是否有
SavedStateHandle,若有则调用SavedStateHandleController.attachToLifecycle完成注册。 - 当宿主收到
onSaveInstanceState时,SavedStateRegistry遍历所有已注册的 Provider,调用saveState()收集数据。 - 重建时,
SavedStateHandleController从宿主保存的 Bundle 中反序列化,还原数据到SavedStateHandle。
4. 实现流程:创建带 SavedStateHandle 的 ViewModel
使用 SavedStateHandle 无需额外依赖(AndroidX Activity/Fragment 1.2.0+ 内置支持)。只需在 ViewModel 构造函数中添加 SavedStateHandle 参数,框架会自动注入实例。
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
companion object {
private const val KEY_COUNT = "count"
}
val count: LiveData<Int> = savedStateHandle.getLiveData(KEY_COUNT, 0)
fun increment() {
savedStateHandle[KEY_COUNT] = (savedStateHandle[KEY_COUNT] ?: 0) + 1
}
}
在 Activity 中获取 ViewModel:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
viewModel.count.observe(this) { count ->
findViewById<TextView>(R.id.tv_count).text = count.toString()
}
}
}
5. SavedStateHandle API 详解
| 方法 | 说明 |
|---|---|
get(key: String): T? | 获取保存的值,不存在返回 null |
set(key: String, value: T) | 设置键值对 |
getLiveData(key: String, initialValue: T): MutableLiveData<T> | 返回一个关联的 LiveData,自动响应状态变化 |
keys(): Set<String> | 返回所有键集合 |
contains(key: String): Boolean | 判断是否包含某键 |
remove<T>(key: String): T? | 移除键并返回旧值 |
6. 调试与常见错误
常见问题之一:SavedStateHandle 保存的数据在进程重建后丢失。 检查是否使用了 ViewModelProvider 的默认 Factory,并且 ViewModel 构造函数正确接收了 SavedStateHandle。若自定义 Factory,需手动创建 SavedStateHandle:
class MyViewModelFactory(
private val savedStateHandle: SavedStateHandle
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return modelClass.getConstructor(SavedStateHandle::class.java)
.newInstance(savedStateHandle)
}
}
另一个陷阱:在 Fragment 中使用 SavedStateHandle 时,Fragment 的 arguments 也参与了保存。 如果有自定义 onSaveInstanceState 逻辑,确保没有覆盖掉 SavedStateRegistry 的保存。
7. 性能与稳定性优化
- 避免保存大对象: SavedStateHandle 依赖 Bundle 传输,大数据会导致 TransactionTooLargeException。应只保存关键 ID 或轻量状态,大数据通过 Room 或 DataStore 持久化。
- 使用 LiveData 而不是手动 set/get:
getLiveData会自动跟踪数据变化,减少手动同步代码(但内部仍通过 Bundle 保存最新值)。 - 最小化键的数量: 每个键对应 Bundle 中的一个条目,过多键增加序列化开销。可将相关数据封装成序列化对象(如 data class)存储为单个键。
- 注意空安全: SavedStateHandle 的 get 返回可空类型,确保在使用前处理 null 情况,避免 NPE。
警告: SavedStateHandle 适用于需要跨进程重建持久化的少量 UI 状态(如输入框文本、选中项位置)。切勿用于存储用户账户 、数据库缓存等持久化要求高的数据——那些应使用 Room/DataStore 并结合 Security 库加密。