目录
  1. 1. 简介
  2. 2. View 是如何被添加到屏幕窗口上的
    1. 2.1. 顶层布局容器 DecorView 的创建
  3. 3. View 的绘制流程
    1. 3.1. 绘制入口
    2. 3.2. 绘制的核心类与方法
    3. 3.3. 绘制三大步骤
  4. 4. VSYNC 信号与 Choreographer
    1. 4.1. VSYNC 机制
    2. 4.2. Choreographer:帧回调调度器
      1. 4.2.1. FrameDisplayEventReceiver:VSYNC 接收器
      2. 4.2.2. doFrame:逐帧处理
    3. 4.3. scheduleTraversals 与 VSYNC 同步
  5. 5. View 的测量:确定 DecorView 的 MeasureSpec
    1. 5.1. DecorView 的 MeasureSpec
    2. 5.2. View 的 MeasureSpec 由父容器和自身 LayoutParams 决定
  6. 6. View 的布局
    1. 6.1. View.layout 四步走
    2. 6.2. ViewGroup 的 onLayout
  7. 7. View 的绘制
    1. 7.1. View.draw 的六步绘制顺序
    2. 7.2. drawSoftware:软件绘制路径
  8. 8. 硬件加速:DisplayList 与 RenderThread
    1. 8.1. DisplayList (RenderNode) 记录
    2. 8.2. RenderThread:分离渲染线程
  9. 9. 三重缓冲与 Swap Chain
    1. 9.1. BufferQueue 模型
    2. 9.2. 三重缓冲
  10. 10. 帧性能分析工具
    1. 10.1. dumpsys gfxinfo
    2. 10.2. Profile GPU Rendering(GPU 呈现模式分析)
    3. 10.3. FrameMetrics API(API 24+)
    4. 10.4. Systrace / Perfetto 帧分析
  11. 11. 常见卡顿(Jank)原因与排查
    1. 11.1. 1. 过度绘制(Overdraw)
    2. 11.2. 2. 嵌套权重(Nested Weights)
    3. 11.3. 3. 过长的 measure/layout 次数
    4. 11.4. 4. 在 UI 线程解码位图
    5. 11.5. 5. GC 发生在帧渲染期间
    6. 11.6. 6. 列表 item 的过度 inflate
  12. 12. 总结
    1. 12.1. 关键源码文件
UI进阶之绘制流程及原理

简介

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()
→ WindowManagerImpl.addView(decorView, layoutParams)
→ WindowManagerGlobal.addView()
→ ViewRootImpl.setView(decorView, layoutParams, parentView)

源码位置:frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ...
ViewRootImpl root;
View panelParentView = null;

synchronized (mLock) {
// ...
root = new ViewRootImpl(view.getContext(), display);

view.setLayoutParams(wParams);

mViews.add(view);
mRoots.add(root);
mParams.add(wParams);

// 关键:将 decorView 设置给 ViewRootImpl
root.setView(view, wParams, panelParentView);
}
}

ViewRootImpl.setView 是连接 View 体系与 Window 体系的桥梁。

源码位置:frameworks/base/core/java/android/view/ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// ...
// 请求第一次 layout
requestLayout();
// ...
// 通过 Binder 向 WMS 注册该窗口
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
// ...
}
}
}

关键步骤:

  1. 创建顶层布局容器 DecorView,它继承自 FrameLayout,是 PhoneWindow 持有的顶层 View。
  2. DecorView 初始化后,系统根据主题(Theme)加载基础布局,如带 ActionBar 的布局或不带 ActionBar 的布局。这些基础布局中都有一个 android.R.id.content 的 FrameLayout,用于承载 Activity 的 ContentView。
  3. 通过 WindowManagerGlobal 将 DecorView 添加到窗口系统,创建 ViewRootImpl 并调用 setView 将 DecorView 与 ViewRootImpl 关联。

View 的绘制流程

绘制入口

完整的绘制入口调用链:

ActivityThread.handleResumeActivity
→ WindowManagerImpl.addView(decorView, layoutParams)
→ WindowManagerGlobal.addView()
→ ViewRootImpl.setView(decorView, layoutParms, parentView)
→ ViewRootImpl.requestLayout()
→ scheduleTraversals()
→ doTraversal()
→ performTraversals()

绘制的核心类与方法

核心类及职责:

职责
ViewRootImpl 视图树的根管理者,负责协调 measure/layout/draw 的执行顺序
Choreographer 接收 VSYNC 信号,调度三大回调队列(INPUT / ANIMATION / TRAVERSAL)
DecorView 应用窗口的顶层 View(FrameLayout 子类)
Surface 绘制缓冲区,最终像素写入的目标

绘制三大步骤

performTraversals()
├── performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)
│ └── view.measure(widthMeasureSpec, heightMeasureSpec)
│ └── view.onMeasure(widthMeasureSpec, heightMeasureSpec)
│ └── setMeasuredDimension(measuredWidth, measuredHeight)
│ └── setMeasuredDimensionRaw(measuredWidth, measuredHeight)

├── performLayout(lp, mWidth, mHeight)
│ └── view.layout(left, top, right, bottom)
│ └── view.onLayout(changed, left, top, right, bottom)

└── performDraw()
└── draw(fullRedrawNeeded)
└── drawSoftware(surface, offsetX, offsetY, dirty)
└── view.draw(canvas)

VSYNC 信号与 Choreographer

VSYNC 机制

VSYNC(Vertical Synchronization,垂直同步)是 Android 渲染系统的心脏。显示屏以固定的刷新率(通常是 60Hz,即每 16.6ms 一帧)从帧缓冲区读取数据显示。VSYNC 信号在两次屏幕刷新之间发出,通知系统可以开始准备下一帧。

Choreographer:帧回调调度器

Choreographer 负责订阅 VSYNC 信号并根据回调类型分派任务。

源码位置:frameworks/base/core/java/android/view/Choreographer.java

// 回调类型常量
public static final int CALLBACK_INPUT = 0; // 输入事件处理
public static final int CALLBACK_ANIMATION = 1; // 动画更新
public static final int CALLBACK_TRAVERSAL = 2; // 测量布局绘制

FrameDisplayEventReceiver:VSYNC 接收器

private final class FrameDisplayEventReceiver extends DisplayEventReceiver
implements Runnable {
@Override
public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
// ... 记录 VSYNC 到达时间
mTimestampNanos = timestampNanos;
mFrame = frame;
// 将自身作为 Runnable 提交到主线程执行
Message msg = Message.obtain(mHandler, this);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
}

@Override
public void run() {
doFrame(mTimestampNanos, mFrame);
}
}

doFrame:逐帧处理

void doFrame(long frameTimeNanos, int frame) {
final long startNanos;
synchronized (mLock) {
// ...
mFrameScheduled = false;
mLastFrameTimeNanos = frameTimeNanos;
}

try {
// 按顺序处理所有的回调队列
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
} finally {
// ...
}
}

回调执行顺序严格保证:先处理输入事件(保证触摸响应及时),再更新动画状态,最后执行 UI 的测量、布局和绘制。这个顺序保证了在动画更新和 UI 绘制之前,输入事件已经被处理。

scheduleTraversals 与 VSYNC 同步

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 在下一帧到来之前插入一个同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 注册 CALLBACK_TRAVERSAL 回调
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
// ...
}
}

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) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// ... AT_MOST 和 UNSPECIFIED 分支 ...
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

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) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;

// 1. 设置自己的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

// 2. 如果位置发生变化或需要重新布局
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
// 3. 调用 onLayout——ViewGroup 在此布局子 View
onLayout(changed, l, t, r, b);
// 4. 通知布局变化监听器
// ...
}
// 5. 清除布局标记
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

注意:View 的布局坐标 l、t、r、b 都是相对于父容器而言的。View 的宽度 = r - l,高度 = b - t。

