Android 中 View 的绘制流程是 UI 框架的核心机制,主要分为 Measure(测量)、Layout(布局)、Draw(绘制) 三个阶段。以下是详细的流程和实现原理分析:
一、绘制流程总览
View 的绘制流程由 ViewRootImpl
(Android 3.0+)触发,通过 performTraversals()
方法协调以下三个阶段:
java
// ViewRootImpl.java
private void performTraversals() {
performMeasure(); // 测量
performLayout(); // 布局
performDraw(); // 绘制
}
二、Measure 阶段:确定 View 的尺寸
1. 核心方法
measure(int widthMeasureSpec, int heightMeasureSpec)
由父 View 调用,触发子 View 的测量逻辑。onMeasure(int widthMeasureSpec, int heightMeasureSpec)
自定义 View 需重写此方法,确定自身尺寸。
2. MeasureSpec
-
组成 :32位 int 值,高 2 位表示 Mode (测量模式),低 30 位表示 Size。
-
三种模式:
EXACTLY
:精确尺寸(如match_parent
或具体数值)。AT_MOST
:最大尺寸(如wrap_content
)。UNSPECIFIED
:未指定(如 ScrollView 的子 View)。
3. 流程细节
- 父 View 通过
measureChild()
计算子 View 的MeasureSpec
。 - ViewGroup 需递归测量所有子 View(如
LinearLayout
根据方向计算子 View 尺寸)。 - 最终通过
setMeasuredDimension()
保存测量结果。
关键问题:
-
wrap_content
为何需要特殊处理?默认
onMeasure
不会处理AT_MOST
模式,需要开发者手动设置尺寸。
三、Layout 阶段:确定 View 的位置
1. 核心方法
layout(int l, int t, int r, int b)
由父 View 调用,确定子 View 的位置。onLayout(boolean changed, int l, int t, int r, int b)
ViewGroup 需重写此方法,排列子 View(如FrameLayout
默认堆叠子 View)。
2. 流程细节
- 父 View 根据子 View 的测量结果,计算其左上右下坐标。
- 调用子 View 的
layout()
方法,触发其onLayout()
(仅 ViewGroup 需要实现)。 - 保存位置信息到
mLeft
,mTop
,mRight
,mBottom
。
常见场景:
RelativeLayout
需要两次测量(依赖关系复杂)。- 自定义 ViewGroup 需手动计算子 View 位置。
四、Draw 阶段:绘制内容
1. 核心方法
draw(Canvas canvas)
总控绘制流程,内部调用以下步骤:- 绘制背景 :
drawBackground()
- 绘制自身内容 :
onDraw(Canvas canvas)
- 绘制子 View :
dispatchDraw(Canvas canvas)
- 绘制装饰(如滚动条) :
onDrawForeground()
- 绘制背景 :
2. 关键点
onDraw()
:自定义 View 在此绘制内容(如文本、图形)。dispatchDraw()
:ViewGroup 通过此方法分发绘制到子 View。- 硬件加速:Android 4.0+ 默认开启,使用 GPU 优化绘制(需注意 API 兼容性)。
优化技巧:
- 避免在
onDraw()
中创建对象(频繁调用导致 GC)。 - 使用
Canvas.clipRect()
减少过度绘制。
五、实现原理与底层机制
1. ViewRootImpl 与 Choreographer
-
ViewRootImpl
:连接 WindowManager 和 DecorView 的桥梁,通过performTraversals()
触发绘制。 -
Choreographer:协调 VSYNC 信号(垂直同步),确保绘制按帧率(如 60Hz)执行。
- 收到 VSYNC 后,通过
Handler
触发doFrame()
,进而执行performTraversals()
。
- 收到 VSYNC 后,通过
2. 消息循环机制
- UI 线程通过
Handler
处理MessageQueue
中的绘制任务。 invalidate()
和requestLayout()
会向队列插入任务,触发重绘。
3. 双缓冲与 SurfaceFlinger
- 双缓冲:Surface 包含一个 Front Buffer(显示)和一个 Back Buffer(绘制),减少画面撕裂。
- SurfaceFlinger:合成多个 Surface 的内容,提交给 Display 显示。
六、常见问题与优化
1. 性能瓶颈
- 布局嵌套过深 :导致多次 Measure/Layout(可用
ConstraintLayout
优化)。 - 过度绘制 :可通过开发者选项中的 "Show GPU Overdraw" 检测。
2. 离线绘制
- 场景:复杂静态内容(如图表)。
- 方案 :使用
Bitmap
或Canvas
预渲染,直接绘制缓存。
3. 强制重绘方法
invalidate()
:请求重绘(主线程调用)。postInvalidate()
:非 UI 线程调用重绘。requestLayout()
:触发重新测量和布局。
常用方法:
方法 | 作用 | 触发流程 |
---|---|---|
invalidate() |
标记脏区域,触发重绘(Draw 阶段) | 只走 draw() 流程 |
requestLayout() |
强制重新测量和布局(可能重绘) | 触发 measure() → layout() → draw() |
七、自定义 View 的关键点
- 重写
onMeasure()
:正确处理wrap_content
。 - 避免在
onDraw()
中分配内存:防止卡顿。 - 支持 padding 和 margin:在测量和绘制时需考虑。
总结
View 的绘制流程是一个递归的树形遍历过程,通过 Measure
→ Layout
→ Draw
确定每个 View 的尺寸、位置和内容。理解其底层机制(如 VSYNC、双缓冲)和优化手段,是开发高性能 UI 的关键。