SavedStateHandle 实践指南
1. 什么是 SavedStateHandle?
SavedStateHandle
是 Android Jetpack ViewModel 提供的一个用于保存 UI 相关数据的工具类,目的是实现在屏幕旋转、进程重启等情况下保持状态不丢失。
它被内置注入到 ViewModel
中,作为 Bundle
的包装,避免了传统使用 onSaveInstanceState(Bundle)
的繁琐,方便保存简单、可序列化的数据。
📖 详见官方文档:ViewModel与SavedStateHandle
基础示例
kotlin
class MyViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
val username = savedStateHandle.getStateFlow("username", "")
fun updateName(newName: String) {
savedStateHandle["username"] = newName
}
}
2. 使用场景分析
✅ 适用场景
- 配置变更恢复 :屏幕旋转、后台恢复等导致
Activity
/Fragment
重建时,保存 UI 相关状态 - 轻量级缓存:快速保存轻量级数据,避免频繁访问数据库或网络
- 状态持久化补充:补充 ViewModel 本身不具备的状态持久化能力
❌ 不适用场景
- 大型数据对象:不适合保存 Bitmap、文件、数据库等大型对象
- 长久持久化:需要跨进程、跨重启的数据应该使用数据库、文件或网络存储
3. 传统封装方式(基类继承)
下面是一个传统基类封装,结合 SavedStateHandle
和 StateFlow
,并集成了简单的表单校验功能:
kotlin
abstract class BaseSavedStateViewModel<T: Parcelable>(
private val savedStateHandle: SavedStateHandle,
private val initialState: T,
private val stateStorageKey: String
) : ViewModel() {
private val _stateFlow = savedStateHandle.getStateFlow(
key = stateStorageKey,
initialValue = initialState
)
val stateFlow: StateFlow<T> = _stateFlow
val isFormValid: StateFlow<Boolean> = stateFlow.map {
validateState(it)
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
protected fun updateState(transform: (T) -> T) {
savedStateHandle[stateStorageKey] = transform(stateFlow.value)
}
fun resetState() {
savedStateHandle[stateStorageKey] = initialState
}
protected abstract fun validateState(state: T): Boolean
}
使用示例
kotlin
@Parcelize
data class FormState(
val username: String = "",
val age: Int = 0
) : Parcelable
class MyViewModel1(savedStateHandle: SavedStateHandle) : BaseSavedStateViewModel<FormState>(
savedStateHandle,
initialState = FormState(),
stateStorageKey = "form_state"
) {
override fun validateState(state: FormState): Boolean {
return state.username.isNotBlank() && state.age > 0
}
fun updateUsername(newName: String) {
updateState { it.copy(username = newName) }
}
fun updateAge(newAge: Int) {
updateState { it.copy(age = newAge) }
}
}
基类封装的优势
- 避免每个 ViewModel 重复实现状态管理代码
- 统一状态类型为
Parcelable
,保证序列化安全 - 结合
StateFlow
实现响应式状态观察与校验
4. 现代封装方式(接口+组合)🌟
与基类相比,Google 官方更推荐接口+组合的设计,避免继承带来的僵化和复用限制。
kotlin
interface SavedStateStore<T : Parcelable> {
val stateFlow: StateFlow<T>
val isFormValid: StateFlow<Boolean>
fun update(transform: (T) -> T)
fun reset()
fun validate(state: T): Boolean
}
fun <T : Parcelable> savedStateDelegate(
key: String,
scope: CoroutineScope,
handle: SavedStateHandle,
initial: T,
validateFn: ((T) -> Boolean)? = null
): SavedStateStore<T> = object : SavedStateStore<T> {
private val stateFlowInternal = handle.getStateFlow(key, initial)
override val stateFlow: StateFlow<T> = stateFlowInternal
override val isFormValid: StateFlow<Boolean> =
stateFlow.map { validate(it) }.stateIn(
scope,
SharingStarted.WhileSubscribed(5000),
false
)
override fun update(transform: (T) -> T) {
handle[key] = transform(stateFlow.value)
}
override fun reset() {
handle[key] = initial
}
override fun validate(state: T): Boolean {
return validateFn?.invoke(state) ?: true
}
}
使用示例
kotlin
@Parcelize
data class FormState(
val username: String = "",
val age: Int = 0
) : Parcelable
class MyViewModel2(savedStateHandle: SavedStateHandle) : ViewModel() {
private val formStore = savedStateDelegate(
key = "form_state",
scope = viewModelScope,
handle = savedStateHandle,
initial = FormState()
) { state ->
state.username.isNotBlank() && state.age > 0
}
val stateFlow = formStore.stateFlow
val isFormValid = formStore.isFormValid
fun updateUsername(newName: String) {
formStore.update { it.copy(username = newName) }
}
fun updateAge(newAge: Int) {
formStore.update { it.copy(age = newAge) }
}
fun resetForm() {
formStore.reset()
}
}
🎯 为什么这样封装更好?
优势 | 说明 |
---|---|
解耦性强 | 用接口抽象状态管理职责,不依赖继承层级,增强灵活性 |
组合优于继承 | 避免继承层级复杂、耦合度高的问题,更易扩展和测试 |
响应式风格 | 利用 StateFlow 和函数式 update ,保持状态不可变原则 |
易于测试 | 通过接口模拟和注入,方便 Mock 和测试 |
生命周期安全 | 结合 CoroutineScope 管理,保证状态流的生命周期安全 |
5. SavedStateHandle 工作原理
SavedStateHandle
的核心机制:
- 本质 :封装了一个
Bundle
,借助SavedStateRegistry
将数据保存到 Activity / Fragment 的保存状态中 - 通知机制 :当数据变化时,通过
LiveData
或StateFlow
通知观察者,实现 UI 自动刷新 - 关键接口 :
getStateFlow(key, initial)
内部维护一个MutableStateFlow
,保证状态同步
6. 使用注意事项 ⚠️
- 序列化支持 :保存支持序列化的类型,通常使用
Parcelable
或基础数据类型 - 数据大小限制:不适合存储大型或复杂对象,避免导致性能问题或异常
- 持久化限制:状态恢复依赖系统机制,不能保证长久持久化
- 异常处理:合理设计初始值和校验逻辑,防止异常数据导致崩溃
7. 状态保存方案选择指南
rememberSaveable
、SavedStateHandle
和 onSaveInstanceState()
都能保存状态,该用哪一个?
方案对比
名称 | 属于哪里 | 用于保存什么 | 生命周期范围 |
---|---|---|---|
rememberSaveable |
Jetpack Compose UI 层 | 轻量的 UI 状态(字符串、选中项等) | Compose Composable 生命周期 |
SavedStateHandle |
Jetpack ViewModel | ViewModel 中业务逻辑/表单状态等 | ViewModel 生命周期(支持重建) |
onSaveInstanceState() |
传统 View 层(Activity/Fragment) | 系统层保存的 Bundle 数据 | View 或 Fragment 生命周期 |
详细特性对比
特性/场景 | rememberSaveable |
SavedStateHandle |
onSaveInstanceState() |
---|---|---|---|
使用方式 | rememberSaveable { ... } |
SavedStateHandle.get() |
override fun onSaveInstanceState(outState: Bundle) |
适用范围 | Compose 中的局部状态 | ViewModel 层状态管理 | 传统 View 的状态保存 |
生命周期 | Compose Composable 的重组与销毁 | ViewModel 生命周期(跨配置变更) | View、Fragment 销毁时保存 |
自动恢复 | ✅ 是(系统自动调用) | ✅ 是(Jetpack 自动注入) | ⚠️ 需自己手动写入/读出 |
支持数据类型 | 基础类型、Parcelable 、可序列化对象 |
Parcelable 、基础类型 |
任何支持 Bundle 的类型 |
屏幕旋转恢复 | ✅ | ✅ | ✅ |
进程被杀重启恢复 | ⚠️ 限于 Navigation 支持 | ✅ 推荐使用 | ✅ |
使用难度 | ⭐(非常简单) | ⭐⭐(推荐) | ⭐⭐⭐(繁琐且易错) |
Compose 推荐 | ✅ | ✅(结合 ViewModel) | ❌(非 Compose 推荐) |
8. 推荐使用方式 ✅
📱 Jetpack Compose 项目
场景 | 推荐方案 |
---|---|
文本框内容、勾选状态、分页索引等 UI 层状态 | ✅ rememberSaveable |
表单、业务状态、需要跨页面共享的状态 | ✅ SavedStateHandle + ViewModel |
大量复杂状态(比如网络列表) | ✅ 放到 ViewModel 并通过 StateFlow 管理 |
简单转场或返回状态,暂不使用 Compose ViewModel | 可考虑 rememberSaveable 临时保存 |
📱 传统 Fragment/Activity 项目
场景 | 推荐方案 |
---|---|
保存 RecyclerView 滚动位置、Fragment 显示状态等 | ✅ onSaveInstanceState() |
替代方案(推荐) | ✅ 抽离到 ViewModel 并使用 SavedStateHandle |
9. 总结
SavedStateHandle
是现代 Android 应用中处理 UI 状态持久化的重要工具,结合 StateFlow
和 MVVM 架构能极大提升代码质量和响应性。
核心要点
- 传统基类封装虽然简洁,但容易导致继承耦合和灵活性不足
- 接口+组合方式是官方推荐的现代状态管理最佳实践,能更好地实现职责分离、复用灵活、易测试
- 灵活适配:结合 Compose 或普通 View 都适用,满足复杂业务场景需求
选择合适的状态保存方案,让你的 Android 应用在各种配置变更和系统重启场景下都能提供流畅的用户体验。