Compose - 底层原理(七) - 优化及内存泄漏问题

520 阅读11分钟

Compose的基本优化问题总结

1. 重组(Recomposition)问题

graph TD
    A[重组触发] --> B[状态变化]
    B --> C[父组件重组]
    B --> D[组件内部状态变化]
    C --> E[不必要的重组]
    D --> E

问题原因:

@Composable
fun ProblemExample() {
    // 问题1: 每次重组都创建新实例
    val list = mutableListOf<String>()  // ❌
    
    // 问题2: 重组时丢失状态
    var count = 0  // ❌
    
    // 问题3: 副作用在重组时重复执行
    LaunchedEffect(Unit) {  // ❌
        doSomething()
    }
}

为什么会出现:

  1. Compose的声明式特性导致函数可能被多次调用
  2. 状态没有正确保存导致重组时丢失
  3. 副作用处理不当导致重复执行

问题表现:

@Composable
fun UserProfile(user: User) {
    // ❌ 错误示范
    var count by remember { mutableStateOf(0) }
    LaunchedEffect(Unit) {
        count++  // 每次重组都会增加
    }
}

解决方案:

@Composable
fun UserProfile(user: User) {
    // ✅ 正确做法
    val count by remember(user.id) { // 指定正确的key
        mutableStateOf(0)
    }
    
    // 使用rememberCoroutineScope来处理副作用
    val scope = rememberCoroutineScope()
    
    Button(onClick = {
        scope.launch {
            // 在协程中处理
        }
    })
}

2. 状态管理问题

graph TD
    A[状态管理] --> B[状态提升]
    A --> C[状态下沉]
    B --> D[ViewModel]
    C --> E[remember]
    D --> F[StateFlow]
    E --> G[mutableStateOf]
graph LR
    A[状态问题] --> B[状态分散]
    A --> C[状态重复]
    A --> D[状态同步]
    B --> E[维护困难]
    C --> E
    D --> E

问题代码及原因:

@Composable
fun StateManagementIssue() {
    // 问题1: 状态分散
    var name by remember { mutableStateOf("") }  // ❌
    var age by remember { mutableStateOf(0) }    // ❌
    
    // 问题2: 状态重复
    val user1 = remember { mutableStateOf(User()) }  // ❌
    val user2 = remember { mutableStateOf(User()) }  // ❌
    
    // 问题3: 状态同步问题
    var isLoading by remember { mutableStateOf(false) }
    var data by remember { mutableStateOf<Data?>(null) }
}

问题代码:

@Composable
fun ProductList() {
    // ❌ 错误示范:状态分散
    var items by remember { mutableStateOf(listOf<Product>()) }
    var loading by remember { mutableStateOf(false) }
    var error by remember { mutableStateOf<String?>(null) }
}

解决方案:

// ✅ 正确做法:状态集中管理
data class ProductListState(
    val items: List<Product> = emptyList(),
    val loading: Boolean = false,
    val error: String? = null
)

class ProductViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(ProductListState())
    val uiState: StateFlow<ProductListState> = _uiState.asStateFlow()
    
    fun loadProducts() {
        viewModelScope.launch {
            _uiState.update { it.copy(loading = true) }
            try {
                val products = repository.getProducts()
                _uiState.update { 
                    it.copy(items = products, loading = false) 
                }
            } catch (e: Exception) {
                _uiState.update { 
                    it.copy(error = e.message, loading = false) 
                }
            }
        }
    }
}

3. 性能优化问题

graph TD
    A[性能问题] --> B[过度重组]
    A --> C[内存泄漏]
    A --> D[计算冗余]
    B --> E[性能下降]
    C --> E
    D --> E

性能问题示例:

@Composable
fun PerformanceIssue() {
    // 问题1: 重复计算
    val result = calculateExpensiveValue()  // ❌
    
    // 问题2: 大列表未优化
    Column {
        items.forEach { item ->  // ❌
            ItemRow(item)
        }
    }
    
    // 问题3: 未使用key导致不必要的重组
    MyComposable()  // ❌ 没有key
}

问题代码:

@Composable
fun LargeList(items: List<Item>) {
    // ❌ 错误示范:未优化的列表
    Column {
        items.forEach { item ->
            ItemRow(item)
        }
    }
}

