一、Compose的编译时魔法

Jetpack Compose的核心创新在于编译时代码生成。传统View系统在运行时通过反射和XML解析构建UI,而Compose在编译时将@Composable函数直接转化为UI树结构的描述代码,消除了反射的开销。

1.1 @Composable函数的本质

// Compose编译器插件(composecompiler)处理的转换:
//
// 原始代码:
@Composable
fun Greeting(name: String) {
    Text("Hello $name")
}
//
// 编译器生成的等效代码(伪代码):
fun Greeting(name: String, composer: Composer) {
    composer.startReplaceableGroup(42)  // 组标记
    if (composer.isTraceEventEnabled) {
        composer.traceEventStart(42, "Greeting", -1, -1)
    }
    val $composer = composer.composeableLambda(42) { c ->
        Text("Hello $name")
    }
    $composer()
    if (composer.isTraceEventEnabled) {
        composer.traceEventEnd()
    }
    composer.endReplaceableGroup()
}

// 关键点:
// - 每个@Composable函数都接收隐式的Composer参数
// - 编译器自动注入composer.begin/composer.end调用
// - composeableLambda创建可组合lambda供重组使用

1.2 语义树(Semantic Tree)的构建

// Compose UI的层级结构(不同于View的ViewGroup树)
//
// Composables函数签名决定了它们如何被加入重组范围:
//
// Skippable函数(可跳过的重组):
@Composable fun SkippableContent(x: Int) {
    // 当x未变化时,Compose跳过此函数的执行
    Text("Value: $x")
}
//
// restartable函数(可重启的重组):
@Composable fun RestartableContent() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Clicked $count times")
    }
    // count变化时,从这里开始重启(而非父级)
}

// 组的概念:
// - replaceableGroup:值变化时整体替换
// - restartableGroup:值变化时可从当前位置重启
// - movableContentGroup:支持在组间移动而不重建

// 编译报告(打开composeCompilerReports):
// composeMetrics.gradle
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>.configureEach {
    kotlinOptions {
        freeCompilerArgs += listOf(
            "-P", "plugin:androidx.compose.compiler.plugins.kotlin:reportsOutput=..."
        )
    }
}

二、重组(Recomposition)的最小化策略

2.1 稳定的类型与stable冠名

// Compose如何判断"需要重组":
// 1. 基础类型(Int/String/Boolean等)→ stable,自动比较
// 2. 已知stable的类 → 用equals比较
// 3. 未知类型 → 假设unstable,每次都重组

// 手动标记stable(用于自定义类型):
@Stable
class UiState(
    val userName: String,
    val isLoading: Boolean
)

// @Stable的契约:
// - equals()必须基于值而非身份
// - 所有字段也必须是stable的
// - 相当于告诉编译器"这个类可以安全比较"

import androidx.compose.runtime.Stable

// 常用stable注解:
@Stable      // 单个类
@Immutable  // 不可变类(比@Stable更强的保证)

// 常见unstable陷阱:
data class Result<T>(val data: T?, val error: Throwable?)
    // ⚠️ T是泛型,Compose无法判断其稳定性
    // 解决:@Stable class Result<T : Any>(...)

// Lambda的稳定性:
val onClick: () -> Unit  // 稳定(函数引用是stable的)
val onClick: (Int) -> Unit  // 稳定
// remember { { value -> doSomething(value) } }
//   ↑ remember包裹使lambda稳定

2.2 DerivedStateOf与Snapshot机制

// DerivedStateOf:防止派生状态过度更新
//
// 问题:多个状态同时变化时,派生计算可能被执行多次
var firstName by mutableStateOf("")
var lastName by mutableStateOf("")
val fullName = firstName + " " + lastName  // 每次输入都重算

// 解决:derivedStateOf只在相关状态变化时重算
val fullName by derivedStateOf {
    firstName + " " + lastName  // 只有firstName/lastName变化才重算
}

// Snapshot系统(Compose的状态管理核心):
//
// Snapshot是一种软件事务内存(STM)的轻量实现
// 多个状态写入被合并为一次UI更新
//
// apply {
    snapshot.enter {
        a.value = 1
        b.value = 2  // 两个写入在同一次快照中
    }
}
// → UI只更新一次(而非两次)