ViewGroup 的 onLayout

ViewGroup 的 onLayout 是一个抽象方法,需要各子类实现。它负责在自身布局完成后确定所有子 View 的位置:

@Override
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);

典型的 onLayout 实现模式:遍历所有子 View,根据业务逻辑计算它们的坐标,然后调用 child.layout(childL, childT, childR, childB)

View 的绘制

View.draw 的六步绘制顺序

源码位置:frameworks/base/core/java/android/view/View.java

public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

// 步骤 1:绘制背景
drawBackground(canvas);

// 步骤 2:如有必要,保存 Canvas 图层(渐变边缘)
final boolean drawTop = ...;
final boolean drawBottom = ...;
final boolean drawLeft = ...;
final boolean drawRight = ...;
if (!drawTop && !drawBottom && !drawLeft && !drawRight) {
// 步骤 3:绘制自身内容
onDraw(canvas);

// 步骤 4:绘制子 View
dispatchDraw(canvas);

// 步骤 5:绘制前景(滚动条等装饰)
onDrawForeground(canvas);

// 步骤 6:绘制默认焦点高亮
drawDefaultFocusHighlight(canvas);

return;
}
// 渐变边缘的特殊处理...
}

各步骤说明:

  1. **drawBackground(canvas)**:绘制背景 Drawable。如果 View 没有设置背景,这步不做任何操作。
  2. 保存图层(有渐变边缘时):为顶部/底部/左/右渐变边缘保存 Canvas 图层。
  3. **onDraw(canvas)**:绘制 View 自身的内容。对于 ViewGroup,这个方法通常为空,因为它没有自己的内容可绘制。
  4. **dispatchDraw(canvas)**:绘制子 View。ViewGroup 通过此方法遍历子 View 并调用 child.draw(canvas)
  5. **onDrawForeground(canvas)**:绘制前景内容,如滚动条、前景 Drawable(API 23+)。
  6. **drawDefaultFocusHighlight(canvas)**:绘制默认的焦点高亮(用于键盘导航)。

这四个绘制方法中,只有 dispatchDraw 是 ViewGroup 特有的(View 的 dispatchDraw 是空实现),其余三个方法 View 和 ViewGroup 共用。

drawSoftware:软件绘制路径

当不使用硬件加速时,绘制走 drawSoftware 路径:

private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scalingRequired, Rect dirty) {
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;

// 通过 Surface.lockCanvas 获取 Canvas
canvas = mSurface.lockCanvas(dirty);

// ... 设置 canvas 密度...
// ...

// 调用 view.draw(canvas)
mView.draw(canvas);
} finally {
// 释放 Canvas,将缓冲区提交到 SurfaceFlinger
surface.unlockCanvasAndPost(canvas);
}
return true;
}

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) {
// ... 硬件加速绘制路径
if (canvas.isHardwareAccelerated()) {
// 获取或创建 RenderNode
RenderNode renderNode = updateDisplayListIfDirty();
if (renderNode != null) {
// 将 RenderNode 绘制到 Canvas
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
}
}
}

@NonNull
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ...检查是否需要重新录制...
if ((mPrivateFlags & PFLAG_DRAWN) != PFLAG_DRAWN || renderNode == null
|| !renderNode.isValid() || (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0) {
// 获取 DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
try {
// 录制绘制命令
canvas.drawRenderNode(mBackgroundRenderNode);
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
onDraw(canvas); // 自身内容 → 写入 DisplayList
}
dispatchDraw(canvas); // 子 View → 写入 DisplayList
onDrawForeground(canvas);
} finally {
renderNode.end(canvas);
}
}
return renderNode;
}

DisplayList 录制的内容被缓存起来。如果 View 只移动、旋转、缩放或改变透明度(即只有变换矩阵或 alpha 改变),不需要重新录制 DisplayList,GPU 可以直接用新参数重新合成缓存的内容。这是硬件加速性能优势的核心。

RenderThread:分离渲染线程