解决方案:

@Composable
fun LargeList(items: List<Item>) {
    // ✅ 正确做法:使用LazyColumn
    LazyColumn {
        items(
            items = items,
            key = { it.id } // 使用稳定的key
        ) { item ->
            key(item.id) { // 局部key优化
                ItemRow(item)
            }
        }
    }
}

4. 副作用处理

graph LR
    A[副作用处理] --> B[LaunchedEffect]
    A --> C[DisposableEffect]
    A --> D[SideEffect]
    B --> E[异步操作]
    C --> F[清理资源]
    D --> G[同步操作]

问题代码:

@Composable
fun ChatScreen() {
    // ❌ 错误示范:直接在Composable中处理副作用
    val socket = WebSocket()
    socket.connect()
}

解决方案:

@Composable
fun ChatScreen() {
    // ✅ 正确做法:使用适当的副作用处理器
    DisposableEffect(Unit) {
        val socket = WebSocket()
        socket.connect()
        
        onDispose {
            socket.disconnect()
        }
    }
}

5. 组合优化

问题代码:

@Composable
fun ExpensiveUI(data: Data) {
    // ❌ 错误示范:不必要的重组
    Box {
        ExpensiveComponent(data)
        SimpleOverlay()
    }
}

解决方案:

@Composable
fun ExpensiveUI(data: Data) {
    // ✅ 正确做法:使用remember和key优化
    val expensiveData by remember(data.id) {
        derivedStateOf { processData(data) }
    }
    
    Box {
        key(data.id) {
            ExpensiveComponent(expensiveData)
        }
        SimpleOverlay()
    }
}

6. 生命周期管理

@Composable
fun LifecycleAwareScreen() {
    // ✅ 生命周期感知
    val lifecycle = LocalLifecycleOwner.current.lifecycle
    
    DisposableEffect(lifecycle) {
        val observer = LifecycleEventObserver { _, event ->
            when (event) {
                Lifecycle.Event.ON_RESUME -> {
                    // 处理恢复事件
                }
                Lifecycle.Event.ON_PAUSE -> {
                    // 处理暂停事件
                }
            }
        }
        
        lifecycle.addObserver(observer)
        onDispose {
            lifecycle.removeObserver(observer)
        }
    }
}

7. 主题和样式处理

// ✅ 创建自定义主题
@Composable
fun AppTheme(
    darkTheme: Boolean = isSystemInDarkTheme(),
    content: @Composable () -> Unit
) {
    val colors = if (darkTheme) {
        DarkColorPalette
    } else {
        LightColorPalette
    }
    
    CompositionLocalProvider(
        LocalColors provides colors,
        LocalTypography provides Typography,
        LocalShapes provides Shapes
    ) {
        content()
    }
}

8. 测试策略

@Test
fun testUserProfile() {
    // ✅ 组件测试
    composeTestRule.setContent {
        UserProfile(user = testUser)
    }
    
    composeTestRule.onNodeWithText("User Name")
        .assertIsDisplayed()
        .performClick()
    
    composeTestRule.onNodeWithTag("profile_details")
        .assertIsDisplayed()
}

9. 最佳实践总结

  1. 状态管理
// 使用密封类管理UI状态
sealed class UiState<out T> {
    object Loading : UiState<Nothing>()
    data class Success<T>(val data: T) : UiState<T>()
    data class Error(val message: String) : UiState<Nothing>()
}
  1. 性能优化
// 使用remember和key优化重组
val memoizedValue = remember(key1, key2) { 
    expensiveOperation() 
}
  1. 架构设计
// 使用MVI架构
data class ViewState(/*...*/)
sealed class ViewEvent
sealed class ViewEffect

class ViewModel : ViewModel() {
    fun processEvent(event: ViewEvent)
    fun observeState(): StateFlow<ViewState>
    fun observeEffect(): Flow<ViewEffect>
}

这些解决方案能帮助你构建更稳定、高效的 Compose 应用。记住,Compose 是声明式UI,要始终考虑状态管理和重组优化。

4. 副作用处理问题

