1. SavedStateHandle核心原理

SavedStateHandle 是 Android Architecture Components 中 ViewModel 与 进程重建 之间的桥梁。它解决了传统 ViewModel 在配置变更(如屏幕旋转)时能保持状态,但 进程被系统杀死后重建 却会丢失数据的问题。理解其原理需要从三个层面入手:ViewModel 的宿主生命周期、onSaveInstanceState 的调用时机、以及 SavedStateRegistry 的底层回调注册机制。

SavedStateHandle 核心原理流程 Activity / Fragment 生命周期 onSaveInstanceState 时机 SavedStateRegistry SavedStateHandle ViewModel 创建/恢复 持久化保存状态数据 恢复状态数据到 UI 关键说明: SavedStateRegistry 是底层注册表,由 Activity/Fragment 在 onSaveInstanceState 回调中遍历注册的 SavedStateProvider, 将数据写入 Bundle。ViewModel 通过 SavedStateHandle 对外暴露 get()/set() 方法,开发者无需直接操作 Bundle。

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 将自己注册到宿主的注册表上。流程如下:

  1. 宿主(Activity/Fragment)初始化时创建 SavedStateRegistry。
  2. ViewModelProvider 创建 ViewModel 时,检测构造参数中是否有 SavedStateHandle,若有则调用 SavedStateHandleController.attachToLifecycle 完成注册。
  3. 当宿主收到 onSaveInstanceState 时,SavedStateRegistry 遍历所有已注册的 Provider,调用 saveState() 收集数据。
  4. 重建时,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 库加密。