4. SavedStateHandle 的原理与使用:构造函数注入、getLiveData、与 SavedStateRegistry 的关联

4.1 核心原理概述

SavedStateHandle 是 AndroidX SavedState 库提供的核心数据容器,用于在 Activity 或 Fragment 重建时保存和恢复 UI 相关状态。它的设计本质上是一个键值对存储(类似 Bundle),但具有以下关键特性:

特性 说明
生命周期感知 自动跟随 SavedStateRegistry 在 onSaveInstanceState 时保存,在 onCreate 后恢复。
LiveData 支持 通过 getLiveData(key) 返回的 LiveData 在重建后自动恢复最新值,并仅在新值变更时触发。
构造函数注入 ViewModel 可通过 SavedStateHandle 参数直接注入,无需手动关联 Registry。
类型安全 支持基本类型、String、Bundle、Serializable、Parcelable。

SavedStateHandle 的内部实现依赖于 SavedStateRegistry:每个 Activity/Fragment 拥有一个 Registry,它会处理实际的状态序列化与恢复,而 Handle 只是 Registry 的一个简化 API 层。

关键洞见: SavedStateHandle 本身不直接参与序列化,而是将数据委托给 SavedStateRegistry 的 consumeRestoredStateForKey() 和 registerSavedStateProvider() 方法。因此,任何容器只要通过 SavedStateRegistry 注册,即可利用 SavedStateHandle。

4.2 工作流程图:SavedStateHandle 与 SavedStateRegistry 的协作

SavedStateHandle 与 SavedStateRegistry 协作流程 Activity / Fragment SavedStateRegistry SavedStateHandle ViewModel onCreate解绑 注册 Provider 构造函数注入 onSaveInstanceState onCreate 恢复 序列化 回调 Provider 保存到 Bundle 说明:SavedStateRegistry 将状态保存/恢复委托给所有注册的 Provider,SavedStateHandle 是其中一个 Provider。 ViewModel 通过 SavedStateHandle 读写数据,重建后数据自动还原。

4.3 构造函数注入:ViewModel 如何获取 SavedStateHandle

从 activity-ktx:1.2.0 和 lifecycle-viewmodel-savedstate:1.0.0 开始,ViewModel 可以直接通过构造函数接收 SavedStateHandle 参数,无需手动创建。

前置条件: 确保 app/build.gradle 包含依赖:

implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:1.0.0"
implementation "androidx.activity:activity-ktx:1.2.0"

示例 ViewModel:

class MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // getLiveData 直接使用
    val filterText: LiveData<String> = savedStateHandle.getLiveData("filter_key")

    // 设置值
    fun updateFilter(text: String) {
        savedStateHandle.set("filter_key", text)
    }
}

在 Activity 中通过 ViewModelProvider 获取时,框架自动解析构造函数:

val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)

如果使用 by viewModels() 委托:

class MyActivity : AppCompatActivity() {
    private val viewModel: MyViewModel by viewModels()
    // 无需任何额外配置
}

4.4 getLiveData 方法详解

SavedStateHandle.getLiveData(key, initialValue) 返回一个 MutableLiveData 实例,其行为遵循以下规则:

  • 首次创建: 如果 key 之前未保存过,返回的 LiveData 的值为 initialValue(默认为 null)。
  • 重建恢复: 如果 key 在 onSaveInstanceState 时被保存,LiveData 会立即设置为保存的值,且只对新观察者发送一次(避免重复触发)。
  • 值变更: 后续调用 savedStateHandle.set(key, newValue) 会更新 LiveData 的值并通知所有活跃观察者。
  • 线程安全: 所有方法都是主线程安全的,内部通过 SavedStateRegistry 的锁机制保证。

内部实现伪代码:

public <T> MutableLiveData<T> getLiveData(String key, T initialValue) {
    if (!liveDatas.containsKey(key)) {
        MutableLiveData<T> liveData = new MutableLiveData<>(initialValue);
        // 如果有恢复值,立即设置
        T restored = consumeRestoredStateForKey(key);
        if (restored != null) {
            liveData.setValue(restored);
        }
        liveDatas.put(key, liveData);
    }
    return liveDatas.get(key);
}