graph LR
    A[副作用问题] --> B[生命周期]
    A --> C[资源泄漏]
    A --> D[线程安全]
    B --> E[应用不稳定]
    C --> E
    D --> E

副作用问题示例:

@Composable
fun SideEffectIssue() {
    // 问题1: 直接调用挂起函数
    suspend fun loadData() { }  // ❌
    
    // 问题2: 资源未正确释放
    val context = LocalContext.current
    val mediaPlayer = MediaPlayer()  // ❌
    
    // 问题3: 不安全的协程启动
    launch {  // ❌
        // 一些操作
    }
}

5. 组合优化问题

graph TD
    A[组合问题] --> B[智能重组]
    A --> C[组件粒度]
    A --> D[状态提升]
    B --> E[应用性能]
    C --> E
    D --> E

组合问题示例:

@Composable
fun CompositionIssue() {
    // 问题1: 组件粒度过大
    Box {  // ❌
        Header()
        Content()
        Footer()
    }
    
    // 问题2: 状态提升不当
    var text by remember { mutableStateOf("") }  // ❌ 应该提升到ViewModel
    
    // 问题3: 未优化的重组
    ExpensiveComponent(data)  // ❌ 未使用remember
}

6. 生命周期管理问题

@Composable
fun LifecycleIssue() {
    // 问题1: 生命周期监听不当
    val activity = LocalContext.current as Activity  // ❌
    
    // 问题2: 资源清理不及时
    DisposableEffect(Unit) {
        val listener = SomeListener()
        // ❌ 未在onDispose中清理
        onDispose { }
    }
    
    // 问题3: 生命周期范围错误
    LaunchedEffect(Unit) {  // ❌ 范围过大
        while(true) {
            delay(1000)
            // 做一些事情
        }
    }
}

7. 解决方案示例

正确的状态管理:

// ✅ 使用密封类管理状态
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: Data) : UiState()
    data class Error(val message: String) : UiState()
}

class ViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState = _uiState.asStateFlow()
}

正确的副作用处理:

@Composable
fun ProperSideEffect(viewModel: ViewModel) {
    // ✅ 正确使用LaunchedEffect
    LaunchedEffect(viewModel) {
        viewModel.events.collect { event ->
            when (event) {
                is Event.ShowSnackbar -> {
                    // 处理事件
                }
            }
        }
    }
}

正确的性能优化:

@Composable
fun OptimizedList(items: List<Item>) {
    // ✅ 使用LazyColumn优化列表性能
    LazyColumn {
        items(
            items = items,
            key = { it.id }
        ) { item ->
            ItemRow(item)
        }
    }
}

8. 最佳实践总结

  1. 状态管理
  • 集中管理状态
  • 使用不可变状态
  • 正确的状态提升
  1. 性能优化
  • 使用remember缓存计算结果
  • 合理使用key避免不必要重组
  • 优化列表性能
  1. 生命周期管理
  • 正确处理副作用
  • 及时清理资源
  • 遵循Compose生命周期
  1. 架构设计
  • 遵循单向数据流
  • 使用ViewModel分离业务逻辑
  • 保持组件纯函数特性

理解这些问题的原因,才能更好地避免和解决它们,写出高质量的Compose应用。

Compose中内存泄漏问题

让我详细解释 Compose 中常见的内存泄漏情况及其解决方案:

1. 常见内存泄漏场景

1.1 协程泄漏

// ❌ 错误示例:直接在 Composable 中启动协程
@Composable
fun LeakyScreen() {
    val scope = CoroutineScope(Dispatchers.Main)  // 错误:作用域不会随组件销毁
    
    Button(onClick = {
        scope.launch {  // 这个协程不会被取消
            while(true) {
                delay(1000)
                doSomething()
            }
        }
    }) {
        Text("Start")
    }
}

// ✅ 正确示例:使用 rememberCoroutineScope
@Composable
fun NonLeakyScreen() {
    val scope = rememberCoroutineScope()  // 正确:作用域会随组件销毁
    
    Button(onClick = {
        scope.launch {
            doSomething()
        }
    }) {
        Text("Start")
    }
}

让我详细解释 CoroutineScoperememberCoroutineScope 的区别:

1. CoroutineScope 的问题
@Composable
fun LeakyExample() {
    // ❌ 问题:这个作用域与 Compose 生命周期无关
    val scope = CoroutineScope(Dispatchers.Main)
    
    // 即使组件被销毁,这个协程还会继续运行
    scope.launch {
        while(true) {
            delay(1000)
            println("Still running!")
        }
    }
}

就像是雇了一个临时工:

  • 即使店铺关门了
  • 他还在继续工作
  • 没人管理他的工作时间
2. rememberCoroutineScope 的实现
// 简化的 rememberCoroutineScope 实现原理
@Composable
fun rememberCoroutineScope(): CoroutineScope {
    // 1. 获取 Compose 的生命周期
    val composition = currentComposition
    
    // 2. 创建与组件生命周期绑定的作用域
    return remember {
        // 创建作用域
        val scope = CoroutineScope(Job())
        
        // 当组件被销毁时,取消作用域
        composition.addOnDisposable {
            scope.cancel()
        }
        
        scope
    }
}

就像是雇了一个正式员工:

  • 跟随店铺的营业时间
  • 店铺关门,他就下班
  • 有规范的工作时间管理

让我详细解释 rememberCoroutineScope 是如何绑定 Compose 生命周期的:

1. rememberCoroutineScope 的源码实现
// 简化版源码实现
@Composable
fun rememberCoroutineScope(
    context: CoroutineContext = EmptyCoroutineContext
): CoroutineScope {
    // 1. 获取当前 Composition
    val composition = currentCompositionLocalContext
    
    // 2. 创建作用域
    return remember {
        // 创建一个新的 CoroutineScope
        val scope = CoroutineScope(
            // 合并上下文
            context + Job() + composition.coroutineContext
        )
        
        // 3. 注册销毁回调
        composition.addDisposableEffect {
            onDispose {
                // 当组件被销毁时取消作用域
                scope.cancel()
            }
        }
        
        scope
    }
}
2. 生命周期绑定过程
class ComposeLifecycleOwner {
    // 1. Compose 组件的生命周期状态
    private val _lifecycleState = MutableStateFlow(Lifecycle.State.CREATED)
    
    // 2. 组件进入组合
    fun onEnterComposition() {
        _lifecycleState.value = Lifecycle.State.STARTED
    }
    
    // 3. 组件离开组合
    fun onLeaveComposition() {
        _lifecycleState.value = Lifecycle.State.DESTROYED
    }
}

// 使用示例
@Composable
fun LifecycleDemoScreen() {
    // 创建与生命周期绑定的作用域
    val scope = rememberCoroutineScope()
    
    DisposableEffect(Unit) {
        // 进入组合
        onEnterComposition()
        
        onDispose {
            // 离开组合时
            onLeaveComposition()
            // scope 会自动取消
        }
    }
}
3. 实际工作流程
sequenceDiagram
    participant C as Composable
    participant R as rememberCoroutineScope
    participant S as CoroutineScope
    participant L as Lifecycle
    
    C->>R: 创建作用域
    R->>S: 创建新的 CoroutineScope
    R->>L: 注册生命周期回调
    L-->>S: 组件销毁时取消作用域
4. 具体使用示例
@Composable
fun OrderScreen() {
    // 1. 创建作用域
    val scope = rememberCoroutineScope()
    
    // 2. 状态
    var orders by remember { mutableStateOf(emptyList<Order>()) }
    
    // 3. 使用作用域处理异步操作
    Button(
        onClick = {
            scope.launch {
                try {
                    // 异步操作
                    val newOrders = fetchOrders()
                    orders = newOrders
                } catch (e: Exception) {
                    // 错误处理
                }
            }
        }
    ) {
        Text("刷新订单")
    }
    
    // 4. 当组件被销毁时
    // scope 会自动取消所有协程
}
5. 生命周期管理示例
class ComposableScope {
    private var job: Job? = null
    
    // 启动新任务
    fun launchTask(block: suspend () -> Unit) {
        // 取消旧任务
        job?.cancel()
        
        // 启动新任务
        job = scope.launch {
            block()
        }
    }
    
    // 清理
    fun cleanup() {
        job?.cancel()
        job = null
    }
}

