Jetpack Compose 深入面经
Jetpack Compose 核心知识
1. 重组原理
什么时候会重组?
- 状态变化时,依赖该状态的 Composable 会重组
- Compose 通过水平比较(Structural Equality)决定是否跳过重组
如何避免不必要的重组
// 1. @Stable / @Immutable 标记类,告知 Compose 安全跳过
@Stable // 所有 public property 变化时都通知 Compose
data class User(val name: String, val age: Int)
// 2. remember 缓存计算结果
val sortedList = remember(list) { list.sorted() }
// 3. derivedStateOf 对 state 派生新 state
val isScrolled = remember { derivedStateOf { scrollState.value > 0 } }
// 4. key() 给列表项设置稳定标识
Column {
items.forEach { item ->
key(item.id) { // 避免内容相同时不必要的重组
ItemComposable(item)
}
}
}
2. 状态管理
状态提升(State Hoisting)
// 非受控组件(自己持有状态)
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) { Text("$count") }
}
// 受控组件(状态提升到调用者)
@Composable
fun Counter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) { Text("$count") }
}
// 调用者
@Composable
fun ParentScreen() {
var count by remember { mutableStateOf(0) }
Counter(count = count, onIncrement = { count++ })
}
ViewModel 与 Compose 集成
@Composable
fun OrderScreen(viewModel: OrderViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
is Event.NavigateToSuccess -> navController.navigate("success")
is Event.ShowError -> showSnackbar(event.msg)
}
}
}
when (uiState) {
is Loading -> CircularProgressIndicator()
is Success -> OrderContent((uiState as Success).order)
is Error -> ErrorView((uiState as Error).message)
}
}
3. 布局与修饰器
// Modifier 顺序很重要!
Box(
Modifier
.size(100.dp) // 先设大小
.background(Color.Blue) // 再设背景(背景在内容区域内)
.padding(8.dp) // 再设内边距
.clickable { } // 点击区域包含 padding
)
// Modifier 和 UI 组件顺序的弱和强规则
// padding -> background 让内边距内没有背景
Text(
text = "Hello",
modifier = Modifier
.background(Color.Red) // 背景包含 padding 内容
.padding(16.dp)
)
自定义 Layout
@Composable
fun MyCustomLayout(
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
Layout(content = content, modifier = modifier) { measurables, constraints ->
val placeables = measurables.map { it.measure(constraints) }
val width = placeables.maxOf { it.width }
val height = placeables.sumOf { it.height }
layout(width, height) {
var y = 0
placeables.forEach {
it.placeRelative(0, y)
y += it.height
}
}
}
}
4. 动画系统
// 状态动画
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(durationMillis = 300)
)
// 可见性动画
AnimatedVisibility(
visible = visible,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically()
) {
MyContent()
}
// 转场动画
AnimatedContent(
targetState = currentPage,
transitionSpec = { fadeIn() togetherWith fadeOut() }
) { page ->
PageContent(page)
}
// 共享元素过渡
// Screen A
Box(Modifier.sharedElement(rememberSharedContentState("image"), ...) ) {
Image(...)
}
// Screen B 中同样的 sharedElement key 就会自动过渡
5. 性能优化
// 1. 长列表用 LazyColumn
LazyColumn {
items(items = list, key = { it.id }) { item -> // key 必须提供
ItemCard(item)
}
}
// 2. 避免在 composable 中读取 MutableList
// bad: list.forEach { ... } -> 无法追踪变化
// good: val items by rememberUpdatedState(newItems)
// 3. 使用 Baseline Profiles 提升启动性能
// 在 macrobenchmark 中定义核心路径,编译器预编译这些类
// 4. 不要在 Composable 中做老不必要的异步工作
// bad: 在 composable 中直接 launch
// good: 使用 LaunchedEffect(key) { ... }
// 5. 提副本加载验证
CompositionLocal
6. Compose 中的副作用
// LaunchedEffect:进入居和成立时执行,key 变化时重新执行
LaunchedEffect(userId) {
viewModel.loadUser(userId) // 副作用都在协程中执行
}
// SideEffect:每次重组成功后执行,不可以挂起
SideEffect {
analytics.reportScreen("OrderScreen") // 同步,安全访问外部系统
}
// DisposableEffect:组件离开时需清理
DisposableEffect(sensor) {
sensor.start()
onDispose { sensor.stop() } // 离开成居时执行
}
// rememberCoroutineScope:获取协程作用域,用于事件回调中
val scope = rememberCoroutineScope()
Button(onClick = { scope.launch { doSomething() } }) { ... }
7. 资深面试题
- Compose 与传统 View 的格局算法比较?
- 传统 View:Measure(36项 spec) -> Layout -> Draw,可多次 Measure
- Compose:单次 Measure(展开 Composable) -> 分配大小 -> 放置 -> 绘制
- Compose 由设计上保证单次 Measure,性能更可预测
- remember 在配置变化后是否会清除?
- 默认会清除,用
rememberSaveable写入 Bundle 保持状态 - 复杂对象用
Saver接口自定义序列化
- 默认会清除,用
- derivedStateOf 和 remember { ... } 的区别?
remember缓存结果,依赖的 key 变化时重新计算derivedStateOf用于导出 state,内部 state 变化时才重算,避免不必要重组derivedStateOf应包在remember { }里
- 如何在 Compose 中使用传统 View?
AndroidView { context -> CustomView(context).apply { ... } }AndroidViewBinding使用 ViewBindingsetViewCompositionStrategy管理成居策略