View绑制与渲染
# 07.View绑制与渲染
# 目录介绍
- 一、引言:从setContentView到像素显示
- 二、View树与ViewRootImpl
- 2.1 View层级结构
- 2.2 ViewRootImpl的角色
- 2.3 requestLayout触发绘制
- 三、Measure过程深度剖析
- 3.1 performMeasure入口
- 3.2 View的onMeasure默认实现
- 3.3 ViewGroup的测量——以LinearLayout为例
- 四、Layout过程深度剖析
- 4.1 performLayout入口
- 4.2 View.layout()
- 4.3 ViewGroup的布局
- 五、Draw过程深度剖析
- 5.1 performDraw入口
- 5.2 View.draw()的六步绘制
- 5.3 软件绘制路径
- 六、MeasureSpec的设计原理
- 6.1 MeasureSpec的位运算设计
- 6.2 MeasureSpec的生成规则
- 七、自定义View的绘制流程
- 7.1 自定义View必须处理wrap_content
- 7.2 View.post()获取宽高的原理
- 八、硬件加速渲染原理
- 8.1 软件渲染 vs 硬件加速
- 8.2 RenderNode与DisplayList
- 九、VSync与Choreographer
- 9.1 VSync信号的作用
- 9.2 Choreographer的工作机制
- 9.3 掉帧的原因分析
- 十、Surface与SurfaceFlinger
- 10.1 Surface是什么
- 10.2 BufferQueue:三重缓冲
- 10.3 SurfaceFlinger的合成过程
- 十一、RenderThread与GPU渲染
- 11.1 RenderThread的工作流程
- 11.2 GPU渲染管线
- 十二、invalidate与requestLayout的区别
- 12.1 invalidate的流程
- 12.2 requestLayout的流程
- 12.3 使用场景
- 十三、布局优化与渲染性能
- 13.1 过度绘制(Overdraw)
- 13.2 布局层级优化
- 13.3 GPU呈现模式分析
- 十四、面试高频问题与深度分析
- 14.1 View的绘制流程是从哪里开始的?
- 14.2 为什么子线程不能更新UI?
- 14.3 getWidth和getMeasuredWidth的区别?
- 十五、MeasureSpec设计详解
- 15.1 MeasureSpec意图设计
- 15.2 测量的"递"和"归"思想
- 15.3 单个控件测量流程
- 15.4 完整View树测量流程
- 十六、View刷新与显示
- 16.1 View显示在屏幕的完整流程
- 16.2 View生成图片原理
- 十七、总结
# 一、引言:从setContentView到像素显示
当我们在Activity的onCreate中调用setContentView(R.layout.activity_main)时,从布局XML被解析到屏幕上出现像素,中间经历了复杂而精密的过程。
疑惑:为什么在onCreate中调用view.getWidth()返回0?为什么自定义View要经过measure-layout-draw三个阶段?
本文将从ViewRootImpl触发绘制开始,逐层深入到Surface、SurfaceFlinger、GPU等底层渲染机制。
# 二、View树与ViewRootImpl
# 2.1 View层级结构
Activity的View层级:
ViewRootImpl(不是View,是View树的管理者)
└── DecorView(FrameLayout子类)
├── StatusBarBackground
├── NavigationBarBackground
└── LinearLayout
├── ActionBarContainer
└── FrameLayout (android.R.id.content)
└── 用户布局根View
├── TextView
├── ImageView
└── LinearLayout
├── Button
└── EditText
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 2.2 ViewRootImpl的角色
// ViewRootImpl是View树与WindowManagerService之间的桥梁
// 职责:
// 1. 触发View树的measure/layout/draw
// 2. 管理Surface(绘制目标)
// 3. 处理输入事件分发
// 4. 管理与WMS的通信
// 创建时机:WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params, ...) {
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);
root.setView(view, wparams, panelParentView, userId);
}
// ViewRootImpl.setView() → requestLayout()
public void setView(View view, WindowManager.LayoutParams attrs, ...) {
mView = view; // DecorView
requestLayout(); // 触发首次绘制
// addToDisplayAsUser → 通知WMS添加窗口
res = mWindowSession.addToDisplayAsUser(mWindow, ...);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 2.3 requestLayout触发绘制
// ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); // 检查是否在创建ViewRootImpl的线程
mLayoutRequested = true;
scheduleTraversals(); // 安排遍历
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 1. 设置同步屏障(保证绘制消息优先处理)
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 2. 注册VSync回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL,
mTraversalRunnable, // VSync到来时执行
null);
}
}
// VSync信号到来后执行
final class TraversalRunnable implements Runnable {
public void run() {
doTraversal();
}
}
void doTraversal() {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 执行measure/layout/draw三大流程
performTraversals();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 三、Measure过程深度剖析
# 3.1 performMeasure入口
// ViewRootImpl.performTraversals() 中
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View.measure() — final方法,不可重写
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 如果MeasureSpec没变且已经测量过,跳过
if (forceLayout || needsLayout) {
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec); // 实际测量
} else {
long value = mMeasureCache.valueAt(cacheIndex); // 使用缓存
setMeasuredDimensionRaw((int)(value >> 32), (int)value);
}
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 3.2 View的onMeasure默认实现
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size; // 使用建议最小值
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize; // 使用父容器给的大小
break;
}
return result;
}
// 注意:AT_MOST和EXACTLY返回相同值
// 这就是为什么自定义View必须重写onMeasure处理wrap_content
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 3.3 ViewGroup的测量——以LinearLayout为例
// LinearLayout.onMeasure() 简化逻辑
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
int totalLength = 0; // 总高度
int totalWeight = 0; // 总权重
// 第一次测量:测量非weight子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.weight > 0) {
totalWeight += lp.weight;
if (lp.height == 0) {
continue; // weight子View第一次不测量
}
}
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, totalLength);
totalLength += child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
}
// 第二次测量:分配剩余空间给weight子View
if (totalWeight > 0) {
int remainingSpace = heightSize - totalLength;
for (int i = 0; i < count; i++) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.weight > 0) {
int share = (int)(lp.weight * remainingSpace / totalWeight);
int childHeight = Math.max(0, share);
child.measure(
childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 四、Layout过程深度剖析
# 4.1 performLayout入口
// ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
final View host = mView; // DecorView
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
1
2
3
4
5
6
2
3
4
5
6
# 4.2 View.layout()
public void layout(int l, int t, int r, int b) {
// 记录旧位置
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 设置新位置
boolean changed = setFrame(l, t, r, b);
// setFrame内部:mLeft=l, mTop=t, mRight=r, mBottom=b
// 同时计算:mWidth = r - l, mHeight = b - t
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) != 0) {
onLayout(changed, l, t, r, b); // 子类实现
}
}
// View.getWidth() = mRight - mLeft
// 这就是为什么在onCreate中getWidth()=0:layout还未执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 4.3 ViewGroup的布局
// FrameLayout.onLayout() 简化
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 根据gravity计算子View位置
int childLeft, childTop;
switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = (right - left - child.getMeasuredWidth()) / 2;
break;
default:
childLeft = lp.leftMargin;
}
child.layout(childLeft, childTop,
childLeft + child.getMeasuredWidth(),
childTop + child.getMeasuredHeight());
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 五、Draw过程深度剖析
# 5.1 performDraw入口
// ViewRootImpl.java
private boolean performDraw() {
// 选择绘制方式
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
// 硬件加速绘制路径
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// 软件绘制路径
drawSoftware(surface, mAttachInfo, ...);
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 5.2 View.draw()的六步绘制
// View.draw() — 绘制的核心流程
public void draw(Canvas canvas) {
// Step 1: 绘制背景
drawBackground(canvas);
// Step 2: 保存画布层(如果需要渐变边缘效果)
// 通常跳过
// Step 3: 绘制自身内容
onDraw(canvas); // 子类重写这个方法绘制自己的内容
// Step 4: 绘制子View
dispatchDraw(canvas); // ViewGroup重写此方法遍历绘制子View
// Step 5: 绘制渐变边缘效果和滚动条
// 通常跳过
// Step 6: 绘制前景和滚动指示器
onDrawForeground(canvas);
drawDefaultFocusHighlight(canvas);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 5.3 软件绘制路径
// ViewRootImpl.drawSoftware()
private boolean drawSoftware(Surface surface, ...) {
// 1. 锁定Canvas(从Surface获取绘制缓冲区)
Canvas canvas = mSurface.lockCanvas(dirty);
// 底层:通过GraphicBuffer获取一块内存区域用于绘制
try {
// 2. 清除dirty区域
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
// 3. 从DecorView开始递归绘制
mView.draw(canvas);
} finally {
// 4. 解锁并提交
surface.unlockCanvasAndPost(canvas);
// 底层:将绘制好的GraphicBuffer提交给SurfaceFlinger合成
}
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 六、MeasureSpec的设计原理
# 6.1 MeasureSpec的位运算设计
// MeasureSpec用一个int值同时编码mode和size
// 高2位 = mode,低30位 = size
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 + 30位size
public static final int EXACTLY = 1 << MODE_SHIFT; // 01 + 30位size
public static final int AT_MOST = 2 << MODE_SHIFT; // 10 + 30位size
public static int makeMeasureSpec(int size, int mode) {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
// 设计思想:用一个int传递两个信息,减少方法参数和对象创建
// 30位size最大值 = 2^30 - 1 = 1073741823 ≈ 10亿像素
// 远超任何屏幕尺寸,所以30位足够
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 6.2 MeasureSpec的生成规则
父View的MeasureSpec + 子View的LayoutParams → 子View的MeasureSpec
┌─────────────────┬───────────────┬──────────────┬──────────────┐
│ │ 子View: │ 子View: │ 子View: │
│ 父View Mode │ match_parent │ wrap_content │ 固定尺寸dp │
├─────────────────┼───────────────┼──────────────┼──────────────┤
│ EXACTLY (精确值) │ EXACTLY │ AT_MOST │ EXACTLY │
│ 如200dp │ size=父size │ size=父size │ size=子size │
├─────────────────┼───────────────┼──────────────┼──────────────┤
│ AT_MOST (最大值) │ AT_MOST │ AT_MOST │ EXACTLY │
│ 如wrap_content │ size=父size │ size=父size │ size=子size │
├─────────────────┼───────────────┼──────────────┼──────────────┤
│ UNSPECIFIED │ UNSPECIFIED │ UNSPECIFIED │ EXACTLY │
│ 如ScrollView内 │ size=0 │ size=0 │ size=子size │
└─────────────────┴───────────────┴──────────────┴──────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 七、自定义View的绘制流程
# 7.1 自定义View必须处理wrap_content
// 错误:不处理wrap_content
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 问题:wrap_content和match_parent效果相同
}
// 正确:区分AT_MOST
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width, height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = calculateContentWidth(); // 计算内容实际宽度
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(width, widthSize); // 不超过父容器限制
}
}
// height同理
setMeasuredDimension(width, height);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 7.2 View.post()获取宽高的原理
// 在onCreate中获取View宽高的正确方式
view.post(new Runnable() {
@Override
public void run() {
int width = view.getWidth(); // 此时已有值
int height = view.getHeight();
}
});
// 原理:
// 1. View.post()在View未attach时,将Runnable保存到HandlerActionQueue
// 2. View.dispatchAttachedToWindow()时,将队列中的Runnable post到Handler
// 3. 此时measure/layout已执行完毕
// 4. Runnable在主线程消息队列中执行,此时getWidth()已有值
// 源码路径:
// View.post(runnable)
// → if (attachInfo != null) { attachInfo.mHandler.post(runnable); }
// → else { getRunQueue().post(runnable); } // 暂存
// dispatchAttachedToWindow() → getRunQueue().executeActions(handler);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 八、硬件加速渲染原理
# 8.1 软件渲染 vs 硬件加速
软件渲染(Software Rendering):
CPU负责所有绘制操作
View.draw() → Canvas → Skia → 写入GraphicBuffer(CPU内存)
└── 单线程,主线程执行
硬件加速(Hardware Accelerated Rendering):
CPU记录绘制命令,GPU执行实际渲染
View.draw() → DisplayListCanvas → 记录DrawOp
└── RenderThread → OpenGL/Vulkan → GPU → 写入GraphicBuffer
└── 独立线程,不阻塞主线程
硬件加速的优势:
1. GPU并行计算能力强(数千个核心同时工作)
2. RenderThread独立于主线程(减少主线程负担)
3. DisplayList可以缓存和复用(避免重复构建绘制命令)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8.2 RenderNode与DisplayList
// 每个View都有一个RenderNode
// RenderNode内部维护一个DisplayList(绘制指令列表)
// View.updateDisplayListIfDirty()
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (needsUpdate) {
// 开始录制绘制指令
RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
// 调用draw(),但此时Canvas是RecordingCanvas
// 所有绘制操作被记录而非直接执行
draw(canvas);
// canvas.drawRect() → 记录DrawRectOp
// canvas.drawText() → 记录DrawTextOp
// canvas.drawBitmap() → 记录DrawBitmapOp
} finally {
renderNode.endRecording();
}
}
return renderNode;
}
// DisplayList结构示意:
// RenderNode(DecorView) {
// DrawRectOp(0,0,1080,1920, paint=white) // 背景
// DrawRenderNodeOp → RenderNode(LinearLayout) {
// DrawRenderNodeOp → RenderNode(TextView) {
// DrawTextOp("Hello", 100, 50, paint)
// }
// DrawRenderNodeOp → RenderNode(ImageView) {
// DrawBitmapOp(bitmap, 0, 100)
// }
// }
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 九、VSync与Choreographer
# 9.1 VSync信号的作用
没有VSync时的问题 — 画面撕裂(Tearing):
显示器刷新 ─────────────────────────────────→
│← 帧1 →│← 帧2 →│
GPU渲染 ────────────────────────────────→
│← 帧A ──→│← 帧B ─→│
↑ GPU正在写入帧B时,显示器读取了半帧A+半帧B
→ 画面撕裂!
有VSync后:
VSync信号 ──┤────────┤────────┤────────┤──→
↑ 通知开始绘制下一帧
显示器刷新 ──│← 帧1 →│← 帧2 →│← 帧3 →│──→
GPU渲染 ──│← 帧A →│← 帧B →│← 帧C →│──→
同步!无撕裂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 9.2 Choreographer的工作机制
// Choreographer是Android UI渲染的"指挥家"
// 它协调输入、动画、绘制三者的执行时机
public final class Choreographer {
// 回调类型(按优先级排序)
public static final int CALLBACK_INPUT = 0; // 输入事件
public static final int CALLBACK_ANIMATION = 1; // 动画
public static final int CALLBACK_INSETS_ANIMATION = 2;
public static final int CALLBACK_TRAVERSAL = 3; // View遍历(measure/layout/draw)
public static final int CALLBACK_COMMIT = 4; // 帧提交
// VSync回调
private final class FrameDisplayEventReceiver extends DisplayEventReceiver {
@Override
public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
// VSync信号到来
mTimestampNanos = timestampNanos;
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true); // 异步消息,不受同步屏障影响
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}
@Override
public void run() {
doFrame(mTimestampNanos, mFrame);
}
}
void doFrame(long frameTimeNanos, int frame) {
// 按优先级依次执行回调
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
}
}
// 一帧的完整流程(16.6ms @60fps):
// VSync → Input处理 → Animation计算 → Traversal(measure/layout/draw)
// → RenderThread → GPU渲染 → SurfaceFlinger合成 → 显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 9.3 掉帧的原因分析
正常帧(16.6ms内完成):
VSync0 ──── Input → Animation → Traversal → Render ──── VSync1
← 16.6ms →
掉帧(超过16.6ms):
VSync0 ──── Input → Animation → [耗时操作] → Traversal → Render ──── VSync2
← 33.2ms(丢了1帧)→
VSync1时没有新帧,重复显示旧帧
常见掉帧原因:
1. 主线程耗时操作(IO、计算)→ Traversal被延迟
2. 布局太复杂 → measure/layout耗时长
3. 过度绘制 → draw耗时长
4. GC停顿 → 所有线程暂停
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 十、Surface与SurfaceFlinger
# 10.1 Surface是什么
Surface的本质:一块图形缓冲区(GraphicBuffer)的管理者
┌──────────────────────────────────────────────┐
│ App进程 │
│ ┌──────────┐ │
│ │ Surface │ → 管理BufferQueue的生产者端 │
│ │ │ → dequeueBuffer() 获取空缓冲区 │
│ │ │ → 绘制内容到缓冲区 │
│ │ │ → queueBuffer() 提交已绘制缓冲区│
│ └──────────┘ │
└──────────────────────────────────────────────┘
│ BufferQueue
↓
┌──────────────────────────────────────────────┐
│ SurfaceFlinger进程 │
│ ┌──────────┐ │
│ │ Layer │ → 管理BufferQueue的消费者端 │
│ │ │ → acquireBuffer() 获取已绘制缓冲区│
│ │ │ → 合成所有Layer │
│ │ │ → 输出到显示设备 │
│ └──────────┘ │
└──────────────────────────────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 10.2 BufferQueue:三重缓冲
BufferQueue的三重缓冲机制:
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Buffer0 │ │ Buffer1 │ │ Buffer2 │
│ (Front) │ │ (Back) │ │ (Free) │
│ 正在显示 │ │ 正在绘制 │ │ 空闲 │
└─────────┘ └─────────┘ └─────────┘
一帧的Buffer流转:
1. App: dequeueBuffer() → 获取Free Buffer(Buffer2)
2. App: 在Buffer2上绘制
3. App: queueBuffer(Buffer2) → Buffer2变为Queued
4. SF: acquireBuffer() → 获取Buffer2用于合成
5. SF: 合成完成 → 释放Buffer0(之前的Front)→ Buffer0变为Free
6. Buffer2变为新的Front Buffer
// 三重缓冲减少了"掉帧"概率:
// 双缓冲:如果渲染和合成都需要Buffer,只有2个可能都在用
// 三重缓冲:总有一个空闲Buffer可用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 10.3 SurfaceFlinger的合成过程
SurfaceFlinger合成多个Layer的过程:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 状态栏Layer │ │ 应用Layer │ │ 导航栏Layer │
│ (半透明) │ │ (不透明) │ │ (半透明) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
└────────┬───────┘────────────────┘
↓
┌──────────────┐
│ SurfaceFlinger │
│ 合成策略: │
│ 1. GPU合成 │ → OpenGL ES混合各Layer
│ 2. HWC合成 │ → 硬件合成器(更省电)
└──────┬───────┘
↓
┌──────────────┐
│ Display HAL │ → 输出到物理屏幕
└──────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 十一、RenderThread与GPU渲染
# 11.1 RenderThread的工作流程
主线程与RenderThread的协作:
主线程: RenderThread:
┌─────────────────────┐ ┌─────────────────────┐
│ 1. 构建/更新DisplayList │ │ │
│ (View.draw录制) │ │ │
│ 2. syncFrameState() │───────→│ 3. 接收DisplayList │
│ 同步渲染数据 │ 同步 │ 4. 转换为GPU指令 │
│ │ 点 │ (DrawOp → GL Cmd) │
│ 3. 主线程继续处理 │ │ 5. 提交到GPU执行 │
│ 下一帧的事务 │ │ 6. swapBuffers() │
│ │ │ 提交帧到SF │
└─────────────────────┘ └─────────────────────┘
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 11.2 GPU渲染管线
绘制指令到像素的GPU处理流程:
DrawOp (如drawRect)
↓
顶点着色器(Vertex Shader)
→ 处理矩形的4个顶点坐标
→ 应用变换矩阵(平移、旋转、缩放)
↓
光栅化(Rasterization)
→ 将矩形覆盖的区域转换为像素片段
↓
片段着色器(Fragment Shader)
→ 计算每个像素的最终颜色
→ 应用纹理采样、混合模式等
↓
帧缓冲(Framebuffer)
→ 像素写入GraphicBuffer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 十二、invalidate与requestLayout的区别
# 12.1 invalidate的流程
// invalidate:只触发draw,不触发measure和layout
public void invalidate() {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, true, true);
}
void invalidateInternal(int l, int t, int r, int b, ...) {
// 标记PFLAG_DIRTY
mPrivateFlags |= PFLAG_DIRTY;
// 向上传递dirty区域
final ViewParent p = mParent;
if (p != null) {
p.invalidateChild(this, damage);
// 层层向上 → 到ViewRootImpl
// ViewRootImpl.invalidateChildInParent()
// → scheduleTraversals()
// → performTraversals()中只执行performDraw()
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 12.2 requestLayout的流程
// requestLayout:触发measure + layout + draw
public void requestLayout() {
// 标记PFLAG_FORCE_LAYOUT
mPrivateFlags |= PFLAG_FORCE_LAYOUT | PFLAG_INVALIDATED;
if (mParent != null) {
mParent.requestLayout();
// 层层向上 → 到ViewRootImpl
// ViewRootImpl.requestLayout()
// → scheduleTraversals()
// → performTraversals()中执行全部三个阶段
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 12.3 使用场景
invalidate():
→ View的内容变了(颜色、文字等),但大小位置不变
→ 例如:改变背景色、更新文字内容
→ 只需重新draw
requestLayout():
→ View的大小或位置可能变了
→ 例如:setText改变文字长度、setVisibility
→ 需要重新measure + layout + draw
postInvalidate():
→ 在子线程中调用invalidate
→ 内部通过Handler.post切到主线程
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 十三、布局优化与渲染性能
# 13.1 过度绘制(Overdraw)
过度绘制:同一个像素被绘制多次
示例:
Activity背景(白色) → 第1次绘制
LinearLayout背景(灰色) → 第2次绘制
CardView背景(白色) → 第3次绘制
TextView文字 → 第4次绘制
这个像素被绘制了4次,其中前3次是浪费的
优化方案:
1. 移除不必要的背景
→ Activity主题设置:<item name="android:windowBackground">@null</item>
→ 移除ViewGroup的默认背景
2. clipRect裁剪
→ 自定义View中只绘制可见区域
canvas.clipRect(visibleRect);
canvas.drawBitmap(bitmap, 0, 0, paint);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 13.2 布局层级优化
减少布局层级的策略:
1. ConstraintLayout替代嵌套LinearLayout
嵌套3层 → ConstraintLayout 1层
measure次数从 O(2^n) 降为 O(n)
2. merge标签
减少include引入的额外层级
3. ViewStub延迟加载
不常显示的布局(错误页、空状态)使用ViewStub
只在需要时才inflate,不占用measure/layout时间
4. 避免RelativeLayout嵌套
RelativeLayout会触发两次measure
嵌套两层 = 4次measure(指数增长)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 13.3 GPU呈现模式分析
开发者选项 → GPU呈现模式 → 柱状图:
每个竖条代表一帧的渲染时间,颜色代表各阶段:
绿色横线 = 16.6ms(60fps目标线)
超过绿线 = 掉帧
竖条颜色(从底到顶):
├── 蓝色:Draw时间(创建/更新DisplayList)
│ → 问题:onDraw()太复杂,Canvas操作太多
├── 紫色:同步上传(纹理上传到GPU)
│ → 问题:Bitmap太大
├── 红色:处理执行(GPU执行绘制命令)
│ → 问题:View树太深,过度绘制严重
├── 橙色:交换缓冲区
│ → 问题:合成负担太重
└── 黄色:其他(Input、Animation等)
→ 问题:主线程有耗时操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 十四、面试高频问题与深度分析
# 14.1 View的绘制流程是从哪里开始的?
完整链路:
ViewRootImpl.requestLayout()
→ scheduleTraversals()
→ mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable)
→ VSync信号到来
→ doTraversal()
→ performTraversals()
→ performMeasure() → View.measure() → onMeasure()
→ performLayout() → View.layout() → onLayout()
→ performDraw() → View.draw() → onDraw()
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 14.2 为什么子线程不能更新UI?
根本原因:ViewRootImpl.checkThread()
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException("...");
}
}
调用时机:requestLayout()、invalidate()等
但也有例外:
1. ViewRootImpl创建前(onResume之前)→ 无检查
2. SurfaceView → 专门支持子线程绘制
3. TextureView → 通过lockCanvas支持子线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 14.3 getWidth和getMeasuredWidth的区别?
getMeasuredWidth():
→ 在onMeasure()中由setMeasuredDimension()设置
→ 表示View测量后的宽度
→ 在measure后可用
getWidth():
→ 等于mRight - mLeft,在layout()中由setFrame()设置
→ 表示View最终在屏幕上的宽度
→ 在layout后可用
通常两者相等,但也可以不等:
→ 在layout()中故意设置不同的值
→ 例如:child.layout(0, 0, child.getMeasuredWidth() + 100, ...)
→ 此时 getWidth() = getMeasuredWidth() + 100
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 十五、MeasureSpec设计详解
# 15.1 MeasureSpec意图设计
测量流程中对布局的设计是通过MeasureSpec类来描述的。MeasureSpec包含两个属性:测量模式和测量大小。
MeasureSpec用一个32位int值来表示布局要求。前2位代表测量模式,后30位表示测量大小,通过位运算获取mode和size。
三种测量模式:
- UNSPECIFIED:父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小
- EXACTLY:父元素决定子元素的确切大小(对应match_parent或指定dp/px)
- AT_MOST:子元素至多达到指定大小的值(对应wrap_content)
# 15.2 测量的"递"和"归"思想
View测量流程使用了经典的递归思想:
递流程(自顶向下):
- 顶层父控件将布局要求(MeasureSpec)传递给子控件
- 子控件根据测量策略计算出自身的布局要求,再传递给下一级子控件
- 如此往复,直至最后一级子控件
归流程(自底向上):
- 最后一级子控件测量完毕后调用setMeasuredDimension()
- 父控件根据所有child的宽高数据进行聚合,计算出自身宽高
- 如此往复,直至完成顶层View的测量
关键理解:子控件的测量结果是由父控件和其本身共同决定的,父控件通过MeasureSpec将布局约束传递给子控件。
# 15.3 单个控件测量流程
单个View测量流程:
父控件调用 child.measure(widthSpec, heightSpec)
→ measure() [final方法,公共逻辑]
→ onMeasure() [开发者自定义测量策略]
→ getDefaultSize() [根据MeasureSpec计算默认大小]
→ setMeasuredDimension() [保存测量结果,标志测量完成]
1
2
3
4
5
6
7
2
3
4
5
6
7
measure():被配置了final修饰符,保证公共逻辑代码安全onMeasure():暴露给开发者重写,自定义测量策略setMeasuredDimension():将测量结果存储到mMeasuredWidth和mMeasuredHeight
# 15.4 完整View树测量流程
以竖直方向LinearLayout为例,测量策略为"遍历获取所有子控件,将高度累加":
// 简化版LinearLayout测量
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 1.遍历测量每个child(递流程)
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// 计算子控件的布局要求(考虑padding)
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
// 2.累加高度(归流程)
int height = 0;
for (int i = 0; i < getChildCount(); i++) {
height += child.getMeasuredHeight();
}
// 3.完成自身测量
setMeasuredDimension(width, height);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
measureChild()方法的核心作用是:根据父布局的MeasureSpec和padding值,通过getChildMeasureSpec()计算出子控件的MeasureSpec,再让子控件根据新的布局要求进行测量。
# 十六、View刷新与显示
# 16.1 View显示在屏幕的完整流程
View显示在屏幕的完整流程:
Vsync调度 → 消息调度(doFrame) → input处理 → 动画处理
→ View三大流程(measure/layout/draw)
→ DisplayList更新 → OpenGL指令转换
→ 指令buffer交换 → GPU处理
→ Layer合成 → 光栅化 → Display → buffer切换
各阶段详细说明:
1. VSync调度
Display硬件产生VSync信号(每16.6ms一次)
→ SurfaceFlinger分发给Choreographer
→ Choreographer触发doFrame回调
2. 消息调度
Choreographer.doFrame()
→ 按优先级依次处理:Input → Animation → Traversal
→ Traversal回调中执行performTraversals()
3. View三大流程
performTraversals()
→ performMeasure() → 自顶向下递归测量
→ performLayout() → 自顶向下递归布局
→ performDraw() → 构建/更新DisplayList
4. GPU渲染
RenderThread接收DisplayList
→ 遍历RenderNode树
→ 转换为OpenGL/Vulkan绘制命令
→ GPU执行光栅化
→ 结果写入GraphicBuffer
5. 合成显示
SurfaceFlinger接收所有App的GraphicBuffer
→ HWC(硬件合成器)合成所有Layer
→ 写入FrameBuffer
→ Display控制器读取FrameBuffer → 屏幕显示
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 16.2 View生成图片原理
从普通View获取图像的核心API是view.getDrawingCache()(API 28已废弃),原理是:
- 根据View的宽高属性创建一个新的Bitmap
- 将新Bitmap设置给一个Canvas
- 调用源View的draw(canvas)方法,将图像绘制到新Bitmap上
- 保存新Bitmap即得到View的图像
View截图的多种方案对比:
1. getDrawingCache() [已废弃]
view.setDrawingCacheEnabled(true);
Bitmap bmp = view.getDrawingCache();
→ 缺点:Bitmap大小受drawingCacheSize限制
→ API 28标记为@Deprecated
2. Canvas直接绘制 [推荐]
Bitmap bmp = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
view.draw(canvas);
→ 优点:无大小限制,兼容性好
→ 缺点:软件渲染,不包含硬件加速的绘制内容
3. PixelCopy [Android 7.1+, 推荐]
PixelCopy.request(window, rect, bitmap, listener, handler);
→ 优点:可以截取硬件加速渲染的内容
→ 从SurfaceFlinger的合成结果中拷贝像素
→ 支持截取指定区域
→ 异步回调,不阻塞主线程
4. Surface截图
Surface.lockHardwareCanvas()
→ 直接从GPU的Buffer中获取像素数据
→ 性能最好,但需要Surface引用
注意事项:
SurfaceView/TextureView有独立Surface
→ Canvas方式无法截取其内容
→ 必须使用PixelCopy或Surface方案
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 十七、总结
View绘制与渲染是Android UI框架的核心:
View渲染知识图谱:
绘制流程
├── ViewRootImpl → scheduleTraversals → performTraversals
├── Measure → MeasureSpec → onMeasure → setMeasuredDimension
├── Layout → onLayout → setFrame(l,t,r,b)
└── Draw → onDraw → Canvas绑定操作
渲染管线
├── Choreographer → VSync信号协调
├── 硬件加速 → RenderNode → DisplayList → RenderThread
├── Surface → BufferQueue → 三重缓冲
└── SurfaceFlinger → Layer合成 → 显示
性能优化
├── 减少布局层级 → ConstraintLayout
├── 减少过度绘制 → 移除不必要背景
├── invalidate vs requestLayout → 选择最小代价刷新
└── 硬件加速 → 充分利用GPU能力
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
理解View绘制原理,是做UI性能优化、自定义View开发、动画实现的基础。
上次更新: 2026/06/10, 11:13:41