@Composable
fun TaskScreen() {
    val scope = rememberCoroutineScope()
    
    DisposableEffect(Unit) {
        val composableScope = ComposableScope()
        
        onDispose {
            // 自动清理
            composableScope.cleanup()
        }
    }
}
6. 优化建议
@Composable
fun OptimizedScreen() {
    // 1. 正确使用作用域
    val scope = rememberCoroutineScope()
    
    // 2. 错误处理
    LaunchedEffect(Unit) {
        try {
            // 异步操作
        } catch (e: CancellationException) {
            // 处理取消
        } catch (e: Exception) {
            // 处理其他错误
        }
    }
    
    // 3. 状态管理
    var state by remember { mutableStateOf(InitialState) }
    
    // 4. 生命周期感知
    DisposableEffect(Unit) {
        onDispose {
            // 清理工作
        }
    }
}

关键点:

  1. 自动生命周期管理
  2. 防止内存泄漏
  3. 协程作用域的复用
  4. 异常处理
  5. 状态管理

这样的设计确保了:

  • 协程的安全取消
  • 资源的及时释放
  • 代码的可维护性
  • 应用的稳定性
3. 实际使用对比
class RestaurantScreen {
    // ❌ 错误方式:
    val scope = CoroutineScope(Dispatchers.Main)
    
    fun cleanup() {
        // 可能忘记调用取消
        scope.cancel()
    }
}

@Composable
fun RestaurantScreen() {
    // ✅ 正确方式:
    val scope = rememberCoroutineScope()
    
    // 不需要手动取消,会自动清理
    Button(onClick = {
        scope.launch {
            processOrder()
        }
    })
}
4. 生命周期对比
graph TD
    A[CoroutineScope] -->|无生命周期关联| B[一直运行直到手动取消]
    
    C[rememberCoroutineScope] -->|绑定Compose生命周期| D[组件销毁时自动取消]
5. 具体区别:
  1. 生命周期管理
  • CoroutineScope:需要手动管理
  • rememberCoroutineScope:自动跟随组件生命周期
  1. 内存管理
  • CoroutineScope:可能造成内存泄漏
  • rememberCoroutineScope:自动清理,防止泄漏
  1. 使用场景
  • CoroutineScope:适用于全局或长期运行的协程
  • rememberCoroutineScope:适用于 Compose 组件内的协程
6. 最佳实践
@Composable
fun BestPracticeExample() {
    // 1. 使用 rememberCoroutineScope
    val scope = rememberCoroutineScope()
    
    // 2. 在组件生命周期内使用
    LaunchedEffect(Unit) {
        // 自动管理的协程
    }
    
    // 3. 处理用户事件
    Button(onClick = {
        scope.launch {
            // 安全的协程启动
            handleClick()
        }
    })
}

总结:

  1. rememberCoroutineScope 是专门为 Compose 设计的
  2. 自动管理生命周期,防止泄漏
  3. 使用更安全,代码更简洁
  4. 推荐在 Compose 中使用

1.2 Context 引用泄漏

// ❌ 错误示例:持有 Context 引用
class LeakyViewModel(
    private val context: Context  // 错误:直接持有 Context
) : ViewModel()

// ✅ 正确示例:使用 ApplicationContext
class NonLeakyViewModel(
    application: Application
) : AndroidViewModel(application)  // 正确:使用 Application Context

2. Effect 相关泄漏

2.1 LaunchedEffect 泄漏

// ❌ 错误示例:没有正确的 key
@Composable
fun LeakyEffect() {
    LaunchedEffect(Unit) {  // 每次重组都会创建新的协程
        while(true) {
            delay(1000)
            doSomething()
        }
    }
}

// ✅ 正确示例:使用正确的 key
@Composable
fun NonLeakyEffect(key: String) {
    LaunchedEffect(key) {  // 只在 key 变化时重新启动
        doSomething()
    }
}

2.2 DisposableEffect 清理

// ❌ 错误示例:没有清理资源
@Composable
fun LeakyDisposable() {
    val listener = remember { EventListener() }
    eventEmitter.addListener(listener)  // 没有移除监听器
}

