不止是“装修”:从 VSync 到 Compose,重构 Android View 的绘制全景

179 阅读4分钟

一句话总结:

View 的“测量-布局-绘制”三部曲,并非独立运作,而是被 ViewRootImpl 在 VSync 信号的驱动下,于 16.6ms 内统一调度的结果。而现代的 Jetpack Compose,则通过“智能重组”,用一种全新的、更高维的“施工工艺”从根本上优化了这一流程。


第一篇章:“微观”视角——一个 View 的“装修三部曲”

这是理解一切的基础,是你文章中已经出色阐述的部分。

  1. onMeasure (量尺寸):View 提供 MeasureSpec(尺寸约束),子 View 计算并上报自己期望的尺寸 (setMeasuredDimension)。
  2. onLayout (摆家具):View 根据子 View 的尺寸,为它们分配具体的位置(左、上、右、下坐标)。
  3. onDraw (刷墙装饰): ViewCanvas 上进行绘制,将自己的内容呈现出来。

核心顽疾:传统布局的“双重税收”

为什么我们要极力避免深层嵌套的 LinearLayout (带权重) 或 RelativeLayout?因为它们存在“二次测量”问题。父布局为了解决复杂的依赖关系,可能需要对子 View 进行多次 measure,这种成本在嵌套时会急剧恶化,是传统布局卡顿的元凶。ConstraintLayout 的一大优势,就是它通过更复杂的约束算法,通常只需一次 measure 就能完成布局,从而实现了“扁平化”的性能飞跃。


第二篇章:“宏观”视角——谁在驱动这场“装修”?

measure-layout-draw 并非由 View 自己触发,而是由“总承包商” ViewRootImpl 在一个严格的“工期”内统一调度。

这场装修的完整启动流程:

  1. 触发 (requestLayout/invalidate): 当你修改 View 尺寸或内容时,一个重绘请求会向上传递到 ViewRootImpl
  2. 预约工期 (Choreographer): ViewRootImpl 收到请求,不会立即施工,而是向系统的“帧调度员” Choreographer 预约在下一个“工作日”(VSync 信号)进场。
  3. 开工信号 (VSync): 当 16.6ms 一次的 VSync 信号到达时,Choreographer 通知 ViewRootImpl:“可以开工了!”
  4. 统一施工 (performTraversals): ViewRootImpl 调用自己的 performTraversals 方法,自上而下地、依次地对自己管理的整个 View 树,执行 performMeasureperformLayoutperformDraw 三大指令。

结论: View 的绘制,是一个被紧密地、同步地绑定在 VSync “心跳”上的、自上而下的系统级行为。所有优化,都必须以**“在 16.6ms 内完成”**为最终目标。

graph TD
    A[1. requestLayout()] --> B[2. ViewRootImpl];
    B --> C[3. 向 Choreographer 预约];
    D[4. VSync 信号到达] --> E[5. Choreographer 回调];
    E --> F[6. ViewRootImpl.performTraversals()];
    subgraph "三部曲"
        F --> G[performMeasure];
        G --> H[performLayout];
        H --> I[performDraw];
    end

第三篇章:“未来工艺”——Jetpack Compose 的“按需智能装修”

Jetpack Compose 彻底改变了这套“大一统”的施工模式。

核心革命:从“刷新整个房间”到“只补一块墙皮”

  • View 体系: 调用 invalidate,可能会导致整个 View 被重绘;调用 requestLayout,可能会导致整个 View 树重新测量布局。这是一个**“牵一发而动全身”**的模式。
  • Compose 体系: UI 由“状态 (State)”驱动。当一个 State 变化时,Compose 的运行时能够精准地只找到依赖这个 State 的、最小范围的 Composable 函数,并仅仅对它进行“重组”(Recomposition)

示例:

@Composable
fun MyScreen(name: State<String>, counter: State<Int>) {
    Column {
        Text(text = name.value) // 只依赖 name
        Text(text = "Count: ${counter.value}") // 只依赖 counter
    }
}

counter 的值变化时,只有第二个 Text 会被重组和重绘,第一个 Text 的代码完全不会被再次执行。Compose 通过这种极致的“按需更新”,从架构上避免了大量不必要的绘制工作。


四、总结:你的 UI 优化新思路

时代核心思想关键技术/概念
View 体系优化“施工”过程,让每一次 Measure/Layout/Draw 都更快扁平化布局 (ConstraintLayout), 避免二次测量, ViewStub, 优化 onDraw
Compose 体系避免“施工”过程,只更新必要的部分状态驱动, 智能重组, State, Modifier

最终建议:

对于存量 View 项目,你需要成为一名精通“装修三部曲”并善于发现“双重税收”问题的“老工匠”。而对于新项目和新功能,请拥抱 Jetpack Compose 这套“现代化智能施工工艺”,它能让你从一开始就构建出高性能、易维护的用户界面。