Skip to content
Maozy's Blog
Go back

[Jetpack Compose] 纯Compose VS 传统Android

Table of contents

Open Table of contents

引言:不止是 UI 框架的更迭,更是思维方式的重塑

在 Android 发展的十几年里,基于 XML 和命令式(Imperative)的传统 View 系统支撑了无数庞大的应用。

然而,随着业务复杂度的呈指数级上升,UI 状态的同步变得越来越像“一盘散沙”。

Jetpack Compose 的横空出世,不仅消灭了 XML,更重要的是引入了声明式(Declarative)数据驱动的现代前端理念。

本文将从 UI 构建、状态管理、底层渲染机制以及工程实践四个维度,深度且客观地剖析两者的差异。


一、UI 构建范式 —— 命令式 vs 声明式

1.1 传统 View 系统:命令式(Imperative)

传统模式下,UI 是一个有状态的实体树。我们需要手动构建 UI(通过 XML),然后在 Java/Kotlin 代码中获取节点引用(findViewById),最后通过调用方法(命令)去改变它的状态。

传统代码示例:

// 1. 查找 View
TextView likeText = findViewById(R.id.tv_like);
Button likeButton = findViewById(R.id.btn_like);

// 2. 命令式更新 UI
likeButton.setOnClickListener(v -> {
    if (isLiked) {
        likeText.setText("取消赞");
        likeText.setTextColor(Color.GRAY);
    } else {
        likeText.setText("点赞");
        likeText.setTextColor(Color.RED);
    }
    isLiked = !isLiked; // 状态分散在各个组件的逻辑中
});

痛点: 关注点分离失效。XML 负责展示,Activity 负责逻辑,但两者强耦合。随着逻辑增加,容易出现忘记更新某个 View 状态而导致的Bug。

1.2 Jetpack Compose:声明式(Declarative)

Compose 将 UI 视为一个纯函数。只需要描述“在特定数据下,UI 应该长什么样”,而不需要关心“如何去更新它”。

公式即:UI = f(State)

Compose 代码示例:

@Composable
fun LikeButton(isLiked: Boolean, onLikeClick: () -> Unit) {
    // 纯粹地描述映射关系,没有 findViewById,没有 setText
    Button(onClick = onLikeClick) {
        Text(
            text = if (isLiked) "取消赞" else "点赞",
            color = if (isLiked) Color.Gray else Color.Red
        )
    }
}

优势: 极高的内聚性。UI 代码就是逻辑本身,所见即所得。


二、状态管理 —— 意大利面条 vs 单向数据流 (UDF)

2.1 传统 View 系统:状态散落与数据不一致灾难

在复杂的传统页面中,一个数据(如点赞数)可能需要同时更新列表项、底部状态栏和顶部总数。

我们需要让这些 View 互相监听或通过 EventBus 疯狂发消息。状态就像意大利面条一样死锁,极易产生时序 Bug 和数据不一致。

2.2 Jetpack Compose:单一可信数据源 (SSOT) 与 单向数据流 (UDF)

Compose 提倡将状态集中管理(状态提升 State Hoisting),UI 组件变为无状态(Stateless)。

当顶层状态(如 ViewModel 中的 StateFlow)发生改变时,Compose 的快照状态系统(Snapshot State System)会自动追踪读取了该状态的 Composable 函数,并触发精准的重组(Recomposition)。开发者彻底告别了手动的 DOM 操作。


三、底层渲染与性能 —— 多次测量 vs 单次测量

3.1 传统 View 系统:嵌套地狱与指数级测量开销

在 XML 中,如果使用 LinearLayout 嵌套过深,或者是 RelativeLayout,在 onMeasure 阶段,父 View 通常需要多次测量子 View 才能确定尺寸。层级越深,Measure 耗时呈指数级增长,导致掉帧。

3.2 Jetpack Compose:强制单次测量 (Single-pass Measurement)

Compose 在底层架构上做出了革命性的妥协与优化:它严格限制每个 LayoutNode 只能被测量一次。

这意味着,在 Compose 中你可以肆无忌惮地嵌套 RowColumnBox,而完全不用担心传统 XML 中的性能灾难。

Compose 内部使用了一种称为 SubcomposeLayout 的机制和极度扁平化的渲染树来保证这一点。

附加性能点:智能跳过 (Smart Recomposition) Compose 编译器会推断参数的稳定性 (Stability)。在重组时,如果传入子组件的参数(如 StringInt 或标记为 @Stable 的对象)没有发生结构性改变(内存引用不变),Compose 会直接跳过该组件的重新执行,极大节省了性能。


四、工程实践与互操作性

客观来说,没有一项技术是完美的。在实际落地中,我们需要权衡:

传统 View 的优势:

  1. 成熟度极高: 拥有十几年积累的庞大第三方库生态(如图表、复杂音视频播放器)。
  2. 所见即所得的预览: XML 的预览速度极快,拖拽式开发对极度复杂的静态布局仍有一定优势。

Compose 的痛点与现状:

  1. 心智模型转换成本高: 开发者必须跨越“副作用处理(Side Effects/LaunchedEffect)”和“重组作用域”等陡峭的学习曲线。
  2. Preview 预览工具仍显笨重: 相比 XML,Compose 的 @Preview 编译和刷新速度仍有待提升。

平滑过渡方案: Google 设计了极强的互操作性。


五、总结

从传统 View 到 Jetpack Compose,不是简单的 API 替换,而是从“手动维护状态树”到“数据映射 UI”的升维打击。

虽然目前 Compose 的工具链还在持续打磨,但在应对日益复杂的跨端业务(如配合 Kotlin Multiplatform)和高频状态更新场景时,声明式 UI 无疑是不可逆转的未来。


Share this post on:

Previous Post
[Jetpack Compose] 副作用(Side Effects)和 LaunchedEffect
Next Post
[Jetpack Compose] state案例分析