Jetpack Compose 性能优化实战

Jetpack Compose 性能优化实战

从原理到实践,打造流畅的 Compose 应用

目录

  1. Compose 渲染原理概述
  2. 重组(Recomposition)优化
  3. 状态管理最佳实践
  4. 作用域与 remember 的正确使用
  5. 实战案例:列表性能优化

1. Compose 渲染原理概述

Composable 函数的本质

Compose 不是”命令式 UI”的替代,而是声明式的描述:

1
2
3
4
5
6
@Composable
fun MyButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick) {
Text(text)
}
}

每次重组时:

  1. Composable 函数重新执行
  2. 描述新的 UI 状态
  3. Compose 比较新旧”描述”(diffing)
  4. 只更新实际变化的 UI

重组(Recomposition)触发条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun Counter() {
var count by remember { mutableIntStateOf(0) }

// ❌ 错误:每次重组都创建新 lambda
Button(onClick = { count++ }) {
Text("Count: $count")
}

// ✅ 正确:使用 lambda 引用
Button(onClick = { count++ }) {
Text("Count: $count")
}
}

真正触发重组的条件

  • State 对象发生 equals 变化
  • @Composable 参数变化

2. 重组(Recomposition)优化

问题:过度重组

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val uiState by viewModel.uiState.collectAsState()

// ❌ 问题:uiState 任何变化都导致整个屏幕重组
Column {
Header(uiState.user)
Content(uiState.posts)
Footer(uiState.timestamp)
}
}

解决:细化状态粒度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ✅ ViewModel 中拆分状态
data class UserUiState(
val user: User = User(),
val posts: List<Post> = emptyList(),
val timestamp: Long = 0
)

// ❌ 还是会有问题 - 一个字段变化,整个 State 变化

// ✅ 正确做法:使用多个 StateFlow
class UserViewModel : ViewModel() {
private val _user = MutableStateFlow(User())
val user: StateFlow<User> = _user.asStateFlow()

private val _posts = MutableStateFlow<List<Post>>(emptyList())
val posts: StateFlow<List<Post>> = _posts.asStateFlow()
}

// ✅ 正确做法:使用 rememberSaveable + deriveStateFrom
@Composable
fun UserScreen(
user: User,
posts: List<Post>,
timestamp: Long
) {
Column {
Header(user) // 只有 user 变化才重组
Content(posts) // 只有 posts 变化才重组
Footer(timestamp) // 只有 timestamp 变化才重组
}
}

使用 DerivedStateOf 避免不必要的计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Composable
fun ArticleList(articles: List<Article>) {
// ❌ 每次 articles 变化都重新计算
val sortedArticles = articles.sortedByDescending { it.date }

// ✅ 只有 articles 内容变化时才重新排序
val sortedArticles by remember(articles) {
derivedStateOf { articles.sortedByDescending { it.date } }
}

LazyColumn {
items(sortedArticles) { article ->
ArticleItem(article)
}
}
}

3. 状态管理最佳实践

避免不必要的 State 提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun BadExample() {
var text by remember { mutableStateOf("") }
var list by remember { mutableStateOf(listOf<String>()) }

// ❌ 状态在父组件但只在子组件使用
ChildComponent(text = text, list = list)
}

@Composable
private fun ChildComponent(text: String, list: List<String>) {
// 只在这里使用
Text(text)
}

正确:状态就近管理

1
2
3
4
5
6
@Composable
fun GoodExample() {
// ✅ 状态在需要的最低层级
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it })
}

ViewModel 状态如何暴露

1
2
3
4
5
6
7
8
class MyViewModel : ViewModel() {
// ✅ 正确:暴露不可变 StateFlow
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()

// ❌ 错误:暴露可变 MutableStateFlow
val mutableState = MutableStateFlow(UiState())
}

4. 作用域与 remember 的正确使用

理解 Composable 作用域

1
2
3
4
5
6
7
8
9
10
11
12
@Composable
fun Parent() {
// ❌ 错误:remember 不能跨作用域
val data = remember { loadData() } // 在 Parent 作用域

Child() // Child 无法访问 data
}

@Composable
private fun Child() {
// 这里无法访问 Parent 的 remember
}

正确使用 rememberSaveable

1
2
3
4
5
6
7
8
9
10
11
@Composable
fun Screen() {
// ✅ 跨配置变更保存状态
var name by rememberSaveable { mutableStateOf("") }
var age by rememberSaveable { mutableIntStateOf(0) }

// ✅ 带键的 remember - 键变化时重新计算
val cachedData by remember(key1 = userId) {
repository.getUserData(userId)
}
}

mutableStateOf vs mutableIntStateOf

1
2
3
4
5
6
7
8
9
10
11
12
13
@Composable
fun PerformanceDemo() {
// ❌ 使用 Any 类型,会有装箱开销
var count by remember { mutableStateOf(0) }
count++

// ✅ 使用原始类型,避免装箱
var count by remember { mutableIntStateOf(0) }
count++

// ✅ 推荐:使用 DoubleState, FloatState 等
var value by remember { mutableFloatStateOf(0f) }
}

5. 实战案例:列表性能优化

原始版本:性能问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Composable
fun ArticleList(articles: List<Article>) {
LazyColumn {
items(articles.size) { index ->
val article = articles[index]

// ❌ 问题 1:每次都创建新对象
ArticleCard(
title = article.title,
// ❌ 问题 2:lambda 每次都重新创建
onClick = { /* handle click */ },
// ❌ 问题 3:没有使用 key
modifier = Modifier.fillMaxWidth()
)
}
}
}

优化版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun ArticleList(articles: List<Article>) {
LazyColumn {
// ✅ 使用 items 带 key
items(
items = articles,
key = { it.id } // 稳定 ID,保留重组状态
) { article ->
// ✅ 使用 remember 缓存 click handler
val onClick = remember(article.id) {
{ /* handle click */ }
}

ArticleCard(
title = article.title,
onClick = onClick,
modifier = Modifier.fillMaxWidth()
)
}
}
}

进阶:懒加载 + 预取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Composable
fun OptimizedList(
viewModel: ListViewModel = hiltViewModel()
) {
val items by viewModel.items.collectAsState()

LazyColumn(
// ✅ 预加载视图之外的项
beyondBoundsItemCount = 5
) {
items(
items = items,
key = { it.id }
) { item ->
ListItem(
item = item,
onClick = { viewModel.onItemClick(item.id) }
)
}
}
}

使用 AsyncImage 优化图片加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Composable
fun ImageOptimized() {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(url)
.crossfade(true)
.memoryCacheKey(url)
.diskCacheKey(url)
.build(),
contentDescription = null,
modifier = Modifier.fillMaxWidth(),
// ✅ 使用 contentScale 避免重排
contentScale = ContentScale.Crop
)
}

性能检查清单

检查项 状态
使用 rememberSaveable 保存 UI 状态
LazyColumn 使用 key 参数
使用 derivedStateOf 避免重复计算
避免在 Composable 中创建 lambda
使用原始类型 mutableIntStateOf
列表使用 AsyncImage 加载图片
StateFlow 暴露给 Compose
避免过度状态提升

总结

Compose 性能优化的核心:

  1. 减少重组:细化状态粒度,使用 derivedStateOf
  2. 避免重排:为列表项提供稳定 key
  3. 减少计算:使用 remember 缓存复杂计算
  4. 正确引用:避免创建不必要的 lambda

相关文章