Android Compose 重组规则

重组基本概念

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

触发重组的情况

1. State状态变化

1.1 MutableState变化

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

1.2 StateFlow/LiveData变化

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

1.3 derivedStateOf变化

kotlin 复制代码
@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 直接参数变化

kotlin 复制代码
@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参数变化

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

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

kotlin 复制代码
@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 不稳定的数据类

kotlin 复制代码
// ❌ 不稳定的类
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 函数类型参数

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

5. CompositionLocal变化

kotlin 复制代码
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

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

6.2 DisposableEffect

kotlin 复制代码
@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. 使用稳定的参数

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

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

2. 合理使用remember

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

3. 状态提升和局部化

kotlin 复制代码
// ❌ 状态在顶层,导致整个组件树重组
@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()优化列表

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

5. 避免在Composable中创建对象

kotlin 复制代码
@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给列表项
相关推荐
我命由我123456 小时前
Android 对话框 - 对话框全屏显示(设置 Window 属性、使用自定义样式、继承 DialogFragment 实现、继承 Dialog 实现)
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
Jeled9 小时前
Android 本地存储方案深度解析:SharedPreferences、DataStore、MMKV 全面对比
android·前端·缓存·kotlin·android studio·android jetpack
我命由我1234520 小时前
Android 开发问题:getLeft、getRight、getTop、getBottom 方法返回的值都为 0
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton6 天前
Kotlin互斥锁(Mutex):协程的线程安全守护神
android·kotlin·android jetpack
是六一啊i7 天前
Compose 在Row、Column上使用focusRestorer修饰符失效原因
android jetpack
用户060905255228 天前
Compose 主题 MaterialTheme
android jetpack
用户060905255228 天前
Compose 简介和基础使用
android jetpack
用户060905255228 天前
Compose 重组优化
android jetpack
行墨8 天前
Jetpack Compose 深入浅出(一)——预览 @Preview
android jetpack