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 来救急。」
remember ↔ rememberSaveable 选型决策流程 状态管理需求 需要跨越配置变更? 使用 rememberSaveable 使用 remember 数据支持 Bundle 自动保存? 使用默认 Saver 实现自定义 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 调试方法

  1. Logcat 追踪:在自定义 Saver 的 save 和 restore 中添加日志,观察序列化/反序列化时机。
  2. 布局检查器:使用 Android Studio 的 Layout Inspector 查看 Composable 树中 rememberSaveable 保存的状态值。
  3. 配置变更测试:在模拟器中旋转设备(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 应用的基础能力。