Android Compose 重组规则

17 阅读1分钟

重组基本概念

重组(Recomposition)是Compose更新UI的核心机制,当状态发生变化时,Compose会重新执行相关的Composable函数来更新UI。

触发重组的情况

1. State状态变化

1.1 MutableState变化

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    // ✅ count变化时触发重组
    Text("Count: $count")
    Button(onClick = { count++ }) {
        Text("Increment")
    }
}

1.2 StateFlow/LiveData变化

@Composable
fun UserProfile(viewModel: UserViewModel) {
    // ✅ collectAsState()监听状态变化
    val user by viewModel.userState.collectAsState()
    
    Text("Name: ${user.name}") // user变化时重组
}

1.3 derivedStateOf变化

@Composable
fun FilteredList(items: List<String>, query: String) {
    // ✅ 当items或query变化时,filteredItems会重新计算
    val filteredItems by remember(items, query) {
        derivedStateOf {
            items.filter { it.contains(query, ignoreCase = true) }
        }
    }
    
    LazyColumn {
        items(filteredItems) { item ->
            Text(item)
        }
    }
}

2. 参数变化

2.1 直接参数变化

@Composable
fun Greeting(name: String) { // ✅ name变化时重组
    Text("Hello, $name!")
}

@Composable
fun Parent() {
    var name by remember { mutableStateOf("World") }
    
    Greeting(name = name) // name变化导致Greeting重组
}

2.2 Lambda参数变化

@Composable
fun Parent() {
    var count by remember { mutableStateOf(0) }
    
    // ❌ 每次重组都创建新的lambda,导致Child重组
    Child(onClick = { count++ })
    
    // ✅ 使用remember缓存lambda
    val onClick = remember { { count++ } }
    Child(onClick = onClick)
}

3. 父组件重组导致的级联重组

@Composable
fun Parent() {
    var parentState by remember { mutableStateOf(0) }
    
    Column {
        Text("Parent: $parentState") // 状态变化
        
        // ❌ 父组件重组时,所有子组件默认也会重组
        Child1()
        Child2()
        Child3()
    }
}

// ✅ 解决方案:使用稳定的参数或记忆化
@Composable
fun OptimizedParent() {
    var parentState by remember { mutableStateOf(0) }
    
    Column {
        Text("Parent: $parentState")
        
        // ✅ 使用remember避免重组
        remember { Child1() }
        Child2() // 如果Child2没有参数且内容稳定,可能会跳过重组
    }
}

4. 不稳定参数导致的重组

4.1 不稳定的数据类

// ❌ 不稳定的类
class UnstableData(var value: String)

@Composable
fun UnstableComponent(data: UnstableData) {
    // 即使data.value没变,也可能重组
    Text(data.value)
}

// ✅ 稳定的数据类
@Stable
data class StableData(val value: String)

// 或使用@Immutable
@Immutable
data class ImmutableData(val value: String)

4.2 函数类型参数

@Composable
fun Parent() {
    var state by remember { mutableStateOf(0) }
    
    // ❌ 每次重组创建新函数
    Child { state++ }
    
    // ✅ 缓存函数引用
    val callback = remember(state) { { state++ } }
    Child(callback)
}

5. CompositionLocal变化

val LocalTheme = compositionLocalOf { LightTheme }

@Composable
fun App() {
    var isDark by remember { mutableStateOf(false) }
    
    CompositionLocalProvider(
        LocalTheme provides if (isDark) DarkTheme else LightTheme
    ) {
        // ✅ LocalTheme变化时,所有使用它的组件都会重组
        ThemedContent()
    }
}

@Composable
fun ThemedContent() {
    val theme = LocalTheme.current // 监听主题变化
    Box(backgroundColor = theme.backgroundColor) {
        Text("Themed content")
    }
}

6. Side Effects触发的重组

6.1 LaunchedEffect

@Composable
fun TimerComponent() {
    var time by remember { mutableStateOf(0) }
    
    // ✅ 定时器更新state,触发重组
    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            time++
        }
    }
    
    Text("Time: $time")
}

6.2 DisposableEffect

@Composable
fun LocationComponent() {
    var location by remember { mutableStateOf<Location?>(null) }
    
    DisposableEffect(Unit) {
        val listener = LocationListener { newLocation ->
            location = newLocation // ✅ 触发重组
        }
        
        onDispose { /* cleanup */ }
    }
    
    Text("Location: ${location?.toString() ?: "Unknown"}")
}

重组优化策略

1. 使用稳定的参数

// ✅ 使用不可变数据
@Immutable
data class UserInfo(val name: String, val age: Int)

@Composable
fun UserCard(user: UserInfo) {
    // user是稳定的,只有实际内容变化才重组
}

2. 合理使用remember

@Composable
fun ExpensiveComponent(data: List<String>) {
    // ✅ 缓存复杂计算结果
    val processedData = remember(data) {
        data.map { it.uppercase() }.sortedBy { it.length }
    }
    
    LazyColumn {
        items(processedData) { item ->
            Text(item)
        }
    }
}

3. 状态提升和局部化

// ❌ 状态在顶层,导致整个组件树重组
@Composable
fun BadExample() {
    var searchQuery by remember { mutableStateOf("") }
    var isLoading by remember { mutableStateOf(false) }
    
    Column {
        SearchBar(query = searchQuery, onQueryChange = { searchQuery = it })
        LoadingIndicator(isLoading = isLoading)
        ContentList(query = searchQuery)
    }
}

// ✅ 状态局部化
@Composable
fun GoodExample() {
    Column {
        SearchSection()    // 搜索状态只影响这个区域
        LoadingSection()   // 加载状态只影响这个区域
        ContentSection()   // 内容状态只影响这个区域
    }
}

4. 使用key()优化列表

@Composable
fun ItemList(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { item -> item.id } // ✅ 提供稳定的key
        ) { item ->
            ItemRow(item)
        }
    }
}

5. 避免在Composable中创建对象

@Composable
fun BadComponent() {
    // ❌ 每次重组都创建新对象
    val padding = PaddingValues(16.dp)
    val shape = RoundedCornerShape(8.dp)
    
    Box(modifier = Modifier.padding(padding))
}

@Composable
fun GoodComponent() {
    // ✅ 在外部定义或使用remember
    Box(modifier = Modifier.padding(16.dp))
}

// 或者
private val DefaultPadding = PaddingValues(16.dp)
private val DefaultShape = RoundedCornerShape(8.dp)

总结

重组触发的主要情况:

  1. State变化 - 最常见的触发原因
  2. 参数变化 - 包括直接参数和lambda参数
  3. 父组件重组 - 级联效应
  4. 不稳定参数 - 导致不必要的重组
  5. CompositionLocal变化 - 影响范围广
  6. Side Effects - 异步操作结果

优化关键点

  • 使用稳定的数据结构
  • 合理使用remember缓存
  • 状态局部化
  • 避免在Composable中创建对象
  • 提供稳定的key给列表项