// ✅ 正确示例:正确清理资源
@Composable
fun NonLeakyDisposable() {
    DisposableEffect(Unit) {
        val listener = EventListener()
        eventEmitter.addListener(listener)
        
        onDispose {
            eventEmitter.removeListener(listener)  // 清理监听器
        }
    }
}

3. 状态管理泄漏

3.1 Flow 收集泄漏

// ❌ 错误示例:手动收集 Flow
@Composable
fun LeakyFlow(viewModel: MyViewModel) {
    val scope = rememberCoroutineScope()
    
    scope.launch {  // 错误:不会随组件销毁而取消
        viewModel.dataFlow.collect { }
    }
}

// ✅ 正确示例:使用 collectAsState
@Composable
fun NonLeakyFlow(viewModel: MyViewModel) {
    val data by viewModel.dataFlow.collectAsState()  // 正确:自动管理生命周期
}

让我用餐厅点餐的例子来解释这两种方式的区别:

1. 错误的方式(LeakyFlow)
// ❌ 错误示例
@Composable
fun LeakyFlow(viewModel: MyViewModel) {
    val scope = rememberCoroutineScope()
    
    scope.launch {  
        viewModel.dataFlow.collect { }
    }
}

这就像:

  • 你安排一个服务员(scope.launch)
  • 让他一直盯着厨房的出菜口(collect)
  • 即使餐厅已经打烊了(组件销毁)
  • 这个服务员还在那里傻傻地等(内存泄漏)
2. 正确的方式(NonLeakyFlow)
// ✅ 正确示例
@Composable
fun NonLeakyFlow(viewModel: MyViewModel) {
    val data by viewModel.dataFlow.collectAsState()
}

这就像:

  • 餐厅有个智能显示屏(collectAsState)
  • 自动显示厨房的出菜状态
  • 餐厅关门时,显示屏自动关闭
  • 不会有人傻等(不会泄漏)
为什么会这样?
  1. 错误方式的问题
  • scope.launch 创建的协程虽然会跟随 Compose 生命周期取消
  • 但 collect 是永久性的收集,不会自动停止
  • 导致即使组件销毁,收集还在继续
  1. 正确方式的优势
  • collectAsState 是专门为 Compose 设计的
  • 会自动跟随组件的生命周期
  • 组件销毁时,自动停止收集
  • 不会造成内存泄漏

简单说:

  • 错误方式像是忘记下班的服务员
  • 正确方式像是自动化的显示屏
  • 要选择会自动关机的显示屏,而不是永不休息的服务员

4. 自定义 View 泄漏

// ❌ 错误示例:
class LeakyCustomView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null
) : View(context, attrs) {
    private val someCallback = object : Callback {
        override fun onEvent() {
            // 可能导致泄漏
        }
    }
    
    init {
        EventBus.register(someCallback)  // 没有注销回调
    }
}

// ✅ 正确示例:
@Composable
fun NonLeakyCustomView() {
    DisposableEffect(Unit) {
        val callback = object : Callback {
            override fun onEvent() {}
        }
        
        EventBus.register(callback)
        
        onDispose {
            EventBus.unregister(callback)
        }
    }
}

5. 防止内存泄漏的最佳实践

class SafeViewModel : ViewModel() {
    // 1. 使用 viewModelScope
    private val job = viewModelScope.launch {
        // 自动取消的协程
    }
    
    // 2. 正确清理资源
    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}

@Composable
fun SafeScreen() {
    // 1. 使用 remember 缓存对象
    val callback = remember {
        Callback()
    }
    
    // 2. 使用 DisposableEffect 清理资源
    DisposableEffect(Unit) {
        onDispose {
            callback.cleanup()
        }
    }
    
    // 3. 使用 collectAsState 收集 Flow
    val state by viewModel.stateFlow.collectAsState()
}

6. 检测内存泄漏

// 在 Debug 构建中使用 LeakCanary
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.x'

关键防范点:

  1. 使用正确的协程作用域
  2. 及时清理资源
  3. 避免持有不必要的引用
  4. 正确使用 Effect
  5. 使用生命周期感知组件

这样可以:

  • 避免内存泄漏
  • 提高应用性能
  • 减少崩溃风险
  • 优化用户体验