MutableState — 是 Jetpack Compose 中的一个接口,表示可变可观察值的容器。它是 Compose 响应式系统的基础:每次 MutableState 的值通过 setter 改变时,Compose Runtime 会通知所有读取组件并触发重组(recomposition)。根据 Google Android Developers, 2026,理解 MutableState 是在声明式 UI 中正确处理状态的必要条件。
要点
MutableState — 是来自 androidx.compose.runtime 包的一个接口,声明了一个属性:override var value: T。Getter 返回当前值,setter 写入新值并通知 Compose Runtime 关于更改。该接口继承自 State<T>,其中 value 仅可读。这种两层架构允许分离访问:只需要读取值的组件获得 State<T>,而所有者组件获得 MutableState<T>。
MutableState 的默认实现是内部类 SnapshotMutableStateImpl,它使用 snapshot 机制来跟踪更改。当 value setter 被调用时,当前 snapshot 记录写入并将所有注册的 ObservedScope(观察范围)标记为无效。这些范围(通常是可组合函数)将在下一帧中重组。整个过程同步且无阻塞地发生,得益于 Lock-free snapshot 架构。
State 与 MutableState:State — 是只读接口,用于组件的公共 API。当您将可组合函数的参数声明为 State<Int> 时,您保证该组件可以读取但不能更改状态。MutableState 在所有者组件内部使用。这种分离是 Compose 的基本实践之一,防止未经授权的更改。
Compose 中状态接口的层次结构有几个级别。顶层是 State<T>,具有只读 value。下面是 MutableState<T>,具有读写 value。然后是专门的原始版本:MutableIntState、MutableFloatState、MutableLongState、MutableBooleanState 等,它们避免了原始类型的自动装箱(boxing)。
MutableDoubleState 和 MutableLongState — 不太常见但存在的类型。集合接口:MutableListState — 用于跟踪列表内部变化,MutableStateMap — 用于映射。每个接口都针对特定场景进行了优化,并通过额外的集合操作方法扩展了基本的 MutableState。
SnapshotStateList 和 SnapshotStateMap — 是与 snapshot 兼容的可变列表和映射的实现。它们不仅允许跟踪值的替换,还允许跟踪内部更改:向列表添加元素、删除、修改现有元素。对于此类结构,mutableStateListOf() 和 mutableStateMapOf() 创建相应的可观察集合。
| 接口 | 用途 | 创建方法 |
|---|---|---|
| State<T> | 只读容器 | — |
| MutableState<T> | 读写容器 | mutableStateOf() |
| MutableIntState | 无装箱的原始 Int | mutableIntStateOf() |
| MutableFloatState | 无装箱的原始 Float | mutableFloatStateOf() |
| SnapshotStateList | 可观察列表 | mutableStateListOf() |
| SnapshotStateMap | 可观察映射 | mutableStateMapOf() |
SnapshotMutationPolicy — 是一个接口,确定 MutableState 的更改何时被视为有意义。mutableStateOf 接受 policy 作为第二个参数。标准实现:structuralEquality()(equals)、referentialEquality()(===)、neverEqualPolicy()(始终认为更改有意义)。对于自定义逻辑,您可以实现自己的 policy。
structuralEquality() — 默认行为。Compose 通过 equals() 比较新值与旧值。如果结果为 true — 不触发重组。这对于原始类型和 data class 很方便,其中两个具有相同字段的实例被视为相等。问题:如果 data class 包含 List,equals() 执行深度比较,对于大型列表可能代价高昂。
referentialEquality() — 通过 === 比较引用。仅当分配另一个对象时才触发重组,即使内容相同。这对于不可变的 data class 是最优的,其中每个新实例都保证意味着更改。neverEqualPolicy() — 始终认为更改有意义,不进行比较。当 setter 不经常调用且不需要花费时间在 equals 上时很有用。
// 策略对比实践
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality:仅在数据更改时触发重组
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality:在每次赋值时触发重组
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1:copy() 相同字段不会触发重组
// user2:即使 user2.copy() == user2 也会触发重组(新引用)
}
原始 MutableIntState 等 — 是专门化的接口,存储原始类型而无自动装箱。普通的 MutableState<Int> 将 Int 存储为 Integer,每次写入都会在堆上创建一个对象。MutableIntState 存储 int(原始类型),完全消除了装箱开销。这在高频更新时尤其重要 — 计数器、滚动位置、动画值。
mutableIntStateOf()、mutableFloatStateOf()、mutableLongStateOf() — 创建原始 MutableState 的函数。接口分别称为 MutableIntState、MutableFloatState、MutableLongState。它们分别扩展了 MutableState<Int>、MutableState<Float> 和 MutableState<Long>,添加了 intValue 属性作为对原始类型的快速访问。在它们的内部实现中,使用 AtomicInteger 进行无锁读/写。
应用:计数器(Int)、滚动位置(Float offset)、时间戳(Long)。在大多数日常场景中,性能差异不可察觉,但在具有数千个元素和过渡动画的 LazyList 中,原始 State 提供了显著的提升。Google 建议对典型场景使用原始 State 而不是通用的 mutableStateOf。
@Composable
fun ScrollCounter() {
// 不好:每次更新时装箱
var badCount by remember { mutableStateOf(0) }
// 好:无装箱,原始存储
var goodCount by remember { mutableIntStateOf(0) }
// 使用方式相同
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
让我们考虑 TodoList 组件,其中 MutableState 以两种形式使用:作为输入状态的单独变量和作为动态任务列表的 SnapshotStateList。两者都使用委托来简化代码。
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)
@Composable
fun TodoScreen() {
var inputText by remember { mutableStateOf("") }
val items = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = inputText,
onValueChange = { inputText = it }
)
Button(onClick = {
if (inputText.isNotBlank()) {
items.add(TodoItem(items.size, inputText))
inputText = ""
}
}) { Text("添加") }
}
LazyColumn {
items(items) { item ->
Row(modifier = Modifier.fillMaxWidth().clickable {
val idx = items.indexOf(item)
items[idx] = item.copy(isDone = !item.isDone)
}) {
Checkbox(checked = item.isDone, onCheckedChange = null)
Text(item.text)
}
}
}
}
}
mutableStateListOf 创建一个 SnapshotStateList — 一个可变列表,跟踪各个元素的变化。当调用 items.add() 和 items[n] = newValue 时,Compose 看到变更并仅重组那些已更改的 LazyColumn 元素。inputText — 普通的 MutableState<String>。两种 MutableState 类型(单个和集合)的组合是带有表单和列表的屏幕的典型模式。
常见问题
没有 remember 的 MutableState 将在每次重组时重新创建。每次新的 mutableStateOf 调用都会创建一个新对象,旧值会丢失。始终使用 remember 在重组之间保持 State,除非 State 是在可组合函数外部创建的(例如在 ViewModel 中)。
通过 snapshot { } 在 snapshot 外部读取一次 .value。但这会禁用响应性 — 更改将不再触发重组。对于无需订阅的一次性读取,在 snapshot 内部使用 currentValue() 而不进行读取。
mutableIntStateOf 更快,因为它不需要将 int 装箱为 Integer。在每秒数千次更新(动画、滚动)时,差异可能达到分配时间的 30-50%。在罕见的更新(点击、文本输入)时,差异可以忽略不计。
可以,但不推荐。不要传递 MutableState,而是传递 State(只读)加上 onValueChange lambda。这实现了 State Hoisting 模式并使组件可复用。接受 MutableState 的组件违反了单向数据流。
实现 MutableState 接口并提供带有 getter 和 setter 的 override var value。在 setter 中可以添加验证或日志记录。为了与 Compose Runtime 向后兼容,将您的实现包装在 snapshotFlow 中或使用 snapshotIncrement。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。