测量、布局与绘制:View 的三阶段
2026/8/9大约 3 分钟渲染管线Android 14Viewmeasuredraw
测量、布局与绘制:View 的三阶段
上一章建立了主链。这一章进入主线程三段,回答:尺寸怎么算、位置怎么定、绘制指令 怎么记录,以及 invalidate 与 requestLayout 的区别?
measure、layout、draw 解决不同问题
| 常见误解 | 正确理解 |
|---|---|
| onDraw 就是画到屏幕 | onDraw 只是记录绘制指令,真正的画在 RenderThread |
| invalidate 会重新测量 | invalidate 只重绘;重新测量要 requestLayout |
| 每个 View 独立遍历 | 三阶段沿视图树递归,父级先于子级 |
核心结论
- measure 沿视图树自顶向下,用 MeasureSpec 约束宽高。
- layout 确定每个 View 的坐标与尺寸。
- draw 记录 DisplayList,onDraw 只是绘制指令的入口。
invalidate()触发重绘,requestLayout()触发重新测量+布局+绘制。- 三阶段都在主线程,复杂度直接决定掉帧风险。
三阶段主链
逐步解释:
- 触发:布局请求、重绘请求或尺寸变化进入遍历。
- measure:计算宽高。
- layout:确定位置。
- draw:记录绘制指令。
- 执行:RenderThread 拿走指令真正绘制。
invalidate 与 requestLayout 的区别
| 方法 | 作用 | 影响 |
|---|---|---|
invalidate() | 标记重绘 | 只走 draw |
requestLayout() | 标记重排 | 走 measure + layout + draw |
| 尺寸变化 | 自动触发重排 | 父链可能连锁 |
频繁 requestLayout 比 invalidate 贵得多;优化列表滚动时常从“减少不必要的 requestLayout”入手。
源码证据
阅读目标:确认三阶段入口与触发关系。
正文指针:ViewRootImpl.java performTraversals();View.java measure()/layout()/ draw();View.java requestLayout()/invalidate()。
// ViewRootImpl.java(伪代码,行号以 r75 为准)
private void performTraversals() {
// measure → layout → draw
}这段代码证明: 三阶段由 ViewRootImpl 统一编排;invalidate 与 requestLayout 通过 不同标志控制是否重排。这就是两者成本差异的代码来源。
验证与排障
环境:开发设备或模拟器;以下命令不需要 root。
adb shell setprop debug.hwui.profile true
adb shell dumpsys gfxinfo <包名>
adb logcat -v threadtime | grep -E "Choreographer|ViewRootImpl"开启 profile 后 gfxinfo 会给出绘制耗时分布;配合 Perfetto 的 doFrame 轨道确认 measure/layout/draw 各自耗时。列表卡顿先看是不是频繁 requestLayout。
常见误区
- “onDraw 就是上屏”:只是记录指令。
- “invalidate 会重新测量”:只重绘。
- “三阶段各跑各的”:沿树递归,父先子后。
- “主线程绘制开销小”:记录指令也有成本,复杂视图更明显。
延伸问题
- MeasureSpec 的 EXACTLY/AT_MOST/UNSPECIFIED 分别表示什么?
- 为什么层级过深会拖慢 measure/layout?
- invalidate 与硬件加速下的局部重绘如何工作?
- 如何用帧轨道拆分 measure/layout/draw 耗时?
源码入口
| 文件 | 关键位置 | 作用 |
|---|---|---|
ViewRootImpl.java | performTraversals() | 三阶段编排 |
View.java | measure() / layout() / draw() | 三阶段实现 |
View.java | requestLayout() / invalidate() | 触发入口 |
Choreographer.java | doFrame() | 帧调度 |
公共路径:frameworks/base/core/java/android/view/。行号以 r75 检索为准。
绘制命令如何脱离主线程执行,见 DisplayList 与 RenderThread。