remember — 是 Jetpack Compose 中的一个函数,它允许在重组之间保留值,防止每次重新启动 Composable 函数时重置状态。如果没有 remember,函数内部声明的所有变量将在每次 UI 更新时重置,使状态变得不稳定且无用。根据 Google Android Developers, 2026,正确使用 remember 是在声明式 UI 中正确管理状态的基础。
要点
remember — 是 Jetpack Compose 中来自 androidx.compose.runtime 包的内置函数,它创建一个在 Composable 函数连续调用之间保持其值的对象。从技术上讲,remember 使用槽工作 — 为 UI 树中每个函数调用位置分配的特殊内存单元。只要函数保持在组合中(未通过 if 条件移除),remember 将在每次重组时返回保存的值。
remember 的语法很简单:在大括号内指定初始值的计算块。该块只执行一次 — 在函数的第一次组合时。所有后续重组返回已保存的值,而不重新调用该块。但是,如果指定了键(key 参数),则在键更改时重新执行该块,从而允许根据输入数据重新计算值。
remember 不是 Kotlin 魔法 — 它是一个带有 key 参数和计算 lambda 的简单函数。其内部实现使用 Composer 来读写槽。Kotlin 编译器在看到 remember 调用时,会生成访问当前 CompositionContext 并管理槽的代码。这意味着 remember 只能在 Composable 函数内部或在组合上下文中的另一个函数内部调用。
在 Jetpack Compose 中,remember 有三种主要变体,各有其用途。基本 remember 仅在当前进程的内存中保留值 — 屏幕旋转时(配置更改),Compose 重新创建 Composition,所有 remember 值都会重置。要在配置更改和进程停止时保留数据,请使用 rememberSaveable。
rememberSaveable 通过 SavedStateHandle 或 Parcelable 机制将值序列化到 Bundle 中。这允许在屏幕旋转、“不保留活动”调用甚至应用程序临时最小化时存活。但是,rememberSaveable 对保存的数据类型施加限制:它们必须是原始类型、Parcelable、Serializable 或支持 Saver 转换器。
derivedStateOf — 不是存储机制,而是优化。它创建一个 State,其值基于其他 State 对象计算。derivedStateOf 响应源状态的变化,但仅在有订阅者时重新计算值。如果当前重组未读取 derivedStateOf,则不执行计算,从而在频繁但无人需要的更新中节省资源。
@Composable
fun RememberVariants() {
// 1. 基本 remember:持续到离开组合
val createdAt = remember { System.currentTimeMillis() }
// 2. rememberSaveable:存活于屏幕旋转
var username by rememberSaveable { mutableStateOf("") }
// 3. derivedStateOf:仅在需要时计算
val isButtonVisible = remember {
derivedStateOf { username.length() > 3 }
}
Text("创建时间:$createdAt")
TextField(value = username, onValueChange = { username = it })
if (isButtonVisible.value) {
Text("按钮将显示")
}
}
在 remember 和 rememberSaveable 之间的选择取决于在配置更改时数据保留的紧迫程度。对于旋转时丢失不重要的临时状态(动画、当前滚动位置、焦点状态) — 普通的 remember 就足够了。对于关键数据(表单中的文本、选定的项目、复选框) — 使用 rememberSaveable。
性能 rememberSaveable 低于普通的 remember,因为它需要序列化到 Bundle。仅对确实必须存活 Activity 重建的数据使用 rememberSaveable。对于其他一切 — 普通的 remember。过度使用 rememberSaveable 会导致屏幕旋转和应用程序之间切换时变慢。
如果您使用的类不支持 Parcelable 或 Serializable,请使用 Saver — 一个定义如何保存和恢复对象的转换器。Saver 由一对 lambda 描述:save(将对象转换为可保存类型)和 restore(从保存的数据恢复对象)。标准的 Saver 已为 mutableStateListOf 和 mutableStateMapOf 实现。
| 特性 | remember | rememberSaveable |
|---|---|---|
| 重组时保留 | 是 | 是 |
| 旋转时保留 | 否 | 是 |
| 进程停止时保留 | 否 | 是 |
| 类型要求 | 任意 | Parcelable、Serializable、Saver |
| 性能 | 高 | 中等 |
让我们看一个典型场景 — 个人资料编辑屏幕,其中 remember 用于多个目的:存储表单字段的状态、计算派生值以及缓存计算成本高昂的操作。
data class ProfileUiState(
val name: String = "",
val bio: String = "",
val isSaving: Boolean = false
)
@Composable
fun ProfileEditor() {
var state by rememberSaveable { mutableStateOf(ProfileUiState()) }
val isValid = remember {
derivedStateOf { state.name.isNotBlank() && state.bio.length() <= 500 }
}
val bioWarning = remember(state.bio) {
if (state.bio.length() > 400) {
"${500 - state.bio.length} chars left"
} else null
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = state.name,
onValueChange = { state = state.copy(name = it) },
label = { Text("名称") }
)
OutlinedTextField(
value = state.bio,
onValueChange = { state = state.copy(bio = it) },
label = { Text("关于") }
)
bioWarning?.let { Text(it, color = MaterialTheme.colorScheme.error) }
Button(onClick = { /* save */ },
enabled = isValid.value) {
Text("保存")
}
}
}
在该示例中,状态通过 rememberSaveable 保存 — 屏幕旋转时文本不会丢失。isValid 通过 derivedStateOf 计算,这防止了重组时的多余计算。bioWarning 使用带 bio 键的 remember — 这是一个昂贵的计算(可选,用于演示),仅在 bio 更改时重新计算,而不是在每次重组时。
派生状态是从其他 State 对象计算的值。与其在每次重组时计算它们并将 CPU 浪费在相同的结果上,remember 与 derivedStateOf 仅在源更改时计算值。这对于过滤、排序和数据聚合特别有用。
带键的 remember(remember(key) { calculation })— 另一种优化机制。如果键自上次重组以来未更改,则不执行计算块,并返回缓存的值。这对于缓存创建成本高昂的对象很方便:日期格式化、JSON 解析、创建大型不可变集合。
@Composable
fun SearchResults(allItems: List<Item>, query: String) {
// derivedStateOf:过滤器仅在输入更改时重新计算
val filtered = remember {
derivedStateOf {
allItems.filter { it.title.contains(query, true) }
}
}
// 带键的 remember:格式化统计信息仅在查询更改时重新计算
val statsText = remember(query) {
"Results for query \"$query\": ${filtered.value.size}"
}
Text(statsText)
LazyColumn {
items(filtered.value, key = { it.id }) { item ->
Text(item.title)
}
}
}
filtered — 是一个 derivedStateOf,在 allItems 或 query 更改时自动重新计算。statsText 使用 remember(query) — 昂贵的字符串格式化仅在搜索查询更改时执行。derivedStateOf 和带键的 remember 的组合提供了最大性能:派生状态仅在需要时计算,复杂对象在键更改之前保持缓存。
常见问题
不可以,remember — 是来自 compose.runtime 包的函数,需要 CompositionContext。它只能在 @Composable 函数内部或从 Composable 调用的另一个函数内部调用。要在组合外部存储数据,请使用 ViewModel。
没有键的情况下,remember 只计算一次块 — 在第一次组合时。所有后续重组返回保存的值。如果需要在数据更改时重新计算值,请务必将它们指定为键:remember(data) { compute(data) }。
remember 没有直接重置的方法。唯一的方法是将函数从组合中移除(例如,通过 if 条件隐藏),然后重新显示它。重新进入时,remember 块将重新执行,创建新的初始值。
remember 将状态存储在 Composable 函数的槽中,并在函数处于组合中时存活。ViewModel 在屏幕的生命周期内存活。ViewModel 在旋转时保留并用于业务逻辑。remember — 用于不需要超出单个函数范围的本地 UI 状态。
是的,remember 在 @Preview Composable 函数中正常工作,因为 Preview 创建了完整的 CompositionContext。但是,rememberSaveable 在 Preview 中可能无法正常工作,因为预览环境中可能缺少 SavedStateHandle。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。