Compose 中的 状态恢复 指的是在 配置变更(如屏幕旋转、语言切换)或 进程终止后,将 UI 状态和数据恢复到之前状态的能力。这是保障用户体验的关键。
它主要解决两个核心场景:
- 配置变更:Activity 被销毁重建,但进程存活。
- 进程终止:App 被系统从后台杀死后重新打开。
flowchart TD
A[“状态恢复需求”] --> B{“首先判断状态类型”}
B --> C[“UI临时状态<br>(如输入框文字、滚动位置)”]
B --> D[“屏幕/业务逻辑状态<br>(如用户列表、页面选中项)”]
B --> E[“永久/用户偏好状态<br>(如主题、登录令牌)”]
C --> F{“该状态是否重要到<br>需要跨配置变更保留?”}
F -->|否,可丢弃| G[“使用 remember”]
F -->|是,需保留| H[“使用 rememberSaveable”]
D --> I[“首选方案:ViewModel”]
I --> I1[“配合 SavedStateHandle<br>处理基本数据类型”]
I --> I2[“配合 Hilt 等依赖注入框架<br>注入复杂依赖”]
E --> J[“持久化方案<br>DataStore / Room / SharedPreferences”]
下面我们具体看看每种工具的原理和用法。
🧩 核心工具详解
1. rememberSaveable:UI 临时状态的救星
适用于保存简单的 UI 状态,是 remember 的增强版。其核心原理是将数据通过 Bundle 系统进行序列化/反序列化。
使用方式:
@Composable
fun LoginInput() {
// username 在屏幕旋转后会被保留
var username by rememberSaveable { mutableStateOf("") }
OutlinedTextField(
value = username,
onValueChange = { username = it },
label = { Text("用户名") }
)
}
处理复杂数据:对于无法自动保存的数据类,需提供 Saver。
data class UserSettings(val theme: String, val fontSize: Int)
@Composable
fun ComplexStateDemo() {
//mapSaver用法
var userMapSaver = run {
val phone = "phone"
val password = "pasword"
mapSaver( save = { mapOf(phone to it.phone,password to it.password) },
restore = { UserSettings(it[phone].toString() it[password].toString()) })
}
//ListSaver用法
val settingsSaver = listSaver<UserSettings, Any>(
save = { listOf(it.theme, it.fontSize) },
restore = { UserSettings(it[0] as String, it[1] as Int) }
)
var settings by rememberSaveable(stateSaver = settingsSaver) {
mutableStateOf(UserSettings("Dark", 16))
}
// ... 使用 settings
}
注意:rememberSaveable 适合轻量、与 UI 紧密相关的状态。对于重要业务状态,应使用更强大的 ViewModel。
2. ViewModel:业务状态的恢复基石
ViewModel 的生命周期 长于 Activity/Fragment,因此在配置变更时不会销毁,这是其天然的状态恢复优势。
对于需要在进程终止后也恢复的状态,ViewModel 需配合 SavedStateHandle 使用。
使用方式:
// 1. 在 ViewModel 中接收 SavedStateHandle
class MyViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
// 2. 从 handle 中读取保存的状态,并提供默认值
private val _selectedTab = savedStateHandle.getStateFlow("selectedTab", 0)
val selectedTab: StateFlow<Int> = _selectedTab
fun selectTab(index: Int) {
// 3. 更新状态的同时,也保存到 handle 中
savedStateHandle["selectedTab"] = index
// 也可以 _selectedTab.value = index,如果使用 MutableStateFlow
}
// 对于复杂对象,可以序列化为 JSON 字符串存储
private val _userData = savedStateHandle.getStateFlow("userData", "")
val userData: StateFlow<User?> = _userData.map { json ->
if (json.isEmpty()) null else Gson().fromJson(json, User::class.java)
}.stateIn(viewModelScope, SharingStarted.Eagerly, null)
fun saveUser(user: User) {
savedStateHandle["userData"] = Gson().toJson(user)
}
}
在可组合函数中消费:
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val selectedTab by viewModel.selectedTab.collectAsStateWithLifecycle()
TabRow(selectedTabIndex = selectedTab) {
tabs.forEachIndexed { index, title ->
Tab(
selected = selectedTab == index,
onClick = { viewModel.selectTab(index) }
) {
Text(title)
}
}
}
}
3. 持久化存储:应对进程终止
当应用被系统从后台彻底杀死时,ViewModel 和 SavedStateHandle 也会被清除。此时需要真正的持久化存储。
- DataStore (推荐):现代异步 API,适用于键值对数据(如用户偏好设置)。
- Room:功能强大的 SQLite 抽象层,适用于复杂结构化数据。
- 文件存储:适用于大文件或特定格式数据。
在应用启动或进入相关界面时,从持久化存储中读取数据并初始化状态。
✅ 最佳实践总结
| 场景 | 推荐工具 | 关键原因 |
|---|---|---|
| 简单的 UI 状态恢复(如输入文本、开关状态) | rememberSaveable | 使用简单,自动集成到 Bundle 系统。 |
| 屏幕级、业务逻辑状态恢复(如选中的标签页、列表项) | ViewModel + SavedStateHandle | 生命周期更长,与 UI 逻辑分离,是 Android 架构组件推荐的标准模式。 |
| 跨进程终止恢复的用户偏好/重要数据 | ViewModel + 持久化存储(DataStore/Room) | SavedStateHandle 仅在正常生命周期内有效,进程终止后需持久化。 |
通用原则:
- 状态提升:将需要恢复的状态提升到足够高的层级(如屏幕根组件),以确保恢复逻辑能够覆盖。
- 单一可信源:确保每个状态只有一个真实数据源。
ViewModel通常是这个角色的最佳担当。 - 测试:务必测试状态恢复流程,尤其是模拟进程被终止后恢复的场景。
记住一个简单的决策链:先问这个状态是否属于业务逻辑(是 → 用 ViewModel),再问是否需要持久化(是 → 加 DataStore/Room)。如果只是临时的UI状态且值得保留,就用 rememberSaveable。
如果你能分享具体的状态恢复场景(例如什么样的数据、在什么情况下丢失),我可以提供更针对性的代码方案。