注意: 同一 key 只能绑定一个 LiveData。重复调用 getLiveData(key) 会返回之前创建的实例,但 initialValue 参数在后续调用中会被忽略(仅首次生效)。

4.5 与 SavedStateRegistry 的关联机制

SavedStateHandle 之所以能“自动”保存和恢复,是因为它在构造时向 SavedStateRegistry 注册了一个 SavedStateProvider。具体关联流程:

  1. 创建 SavedStateHandle: 通常在 ViewModel 构造时由 SavedStateViewModelFactory 创建,传入当前 SavedStateRegistry 和一个 Bundle(从 LifecycleOwner 获取恢复状态)。
  2. 注册 Provider: Handle 内部调用 savedStateRegistry.registerSavedStateProvider("androidx.lifecycle.internal.SavedStateHandle", provider),其中 provider 是一个 LiveData 快照。
  3. 保存时机: 当 Activity 调用 onSaveInstanceState 时,Registry 遍历所有注册的 Provider,调用 saveState() 收集所有键值对并写入 Bundle。
  4. 恢复时机: 在 Activity 重建后的 onCreate 中,Registry 通过 consumeRestoredStateForKey() 将 Bundle 中的对应数据返回给 Handle,Handle 再分发到各个 LiveData。

手动注册示例(一般情况不需要手动操作):

val registry = SavedStateRegistryController.create(this)
registry.performRestore(savedInstanceState)
val handle = SavedStateHandle.createHandle(
    registry.consumeRestoredStateForKey("my_key"),
    defaultArgs
)
registry.registerSavedStateProvider("my_provider", handle.savedStateProvider)

4.6 常见错误与避坑指南

错误场景 原因 解决方案
ViewModel 中未定义 SavedStateHandle 构造参数,仍期望自动恢复 框架无法注入,ViewModel 的 SavedStateHandle 为 null 确保 constructor 包含 SavedStateHandle 参数,或使用 SavedStateViewModelFactory 显式创建
使用 getLiveData 后立即设置默认值,但重建时被覆盖 恢复的 LiveData 值(如果有)会覆盖 initialValue 不要在 init 中调用 set 覆盖已恢复的值;检查逻辑避免二次赋值
在 Fragment 中调用 by viewModels() 但未设置 NavBackStackEntry 的 SavedStateHandle Fragment 的 SavedStateRegistry 可能未初始化 确保 Fragment 的 ViewModel 使用 by viewModels(ownerProducer) 传入 NavBackStackEntry
保存大量数据导致 Bundle 限制(~1MB) SavedStateHandle 最终通过 Bundle 序列化 仅保存轻量级状态;复杂对象使用 Room 或 DataStore

4.7 调试与验证

在开发阶段,可以通过以下方式验证 SavedStateHandle 是否正常工作:

  • 添加日志:在 ViewModel 的 init 块和 getLiveData 获取值后记录当前值。
  • 使用 Don't keep activities 开发者选项强制 Activity 重建。
  • 检查 onSaveInstanceState 后 Bundle 中是否包含 androidx.lifecycle.internal.SavedStateHandle 键。
// 调试日志示例
class DebugViewModel(handle: SavedStateHandle) : ViewModel() {
    init {
        Log.d("VM", "SavedStateHandle restored value: ${handle.get("filter_key")}")
    }
}

4.8 性能与稳定性优化

  • 避免存储不可序列化对象: SavedStateHandle 内部使用 Bundle,因此仅支持 Bundle 兼容的数据类型。复杂对象应转为 JSON 或使用 Parcelable。
  • 限制 LiveData 数量: 每个 key 绑定一个 LiveData,过多的 LiveData 会增加重建时的内存占用和注册开销。通常一个 ViewModel 的 key 不超过 10 个。
  • 使用 set 而非直接操作 LiveData.value: set 方法会同步更新内部存储和 LiveData,确保与 Registry 同步。
  • 与 Navigation 结合: 在 Navigation 中使用 SavedStateHandle 时,通过 currentBackStackEntry?.savedStateHandle 获取,避免跨 destination 泄漏。

推荐实践: 将 UI 状态(如输入框内容、筛选条件、滚动位置)存储在 SavedStateHandle 中,而业务数据和网络请求结果存储在 ViewModel 的普通字段或 StateFlow 中。这样可以充分利用重建恢复的自动机制,同时保持数据层的独立性。