4. remember 与 rememberSaveable 的选型规则:记忆范围、屏幕旋转保持、自定义 Saver
4.1 原理概述
在 Compose 中,remember 和 rememberSaveable 都是用于在重组(Recomposition)间保持状态的工具,但两者在 记忆范围 和 生命周期耐受性 上存在本质区别。
- remember:状态仅在当前 Composable 的 组合(Composition)生命周期 内保持。当配置变更(如屏幕旋转)导致 Activity 重建时,组合被销毁,状态丢失。
- rememberSaveable:在 remember 的基础上,额外通过 Bundle 机制 将状态持久化。当配置变更触发 Activity 重建后,状态能从 Bundle 中自动恢复。
二者的核心差异在于 是否跨越配置变更。选择错误的函数会导致状态丢失或过度序列化,影响用户体验和性能。
核心原则:UI 状态(如输入框文本、滚动位置、开关状态)应使用 rememberSaveable;计算中间值、非 UI 状态(如 ViewModel 实例)应使用 remember。
4.2 对比分析
| 维度 | remember | rememberSaveable |
|---|---|---|
| 记忆范围 | 当前组合生命周期 | 组合生命周期 + Bundle 持久化 |
| 屏幕旋转保持 | ❌ 丢失 | ✅ 自动恢复 |
| 进程重建保持 | ❌ 丢失 | ⚠️ 取决于 Bundle 数据量 |
| 默认支持的数据类型 | 任意 Kotlin 对象 | 基本类型、String、Parcelable、Serializable |
| 自定义序列化 | 不需要 | 通过 Saver 接口 |
| 性能开销 | 低(内存仅) | 中等(序列化/反序列化) |
4.3 选型规则与决策流程
在实际开发中,应按照以下决策树选择正确的状态保持函数:
选型口诀:「配置变更需保持,Saveable 来保底;复杂对象存不进,自定义 Saver 来救急。」
4.4 自定义 Saver 实现
当使用 rememberSaveable 保存的数据类型不是 Bundle 原生支持时(如自定义数据类、第三方模型类),必须提供自定义 Saver。
Saver 接口定义如下:
interface Saaver<Original, Saveable> {
fun save(value: Original): Saveable
fun restore(value: Saveable): Original
}
其中 Saveable 必须是 Bundle 可存储的类型(String、Int、Boolean 等)。
4.5 完整代码示例
4.5.1 基础用法对比
@Composable
fun UnstableCounter() {
// ❌ 屏幕旋转后 count 丢失
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("丢失的计数: $count")
}
}
@Composable
fun StableCounter() {
// ✅ 屏幕旋转后 count 自动恢复
var count by rememberSaveable { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("持久的计数: $count")
}
}
4.5.2 自定义 Saver 示例
// 自定义数据类
data class UserPreferences(
val theme: String = "light",
val fontSize: Int = 14,
val notificationsEnabled: Boolean = true
)
// 实现 Saver:序列化为 String,格式 "theme:fontSize:notificationsEnabled"
val UserPreferencesSaver = Saver<UserPreferences, String>(
save = { "${it.theme}:${it.fontSize}:${it.notificationsEnabled}" },
restore = { savedString ->
val parts = savedString.split(":")
if (parts.size == 3) {
UserPreferences(
theme = parts[0],
fontSize = parts[1].toIntOrNull() ?: 14,
notificationsEnabled = parts[2].toBooleanStrictOrNull() ?: true
)
} else {
UserPreferences() // 恢复失败时返回默认值
}
}
)
@Composable
fun UserSettings() {
var preferences by rememberSaveable(stateSaver = UserPreferencesSaver) {
mutableStateOf(UserPreferences())
}
Column(modifier = Modifier.padding(16.dp)) {
Text("主题: ${preferences.theme}")
Text("字号: ${preferences.fontSize}")
Switch(
checked = preferences.notificationsEnabled,
onCheckedChange = { enabled ->
preferences = preferences.copy(notificationsEnabled = enabled)
}
)
}
}
4.5.3 使用 mapSaver 简化多字段序列化
// 利用 mapSaver 将数据序列化为 Map<String, String>
val UserPreferencesMapSaver = mapSaver(
save = { userPref ->
mapOf(
"theme" to userPref.theme,
"fontSize" to userPref.fontSize.toString(),
"notificationsEnabled" to userPref.notificationsEnabled.toString()
)
},
restore = { savedMap ->
UserPreferences(
theme = savedMap["theme"] ?: "light",
fontSize = savedMap["fontSize"]?.toIntOrNull() ?: 14,
notificationsEnabled = savedMap["notificationsEnabled"]?.toBooleanStrictOrNull() ?: true
)
}
)
@Composable
fun SettingsWithMapSaver() {
var prefs by rememberSaveable(stateSaver = UserPreferencesMapSaver) {
mutableStateOf(UserPreferences())
}
// ... UI 代码
}
4.6 调试方法
- Logcat 追踪:在自定义 Saver 的
save和restore中添加日志,观察序列化/反序列化时机。 - 布局检查器:使用 Android Studio 的 Layout Inspector 查看 Composable 树中
rememberSaveable保存的状态值。 - 配置变更测试:在模拟器中旋转设备(Ctrl+F11),或通过开发者选项中的「不保留活动」强制重建,检查状态是否恢复。
// 调试用 Saver 示例
val DebugSaver = Saver<UserPreferences, String>(
save = {
Log.d("SaverDebug", "save 调用,值: $it")
"${it.theme}:${it.fontSize}:${it.notificationsEnabled}"
},
restore = {
Log.d("SaverDebug", "restore 调用,原始串: $it")
val parts = it.split(":")
UserPreferences(
theme = parts.getOrElse(0) { "light" },
fontSize = parts.getOrElse(1) { "14" }.toIntOrNull() ?: 14,
notificationsEnabled = parts.getOrElse(2) { "true" }.toBooleanStrictOrNull() ?: true
)
}
)
4.7 常见错误与解决方案
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
| 屏幕旋转后状态回退到初始值 | 使用了 remember 而非 rememberSaveable |
替换为 rememberSaveable,必要时提供自定义 Saver |
| 编译报错「Type not supported」 | 数据类型不可被 Bundle 保存 | 实现自定义 Saver,转换为 String 或基本类型 |
| 恢复数据与保存数据不一致 | Saver 的 save/restore 逻辑不对称 | 单元测试 Saver:先 save 再 restore,验证相等性 |
| 保存大对象导致 ANR | Saver 中执行了 IO 或大量计算 | 仅保存必要字段;考虑使用 ViewModel 管理大数据 |
| 列表数据旋转后丢失 | mutableStateListOf 未配合 rememberSaveable |
使用 rememberSaveable(saver = listSaver(...)) |
⚠️ 重要:不要在 Saver 的
save 或 restore 中执行耗时操作(如网络请求、数据库读写)。Bundle 序列化发生在主线程,耗时操作会直接卡死 UI 并触发 ANR。
4.8 性能与稳定性优化
- 最小化序列化数据:只保存重建 UI 所必需的字段,避免保存派生数据或可重新计算的值。
- 使用 listSaver / mapSaver:官方提供的辅助函数可减少模板代码,同时保证类型安全。
- 避免在 Saver 中持有 Context 或 View:这些对象不可序列化,且会导致内存泄漏。
- 测试配置变更稳定性:在 CI 流程中加入旋转测试用例,确保关键页面状态恢复正确。
- 优先使用 ViewModel + SavedStateHandle:对于跨页面共享或复杂的业务状态,ViewModel 的生命周期更可控,且通过 SavedStateHandle 天然支持配置变更保持。
// 使用 listSaver 保存可变列表
val shoppingListSaver = listSaver<MutableList<String>, String>(
save = { it.toList() },
restore = { it.toMutableList() }
)
@Composable
fun ShoppingCart() {
var items by rememberSaveable(stateSaver = shoppingListSaver) {
mutableStateOf(mutableListOf<String>())
}
// ... 增删逻辑
}
💡 最佳实践:对于 UI 状态(文本、开关、选中项)首选
rememberSaveable;对于业务状态(数据列表、用户信息)首选 ViewModel + SavedStateHandle。二者结合使用,可以同时获得配置变更恢复和生命周期管理的优势。
4.9 总结
- remember 用于组合生命周期内的轻量状态,配置变更后丢失。
- rememberSaveable 用于需要跨越配置变更保持的 UI 状态,依赖 Bundle 序列化。
- 自定义 Saver 是扩展 rememberSaveable 支持非 Bundle 原生类型的关键接口。
- 选型决策应遵循「是否需要跨越配置变更 → 数据是否支持 Bundle → 是否自定义 Saver」的三步流程。
- 调试时重点关注 Saver 的 save/restore 对称性,并通过旋转测试验证。
掌握好 remember 与 rememberSaveable 的选型规则,能够有效避免状态丢失带来的 UI bug,同时避免过度序列化导致的性能问题,是构建稳定 Compose 应用的基础能力。