Android 5.0 引入了 RenderThread,将部分渲染工作从 UI 线程分离出来:

UI Thread                    RenderThread
| |
| syncFrameState() → | 同步当前 View 属性到 RenderNode
| |
| | issueDrawCommands() → GPU 执行绘制命令
| |
| | swapBuffers() → SurfaceFlinger 合成

源码位置:frameworks/base/libs/hwui/renderthread/RenderThread.cpp

关键流程:

  1. syncFrameState:UI 线程在此阶段遍历 View 树,将每个 View 的属性(位置、变换、alpha 等)同步到其 RenderNode。
  2. issueDrawCommands:RenderThread 收集所有 RenderNode 的绘制命令,排序优化后提交给 GPU。
  3. swapBuffers:GPU 完成绘制后,交换缓冲区提交到 SurfaceFlinger。

三重缓冲与 Swap Chain

BufferQueue 模型

Android 使用 BufferQueue 作为生产者-消费者缓冲区管理机制:

生产者 (App/GPU)              消费者 (SurfaceFlinger)
| |
| dequeueBuffer() → | 获取一个空闲缓冲区
| 绘制到缓冲区... |
| queueBuffer() → | 将填充好的缓冲区入队
| | acquireBuffer() → 获取最新缓冲区
| | 合成到屏幕...
| | releaseBuffer() → 释放缓冲区回空闲队列

三重缓冲

三重缓冲(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

Stats since: ...
Total frames rendered: 12834
Janky frames: 234 (1.82%)
50th percentile: 5ms
90th percentile: 14ms
95th percentile: 18ms
99th percentile: 42ms
Number Missed Vsync: 56
Number High input latency: 12
Number Slow UI thread: 89
Number Slow bitmap uploads: 23
Number Slow issue draw commands: 34
HISTOGRAM: 5ms=4782 6ms=3789 7ms=1234 ...

关键指标:

  • 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(
new Window.OnFrameMetricsAvailableListener() {
@Override
public void onFrameMetricsAvailable(Window window,
FrameMetrics frameMetrics, int dropCountSinceLastInvocation) {
long totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION);
long inputHandling = frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION);
long animation = frameMetrics.getMetric(FrameMetrics.ANIMATION_DURATION);
long measureLayout = frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
long draw = frameMetrics.getMetric(FrameMetrics.DRAW_DURATION);
long sync = frameMetrics.getMetric(FrameMetrics.SYNC_DURATION);
long commandIssue = frameMetrics.getMetric(FrameMetrics.COMMAND_ISSUE_DURATION);
long swapBuffers = frameMetrics.getMetric(FrameMetrics.SWAP_BUFFERS_DURATION);

// 判断是否为 jank 帧(超过 16ms)
if (totalDuration > 16_000_000) { // 纳秒
Log.w("FrameMetrics", "Jank frame: " + totalDuration / 1_000_000 + "ms");
}
}
},
new Handler(Looper.getMainLooper())
);

Systrace / Perfetto 帧分析

Systrace(新版为 Perfetto)是深度分析帧性能的最强工具:

# 录制 5 秒的系统跟踪
python systrace.py -t 5 -o trace.html gfx input view wm am freq sched

# 或使用 Perfetto(Android 10+)
adb shell perfetto -o /data/misc/perfetto-traces/trace -t 5s \
sched freq idle am wm gfx view input

在 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 精确定时的管线:

  1. VSYNC 信号从硬件发出,由 Choreographer 的 FrameDisplayEventReceiver 接收。
  2. Choreographer.doFrame 按 INPUT → ANIMATION → TRAVERSAL 的顺序分发回调。
  3. ViewRootImpl.performTraversals 执行 measure → layout → draw。
  4. 硬件加速路径:DisplayList(RenderNode)录制绘制命令 → RenderThread 提交 GPU 执行 → SurfaceFlinger 合成。
  5. 软件绘制路径: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/
打赏
  • 微信
  • 支付宝

评论