Composable 函数与重组
问题
Composable 函数是什么?Recomposition(重组)的机制是怎样的?
答案
1. Composable 函数基础
@Composable 注解标记的函数可以发射 UI 元素:
@Composable
fun Greeting(name: String) {
Text(text = "Hello, $name!")
}
@Composable
fun UserCard(user: User) {
Card(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = user.name, style = MaterialTheme.typography.titleMedium)
Text(text = user.email, style = MaterialTheme.typography.bodyMedium)
}
}
}
Composable 函数的约束
- 只能在其他
@Composable函数或 Composition 上下文中调用 - 没有返回值(返回 Unit),而是发射 UI 节点
- 可以任意次数、任意顺序、在任何线程被重新执行
- 不应包含副作用(网络请求、数据库操作等)
2. Recomposition(重组)机制
当 Composable 读取的状态发生变化时,Compose 运行时会重新执行该 Composable 函数,这个过程称为重组。
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) } // 状态
Button(onClick = { count++ }) { // 修改状态
Text("点击了 $count 次") // 读取状态 → count 变化时此处重组
}
}
3. 智能重组(Intelligent Recomposition)
Compose 编译器会跳过参数未变化的 Composable:
@Composable
fun UserProfile(name: String, age: Int) {
NameSection(name) // name 没变就跳过
AgeSection(age) // age 变了才重组
StaticSection() // 无参数,永远跳过
}
跳过的条件是参数具有稳定性(Stability):
- 基本类型(Int、String、Boolean 等)→ 天然稳定
data class(所有属性都是 val 且稳定类型)→ 稳定- 包含
var属性或List(非SnapshotStateList)→ 不稳定
// ✅ 稳定 - 所有属性都是 val + 不可变类型
data class User(val name: String, val age: Int)
// ❌ 不稳定 - 包含 List(被认为可变)
data class UserList(val users: List<User>)
// ✅ 添加 @Immutable 注解标记为稳定
@Immutable
data class UserList(val users: List<User>)
4. @Composable 编译原理
Compose 编译器插件会对 @Composable 函数做以下处理:
- 插入
Composer参数:追踪 Composition 树 - 插入记忆化代码:缓存计算结果
- 插入比较逻辑:判断参数是否变化以决定是否跳过
- 生成 Group 标记:标识 Composable 在树中的位置
常见面试问题
Q1: Composition 和 Recomposition 有什么区别?
答案:
- Composition(初始组合):首次运行 Composable 函数,构建 UI 树。类似 View 体系的
inflate+onCreate - Recomposition(重组):状态变化后重新执行相关 Composable 函数,更新 UI 树中变化的部分
Compose 运行时通过差异比较只更新变化的节点,类似 React 的 Virtual DOM diff。
Q2: 为什么 Composable 函数不能有副作用?
答案:
因为 Composable 函数可能被多次、乱序、并行执行。如果包含副作用(如网络请求),可能导致:
- 重组时重复发起请求
- 执行顺序不可预测
- 被跳过时副作用丢失
副作用应该放在专用的 Effect API 中(如 LaunchedEffect)。
Q3: remember 的作用是什么?不用 remember 会怎样?
答案:
remember 在 Composition 中缓存一个值,在重组时保持该值不变:
// ✅ 正确:remember 保持 state 跨重组存活
var count by remember { mutableStateOf(0) }
// ❌ 错误:每次重组都会创建新的 state,count 永远是 0
var count by mutableStateOf(0)
remember 的生命周期与所在 Composable 在 Composition 树中的位置绑定。当 Composable 从树中移除时,remember 的值也会被清除。
Q4: key 在 Compose 中有什么作用?
答案:
key 用于帮助 Compose 识别列表中的元素身份,类似 React 的 key prop:
LazyColumn {
items(users, key = { it.id }) { user ->
UserItem(user)
}
}
没有 key 时,Compose 按索引匹配。如果列表中间插入元素,后续所有 Item 都会重组。有 key 后,Compose 可以精确追踪每个 Item,只重组真正变化的。
Q5: @Stable 和 @Immutable 有什么区别?
答案:
@Immutable:标记类的所有属性在构造后永远不会变化。这是更强的保证@Stable:标记类满足以下条件:- 对同一实例调用
equals的结果永远一致 - 公共属性变化时会通知 Composition(如使用
mutableStateOf)
- 对同一实例调用
两者都告诉 Compose 编译器可以安全地跳过重组。@Immutable 比 @Stable 约束更严格。