// Side Effect的正确管理方式:
@Composable
fun AsyncContent() {
    val scope = rememberCoroutineScope()

    // LaunchedEffect:在Composition中启动协程
    LaunchedEffect(key1 = userId) {
        // 副作用:网络请求
        val data = fetchUser(userId)
        viewModel.updateState(data)
    }

    // rememberCoroutineScope:在Composable外部启动协程
    Button(onClick = {
        scope.launch {
            doBackgroundWork()
        }
    }) { Text("执行") }
}

三、性能优化的核心策略

3.1 避免不必要的重组范围

// 问题代码:父级状态变化导致整个UI重建
@Composable
fun BadParent() {
    var showDialog by remember { mutableStateOf(false) }
    HeavyList()  // ⚠️ showDialog变化时,HeavyList也会重组
    Button(onClick = { showDialog = true }) { Text("打开") }
}

// 优化:用key或独立函数隔离重组范围
@Composable
fun GoodParent() {
    var showDialog by remember { mutableStateOf(false) }
    HeavyList()  // ✅ 独立函数,重组范围隔离
    DialogButton(showDialog) { showDialog = true }
}

@Composable
private fun DialogButton(
    show: Boolean,
    onShow: () -> Unit
) {
    // 只有show变化时才重组
    Button(onClick = onShow) { Text("打开") }
}

// 使用key()强制重组隔离:
@Composable
fun ListWithKeys(items: List<Item>) {
    LazyColumn {
        items(
            items = items,
            key = { item -> item.id }  // ✅ key使得只有变化的项目重建
        ) { item ->
            ListItem(item)
        }
    }
}

// 最佳实践:越靠近状态消费者的代码粒度越细
// 重组的最小单位是"可组合函数的调用点"

3.2 LazyColumn大数据量优化

// LazyColumn 99%场景的性能问题来源:
// 1. Item不稳定导致重建
// 2. 每次重组重新Layout
// 3. 绘制内容过大

// 优化①:固定Item高度(避免测量抖动)
LazyColumn {
    items(
        list,
        key = { it.id }
    ) { item ->
        // 固定高度估算
        LazyItemScope
    }
}

// 估算高度缓存(减少测量):
@Composable
fun FixedHeightItem(item: Item) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .heightIn(min = 72.dp)  // 最小高度
    ) { /* content */ }
}

// 优化②:内容类型(ContentType)减少重组
LazyColumn {
    items(
        items = mixedList,
        contentType = { item ->
            when (item) { is Header -> "header"; is Row -> "row" }
        }
    ) { item -> /* ... */ }
}

// 优化③:异步图片加载(Coil)
LazyColumn {
    items(items) { item ->
        AsyncImage(
            model = item.imageUrl,
            contentDescription = null,
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(16f / 9f),
            contentScale = ContentScale.Crop
        )
    }
}

四、测量-布局-绘制的性能陷阱

// Compose的绘制管线:
// Composition → Layout → Draw
//
// 每个阶段的成本比例(近似):
// Composition: ~30%(创建UI节点)
// Layout: ~50%(测量+定位,最耗时)
// Draw: ~20%(光栅化+上传GPU)

// 陷阱①:在Layout阶段执行重计算
@Composable
fun BadLayout() {
    val layoutInfo = remember { computeExpensiveLayout() }  // ⚠️ 每次重组重算
}

// 正确:
@Composable
fun GoodLayout() {
    val layoutInfo = remember { computeExpensiveLayout() }  // ✅ remember缓存
}

// 陷阱②:Modifier链过长
Modifier
    .fillMaxWidth()
    .padding(16.dp)
    .background(Color.Gray)
    .clickable { }           // ⚠️ 过多的修饰符
    .pointerInput(Unit) { }  // ⚠️ 每个pointerInput都可能启动新的协程

// 合并相近修饰符:
Modifier
    .fillMaxWidth()
    .background(Color.Gray)
    .combinedClickable(  // ✅ combinedClickable = clickable + longClickable
        onClick = {},
        onLongClick = {}
    )

// 陷阱③:绘制阶段的无用重绘
@Composable
fun ExpensiveCanvas() {
    Canvas(modifier = Modifier.drawBehind {
        // ⚠️ 每次重组都执行全部绘制逻辑
        for (i in 0..1000) {
            drawCircle(...)  // 1000个圆
        }
    }) { }
}

// 优化:用remember缓存绘制结果
@Composable
fun OptimizedCanvas() {
    val cachedDraw = remember {
        drawDrawComponent { canvas ->
            for (i in 0..1000) {
                canvas.drawCircle(...)
            }
        }
    }
    Canvas(modifier = Modifier.graphicsLayer { cachedDraw }) {}
}