简介
Android 应用界面的每一帧绘制都经历了一个精确编排的渲染管线:从 VSYNC 信号触发 Choreographer 回调开始,经 ViewRootImpl 协调 measure、layout、draw 三大阶段,最终通过 SurfaceFlinger 将画面合成显示到屏幕。深入理解这个管线的每一个环节,是进行 UI 性能优化的基础。
本文从 AOSP 源码角度,完整分析从 Window 的创建、ViewRootImpl 的连接,到 VSYNC 驱动的帧回调、三大绘制流程的执行细节,再到硬件加速和 SurfaceFlinger 合成。同时覆盖 dumpsys gfxinfo、Profile GPU Rendering、FrameMetrics 等性能分析工具,以及常见卡顿原因与排查方法。
注意:源码分析基于 Android-30(Android 11)。核心源码位于
frameworks/base/core/java/android/view/和frameworks/native/libs/gui/。
View 是如何被添加到屏幕窗口上的
在深入绘制流程之前,必须先理解 View 是如何与屏幕窗口关联起来的。
顶层布局容器 DecorView 的创建
Activity 启动过程中,最终会调用 ActivityThread.handleResumeActivity,这是 View 与 Window 关联的入口:
ActivityThread.handleResumeActivity() |
源码位置:frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params, |
ViewRootImpl.setView 是连接 View 体系与 Window 体系的桥梁。
源码位置:frameworks/base/core/java/android/view/ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
关键步骤:
- 创建顶层布局容器 DecorView,它继承自 FrameLayout,是 PhoneWindow 持有的顶层 View。
- DecorView 初始化后,系统根据主题(Theme)加载基础布局,如带 ActionBar 的布局或不带 ActionBar 的布局。这些基础布局中都有一个
android.R.id.content的 FrameLayout,用于承载 Activity 的 ContentView。 - 通过 WindowManagerGlobal 将 DecorView 添加到窗口系统,创建 ViewRootImpl 并调用 setView 将 DecorView 与 ViewRootImpl 关联。
View 的绘制流程
绘制入口
完整的绘制入口调用链:
ActivityThread.handleResumeActivity |
绘制的核心类与方法
核心类及职责:
| 类 | 职责 |
|---|---|
| ViewRootImpl | 视图树的根管理者,负责协调 measure/layout/draw 的执行顺序 |
| Choreographer | 接收 VSYNC 信号,调度三大回调队列(INPUT / ANIMATION / TRAVERSAL) |
| DecorView | 应用窗口的顶层 View(FrameLayout 子类) |
| Surface | 绘制缓冲区,最终像素写入的目标 |
绘制三大步骤
performTraversals() |
VSYNC 信号与 Choreographer
VSYNC 机制
VSYNC(Vertical Synchronization,垂直同步)是 Android 渲染系统的心脏。显示屏以固定的刷新率(通常是 60Hz,即每 16.6ms 一帧)从帧缓冲区读取数据显示。VSYNC 信号在两次屏幕刷新之间发出,通知系统可以开始准备下一帧。
Choreographer:帧回调调度器
Choreographer 负责订阅 VSYNC 信号并根据回调类型分派任务。
源码位置:frameworks/base/core/java/android/view/Choreographer.java
// 回调类型常量 |
FrameDisplayEventReceiver:VSYNC 接收器
private final class FrameDisplayEventReceiver extends DisplayEventReceiver |
doFrame:逐帧处理
void doFrame(long frameTimeNanos, int frame) { |
回调执行顺序严格保证:先处理输入事件(保证触摸响应及时),再更新动画状态,最后执行 UI 的测量、布局和绘制。这个顺序保证了在动画更新和 UI 绘制之前,输入事件已经被处理。
scheduleTraversals 与 VSYNC 同步
void scheduleTraversals() { |
scheduleTraversals 并不直接开始绘制,而是向 Choreographer 注册一个 TRAVERSAL 回调,等待下一个 VSYNC 信号到来时再执行。这就是”异步绘制”的核心——用户的操作(如调用 requestLayout 或 invalidate)不会同步触发重绘,而是等到下一帧统一处理。
同步屏障(Sync Barrier)的作用:在 TRAVERSAL 回调执行之前,阻塞 MessageQueue 中的普通同步消息(如由 Handler.post 提交的任务),保证绘制任务优先执行,减少因其他消息导致的帧延迟。
View 的测量:确定 DecorView 的 MeasureSpec
DecorView 的 MeasureSpec
DecorView 是 View 树的根节点。它的 MeasureSpec 由窗口大小和自身的 LayoutParams 决定:
- LayoutParams.MATCH_PARENT:精确模式(EXACTLY),大小为窗口尺寸
- LayoutParams.WRAP_CONTENT:最大模式(AT_MOST),最大为窗口尺寸
- 固定大小(如 100dp):精确模式(EXACTLY),大小为 LayoutParams 指定的值
View 的 MeasureSpec 由父容器和自身 LayoutParams 决定
普通 View 的 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 通过 ViewGroup.getChildMeasureSpec() 共同决定。具体规则在上文”自定义View”一文中已详细介绍,核心逻辑如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { |
View 的布局
View 的布局阶段负责确定所有 View 在屏幕上的最终位置。
View.layout 四步走
调用 view.layout(left, top, right, bottom) 确定自身的位置,即确定 mLeft、mTop、mRight、mBottom 四个成员变量的值。
public void layout(int l, int t, int r, int b) { |
注意:View 的布局坐标 l、t、r、b 都是相对于父容器而言的。View 的宽度 = r - l,高度 = b - t。
ViewGroup 的 onLayout
ViewGroup 的 onLayout 是一个抽象方法,需要各子类实现。它负责在自身布局完成后确定所有子 View 的位置:
|
典型的 onLayout 实现模式:遍历所有子 View,根据业务逻辑计算它们的坐标,然后调用 child.layout(childL, childT, childR, childB)。
View 的绘制
View.draw 的六步绘制顺序
源码位置:frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) { |
各步骤说明:
- **drawBackground(canvas)**:绘制背景 Drawable。如果 View 没有设置背景,这步不做任何操作。
- 保存图层(有渐变边缘时):为顶部/底部/左/右渐变边缘保存 Canvas 图层。
- **onDraw(canvas)**:绘制 View 自身的内容。对于 ViewGroup,这个方法通常为空,因为它没有自己的内容可绘制。
- **dispatchDraw(canvas)**:绘制子 View。ViewGroup 通过此方法遍历子 View 并调用
child.draw(canvas)。 - **onDrawForeground(canvas)**:绘制前景内容,如滚动条、前景 Drawable(API 23+)。
- **drawDefaultFocusHighlight(canvas)**:绘制默认的焦点高亮(用于键盘导航)。
这四个绘制方法中,只有 dispatchDraw 是 ViewGroup 特有的(View 的 dispatchDraw 是空实现),其余三个方法 View 和 ViewGroup 共用。
drawSoftware:软件绘制路径
当不使用硬件加速时,绘制走 drawSoftware 路径:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, |
Surface.lockCanvas 从 BufferQueue 中 dequeue 一个 GraphicBuffer,包装为 Canvas。unlockCanvasAndPost 将绘制好的缓冲区 queue 回去,交由 SurfaceFlinger 合成显示。
硬件加速:DisplayList 与 RenderThread
从 Android 3.0 开始引入硬件加速,Android 4.0 默认开启。硬件加速使用 GPU 承担部分绘制工作,大幅提升渲染性能。
DisplayList (RenderNode) 记录
启动硬件加速后,View.draw 不再直接绘制到 Canvas,而是将绘制命令录制到一个 DisplayList(Android 5.0 后改名为 RenderNode)中:
源码位置:frameworks/base/core/java/android/view/View.java
public void draw(Canvas canvas) { |
DisplayList 录制的内容被缓存起来。如果 View 只移动、旋转、缩放或改变透明度(即只有变换矩阵或 alpha 改变),不需要重新录制 DisplayList,GPU 可以直接用新参数重新合成缓存的内容。这是硬件加速性能优势的核心。
RenderThread:分离渲染线程
Android 5.0 引入了 RenderThread,将部分渲染工作从 UI 线程分离出来:
UI Thread RenderThread |
源码位置:frameworks/base/libs/hwui/renderthread/RenderThread.cpp
关键流程:
- syncFrameState:UI 线程在此阶段遍历 View 树,将每个 View 的属性(位置、变换、alpha 等)同步到其 RenderNode。
- issueDrawCommands:RenderThread 收集所有 RenderNode 的绘制命令,排序优化后提交给 GPU。
- swapBuffers:GPU 完成绘制后,交换缓冲区提交到 SurfaceFlinger。
三重缓冲与 Swap Chain
BufferQueue 模型
Android 使用 BufferQueue 作为生产者-消费者缓冲区管理机制:
生产者 (App/GPU) 消费者 (SurfaceFlinger) |
三重缓冲
三重缓冲(Triple Buffering)维护三个 GraphicBuffer:
- 一个正在被显示(Display buffer)
- 一个正在被 GPU 绘制(Pending buffer)
- 一个空闲供 CPU 更新(Free buffer)
三重缓冲的优势:当 GPU 处理慢于 VSYNC 周期时,CPU 不必等待 GPU 完成就可以提前开始准备下一帧,避免”流水线停顿”。
帧性能分析工具
dumpsys gfxinfo
dumpsys gfxinfo <package-name> 提供详细的帧性能统计:
adb shell dumpsys gfxinfo com.example.app |
关键指标:
- Janky frames:超过 16ms 的帧。
- Missed Vsync:错过 VSYNC 的帧。
- Slow UI thread:UI 线程执行慢的帧。
- Slow issue draw commands:GPU 提交命令慢的帧。
adb shell dumpsys gfxinfo com.example.app framestats 可以获取更详细的每帧耗时分解。
Profile GPU Rendering(GPU 呈现模式分析)
开发者选项中的”Profile GPU Rendering”以图表形式展示每一帧的绘制耗时,每一条竖条代表一帧,颜色分段含义:
| 颜色 | 含义 | 对应阶段 |
|---|---|---|
| 蓝色 | 进程执行 View 绘制(onDraw 等) | Process |
| 青色 | 执行绘制命令 | Execute |
| 绿色 | 等待 VSYNC | Vsync Delay |
| 紫色 | 输入事件处理 | Input Handling |
| 红色 | 动画处理 | Animation |
| 橙色 | 测量/布局 | Measure/Layout |
| 黄色 | 绘制 | Draw |
| 浅蓝色 | 同步和上传位图到 GPU | Sync/Upload |
| 深绿色 | 提交命令到 GPU | Issue Commands |
| 浅红色 | 交换缓冲区 | Swap Buffers |
理想情况下,所有颜色段的总和不应超过 16ms 的绿色横线。
FrameMetrics API(API 24+)
Android 7.0 引入了 FrameMetrics API,可以在代码中获取每帧的详细性能数据:
getWindow().addOnFrameMetricsAvailableListener( |
Systrace / Perfetto 帧分析
Systrace(新版为 Perfetto)是深度分析帧性能的最强工具:
# 录制 5 秒的系统跟踪 |
在 Systrace 中可以清晰看到每一帧的 F 标记(开始绘制)和 SF 标记(SurfaceFlinger 合成),以及 measure、layout、draw 各阶段的具体耗时。
常见卡顿(Jank)原因与排查
1. 过度绘制(Overdraw)
同一像素在一帧内被绘制多次。常见于多层嵌套布局中,不可见的 View 也参与了绘制。
排查:开启”调试 GPU 过度绘制”(开发者选项中),颜色越深代表过度绘制越严重。
解决:移除不必要的背景、使用 clipRect、合并布局层级。
2. 嵌套权重(Nested Weights)
LinearLayout 的 layout_weight 属性会导致子 View 被测量两次——第一次为确定自身尺寸,第二次为分配剩余空间。嵌套使用会导致测量次数指数级增长。
解决:用 ConstraintLayout 替代 LinearLayout + weight 的组合。
3. 过长的 measure/layout 次数
复杂布局中,一次 requestLayout 可能导致整个视图树重新测量。
解决:使用 sMostRecentSizeInfo 缓存测量结果;减少布局层级深度;优先使用 ConstraintLayout。
4. 在 UI 线程解码位图
BitmapFactory.decodeResource 等操作应在后台线程执行,主线程解码大图可能阻塞数百毫秒。
5. GC 发生在帧渲染期间
在 onDraw 或 onMeasure 中分配对象(如 new Paint()、new Rect())会导致频繁 GC,GC 的 stop-the-world 停顿可能使单帧超时。解决方案:将对象创建提升到构造或初始化阶段。
6. 列表 item 的过度 inflate
RecyclerView / ListView 的 item 布局过于复杂,每次创建 item 的 inflate + measure 耗时累积。
解决:减少 item 布局层级、使用约束布局、开启 setHasFixedSize(true)(如果 item 尺寸固定)。
总结
Android 的绘制流程是一条受 VSYNC 精确定时的管线:
- VSYNC 信号从硬件发出,由 Choreographer 的 FrameDisplayEventReceiver 接收。
- Choreographer.doFrame 按 INPUT → ANIMATION → TRAVERSAL 的顺序分发回调。
- ViewRootImpl.performTraversals 执行 measure → layout → draw。
- 硬件加速路径:DisplayList(RenderNode)录制绘制命令 → RenderThread 提交 GPU 执行 → SurfaceFlinger 合成。
- 软件绘制路径:Surface.lockCanvas 获取 Canvas → View.draw 直接绘制 → unlockCanvasAndPost 提交。
性能优化的核心:减少测量/布局/绘制的耗时,使其保持在 16ms 以内。关键手段包括:减少布局层级、避免过度绘制、使用硬件层缓存、避免主线程分配对象和加载资源。
关键源码文件
| 文件 | 路径 |
|---|---|
| ViewRootImpl.java | frameworks/base/core/java/android/view/ViewRootImpl.java |
| Choreographer.java | frameworks/base/core/java/android/view/Choreographer.java |
| View.java | frameworks/base/core/java/android/view/View.java |
| ViewGroup.java | frameworks/base/core/java/android/view/ViewGroup.java |
| PhoneWindow.java | frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java |
| WindowManagerGlobal.java | frameworks/base/core/java/android/view/WindowManagerGlobal.java |
| DisplayListCanvas.cpp | frameworks/base/libs/hwui/DisplayListCanvas.cpp |
| RenderThread.cpp | frameworks/base/libs/hwui/renderthread/RenderThread.cpp |
| SurfaceFlinger | frameworks/native/services/surfaceflinger/ |

