SavedStateHandle 实现代码实战:定义 ViewModel、注入 SavedStateHandle、读写状态、验证持久化效果

SavedStateHandle 是 Android Architecture Components 提供的一个轻量级状态保存工具,它封装了 Bundle 的读写逻辑,能在 Activity 或 Fragment 因配置变更(如屏幕旋转)或系统回收后重建时,自动保留指定状态。其核心原理是:ViewModel 通过构造函数接收 SavedStateHandle,框架会将状态序列化到 Bundle 并存储在 SavedStateRegistryController 中;当进程重建时,handle 从相同的 Bundle 恢复数据,从而实现跨生命周期持久化。

核心优势:无需手动实现 onSaveInstanceState / onRestoreInstanceState,ViewModel 代码更简洁、可测试性更强。
SavedStateHandle 进程重建恢复流程 SavedStateHandle 进程重建恢复流程 Activity / Fragment 创建或重建 ViewModelProvider 创建/获取 ViewModel MyViewModel 注入 SavedStateHandle SavedStateHandle 内部 Bundle 状态 进程重建 系统回收后恢复 说明:进程重建时 SavedStateHandle 从持久化 Bundle 恢复数据, 保证 ViewModel 内的 LiveData / StateFlow 能自动获取保存的状态。

一、实现流程

  1. 定义 ViewModel 并声明 SavedStateHandle 构造参数 —— 在 ViewModel 构造函数中添加 savedStateHandle: SavedStateHandle 参数,框架会自动注入。
  2. 在 Repository / DataSource 中读写状态 —— 通过 savedStateHandle.getLiveData(key) 或 getStateFlow(key) 获取可观察数据,或使用 get(key) / set(key, value) 进行一次性读写。
  3. 在 Activity / Fragment 中实例化 ViewModel —— 使用 ViewModelProvider(this).get(MyViewModel::class.java),无需手动传参。
  4. 验证持久化效果 —— 触发屏幕旋转或通过开发者选项“不保留活动”强制销毁重建,观察状态是否恢复。

二、完整代码示例

以下是一个典型的 SavedStateHandle 实战 ViewModel:

// MyViewModel.kt
class MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // 使用 LiveData 形式,带默认值
    val count: LiveData<Int> = savedStateHandle.getLiveData("count", 0)

    fun increment() {
        val current = savedStateHandle.get<Int>("count") ?: 0
        savedStateHandle["count"] = current + 1
    }

    // 直接使用 StateFlow 写法(需依赖 lifecycle-viewmodel-ktx)
    val userName: StateFlow<String> = savedStateHandle
        .getStateFlow("userName", "Guest")

    fun updateName(name: String) {
        savedStateHandle["userName"] = name
    }
}

在 Activity 中使用:

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel = ViewModelProvider(this)[MyViewModel::class.java]

        viewModel.count.observe(this) { value ->
            findViewById<TextView>(R.id.tvCount).text = value.toString()
        }

        findViewById<Button>(R.id.btnIncrement).setOnClickListener {
            viewModel.increment()
        }
    }
}

三、SavedStateHandle 与常规状态管理对比

特性 常规 LiveData / StateFlow SavedStateHandle 方案
进程重建后数据恢复 需手动保存到 Bundle 或 Room 自动从 Bundle 恢复
代码侵入性 需实现 onSaveInstanceState ViewModel 构造注入即可
支持类型 任何可序列化对象 仅限 Bundle 支持的类型(原始、Parcelable、Serializable)
可测试性 需要 mock ViewModel + SavedStateRegistryOwner 可直接构造 SavedStateHandle(如 SavedStateHandle(mapOf("key" to value)))

四、调试:验证持久化效果

  • 屏幕旋转测试:旋转设备,观察 count 值是否仍保持递增结果。
  • 强制进程重建:在开发者选项中开启“不保留活动”,压 Home 键后再回到 App,检查状态。
  • 日志验证:在 ViewModel 的 init 或 onCleared() 中添加 Log,确认重建后 ViewModel 实例是否重新创建且状态恢复。
// 调试日志示例
init {
    val saved = savedStateHandle.get<Int>("count")
    Log.d("MyViewModel", "ViewModel初始化,已有count=$saved")
}

五、常见错误与避坑

⚠ 陷阱1:未提供默认值导致空指针
使用 getLiveData("key") 不传默认值时,如果 Bundle 中尚未存储该 key,LiveData 初始值为 null。务必始终提供有意义的默认值,或直接使用带有默认值的泛型方法。

⚠ 陷阱2:键冲突
多个 LiveData 使用相同 key 会互相覆盖。建议为每个数据项定义常量 key 前缀,如 private companion object { const val KEY_COUNT = "count" }。

⚠ 陷阱3:复杂对象无法序列化
SavedStateHandle 底层依赖 Bundle,因此数据必须为 Bundle 支持的类型。对于复杂对象,应使用 Room 数据库或序列化为 JSON 字符串存储(如 Gson.toJson()),不建议直接放入自定义 class。

六、性能与稳定性优化

  • 避免大对象频繁写入:每个 set() 都会触发序列化,大量图片 / bitmap 应存储在文件或数据库,仅保存轻量标识。
  • 使用 StateFlow 替代 LiveData:StateFlow 支持协程配合,且默认支持防抖(distinctUntilChanged),减少冗余通知。
  • 及时清理临时数据:对于仅在配置变更时需要保留、重建后不再需要的数据,可考虑在 ViewModel 的 onCleared() 中移除 key,避免残留 Bundle 占用内存。
小结:SavedStateHandle 是 Android 官方推荐的 ViewModel 状态持久化方案。通过成本极低的构造注入,就能实现配置变更和进程重建的自动恢复。配合 LiveData / StateFlow,无需手动管理 Bundle,显著降低模板代码量和出错率。本节的代码实战可直接用于生产项目,但需留意类型限制和键管理。