EN

Android Perfetto 系列 7:MainThread 和 RenderThread 解读

字数统计: 8.8k阅读时长: 34 min
2025/08/02
loading

一次滑动卡顿,主线程可能在处理布局,也可能早已把绘制命令交给 RenderThread。这篇用 Perfetto 截图对照两条线程、Buffer 提交和 SurfaceFlinger,找出等待发生在哪一步。

先沿截图读一帧,再看 ActivityThread、RenderThread 和 BLASTBufferQueue 的职责。Trace 能缩小调查范围;帧最终是否上屏,还要核对 FrameTimeline 与合成侧记录。

本文目录

系列文章目录

  1. Android Perfetto 系列目录
  2. Android Perfetto 系列 1:Perfetto 工具简介
  3. Android Perfetto 系列 2:Perfetto Trace 抓取
  4. Android Perfetto 系列 3:熟悉 Perfetto View
  5. Android Perfetto 系列 4:使用命令行在本地打开超大 Trace
  6. Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
  7. Android Perfetto 系列 6:为什么是 120Hz?高刷新率的优势与挑战
  8. Android Perfetto 系列 7:MainThread 和 RenderThread 解读
  9. Android Perfetto 系列 8:深入理解 Vsync 机制与性能分析
  10. Android Perfetto 系列 9:CPU 信息解读
  11. Android Perfetto 系列 10:Binder 调度与锁竞争
  12. Android Perfetto 系列 11:PerfettoSQL、Trace Processor 与回归检测
  13. Android Perfetto 系列 12:Trace 数据流与丢失排查
  14. Android Perfetto 系列 13:Perfetto SDK、Track Event 与 App 现场 Trace
  15. Android Perfetto 系列 14:heapprofd 与内存 Profiling
  16. Android Perfetto 系列 15:Boot Trace、长时间现场 Trace 与偶发问题抓取
  17. Android Perfetto 系列 16:GPU、Power Counters 与硬件瓶颈分析
  18. Android Perfetto 系列 17:专项场景自动化、问题清单与平台体系
  19. Android Perfetto 系列 18:响应速度实战,从输入事件到首帧反馈
  20. 视频(B站) - Android Perfetto 基础和案例分享
  21. 视频(B站) - Android Perfetto 分享 - 出图类型分享:AOSP、WebView、Flutter + OEM 系统优化分享

如果大家还没看过 Systrace 系列,下面是传送门:

  1. Systrace 系列目录 : 系统介绍了 Perfetto 的前身 Systrace 的使用,并通过 Systrace 来学习和了解 Android 性能优化和 Android 系统运行的基本规则。
  2. 个人博客 :个人博客,主要是 Android 相关的内容,也放了一些生活和工作相关的内容。

欢迎大家在 关于我 页面查看交流方式,讨论你的问题、你最想看到的关于 Perfetto 的部分,以及跟各位群友讨论所有 Android 开发相关的内容.

本文使用到的 Trace 文件我上传到了 Github :https://github.com/Gracker/SystraceForBlog/blob/master/Android_Perfetto/demo_app_aosp_scroll.perfetto-trace ,需要的可以自取。

版本说明(2026-10 核查):本文 Java/HWUI 节选以 android-16.0.0_r1 核对,BLAST 计数以 android-15.0.0_r1 为例;Android 13 的 unsignaled latch 和 Android 17 的 MessageQueue 更新另行标注。附带 trace 的 SystemInfo 记录为 OPPO PKH110、Android 15/API 35、Perfetto v46.0,文件名含 aosp 不代表纯 AOSP 固件。多数新图显示示例 App,另有 com.tencent.mm 详情图和旧 Systrace 图,不能把它们拼成同一次采集。

基于 Perfetto 的渲染流程分析

这里以滑动列表为例,从主线程的一次 doFrame 走到 RenderThread 的绘制和提交。帧的路径会随软件/硬件绘制、窗口类型和更新内容变化;截图展示的是这一份 Trace 中实际出现的阶段。

帧的概念和基本参数

先区分显示刷新间隔与 App 实际产帧数。固定显示模式的名义刷新间隔如下:

  • 60 Hz 模式:约每 16.67 ms 刷新一次。
  • 90 Hz 模式:约每 11.11 ms 刷新一次。
  • 120 Hz 模式:约每 8.33 ms 刷新一次。

屏幕可以重复显示上一帧,不能由刷新率直接推出 App 每秒提交了多少新帧。

在 Perfetto 中分析渲染性能时,需要重点关注以下两个线程:

  • UI Thread:应用主线程,处理用户输入、业务逻辑、布局计算
  • RenderThread:运行在 CPU 上的渲染线程,组织和提交 GPU 命令;GPU 执行命令,buffer 提交连接到 SurfaceFlinger

主线程和渲染线程的工作流程

image-20250803165650716

截图里,主线程处理回调并记录绘制命令,RenderThread 接手后继续执行绘制和提交。先沿时间线找到两条线程的交接点,再看 Buffer 和 SurfaceFlinger;不要把图中的相邻 slice 全部当成同一帧。

重要说明:并非每一帧都会执行所有步骤。Input、Animation、Insets Animation 都是按需执行。Traversal(measure、layout、draw)同样是按需触发:requestLayout、invalidate、窗口属性/可见性变化等场景常会通过 scheduleTraversals() 投递 CALLBACK_TRAVERSAL。在连续滑动/动画场景下它常常“看起来像每帧都在跑”。

下面按截图中的观察顺序逐段看。

1. 主线程等待 Vsync 信号

  • Perfetto trace:主线程在两次工作之间可能处于 Sleeping 状态。
  • 流程说明:有待处理的帧时,Choreographer 请求下一次 VSync 回调;Sleeping 只能说明线程当时未运行,不能仅凭空白轨道断定它正在等待 VSync。

2. Vsync-app 信号传递过程

SurfaceFlinger 的调度器结合硬件时序与软件预测安排通知,不能把每次 VSYNC-app 翻转都解释为一次硬件中断即时转发。图中的 app/EventThread 服务可能同时面对多个连接;这条全局轨道不能单独归属某个 App。

Choreographer 按需请求一次通知并处理到期回调。其他 Vsync 订阅方式和渲染路径可能不同;某个 App 未申请这一轮 Choreographer 回调,不等于系统中没有其他帧或 buffer 提交。

3. SurfaceFlinger 唤醒 App 主线程

Vsync 事件经 DisplayEventReceiver 的 native FD/JNI 路径到达 Java onVsync(),再投递异步 Message。Looper 稍后分发 Receiver Runnable 时才进入 doFrame();通知到达和开始处理帧不是同一时刻。能否看到对应 slice 取决于插桩和采集配置,详见第五篇。

4. 处理输入事件(Input)

  • Perfetto trace: Input 块
  • 流程说明:只有这一帧登记了相应回调,才会出现 Input 阶段;系统输入事件并非全部在这里处理。
  • 触发条件:
  • 可能有 Input 回调:手指滑动期间,ViewRootImpl 为当前帧登记了输入处理。
  • 可能没有 Input 回调:惯性滑动或静止时没有对应待处理输入。
  • 注意:是否出现该 slice 以当次 Trace 为准,不能由手指状态单独推断。

5. 处理动画(Animation)

animation 阶段执行已到期的 CALLBACK_ANIMATION,其中包括属性动画、View.postOnAnimation 和应用 FrameCallback。有该阶段不一定存在肉眼可见的动画;创建列表 item 或处理输入也不保证出现它。回调可能在之前或当前帧前面的阶段登记,不限于上一帧 post。

6. 处理 Insets 动画

insetsAnimation 阶段处理已到期的窗口 Insets 动画回调,例如受支持的 IME/系统栏动画。Insets 指系统 UI 占用的边缘区域,不是“插入窗口”。静态 Insets 变化、键盘出现或导航栏改变不保证每次都产生这一 slice。

7. Traversal(测量、布局、绘制准备)

  • Perfetto trace: performTraversals, measure, layout, draw
  • 流程说明: Android UI 渲染的三大核心流程,但并不是每个 Vsync 都完整执行一遍,是否执行取决于本帧是否有布局/绘制请求。

7.1 Measure(测量阶段)

  • 作用: 确定每个 View 的尺寸大小
  • 过程: 父容器按布局策略测量需要更新的子 View;缓存或跳过分支可减少工作,并非每次遍历都重测整棵树
  • 关键概念:
  • MeasureSpec:封装了父容器对子 View 的尺寸要求(EXACTLY、AT_MOST、UNSPECIFIED)
  • onMeasure():自定义 View 可重写此方法实现测量逻辑,并非每个 View 都要重写
  • Perfetto 中的表现: measure 事件,耗时取决于 View 层级复杂度

7.2 Layout(布局阶段)

  • 作用: 确定每个 View 在父容器中的位置坐标
  • 过程: 基于 Measure 阶段的结果,为每个 View 分配实际的显示位置
  • 关键概念:
  • layout(left, top, right, bottom):设置 View 的四个边界坐标
  • onLayout():ViewGroup 重写此方法来确定子 View 的位置
  • Perfetto 中的表现: layout 事件;耗时由布局逻辑与层级决定,不能普遍认定比 measure 快

7.3 Draw(绘制阶段)

  • 作用: 将 View 的内容绘制到画布上
  • 硬件加速 View 路径:记录或更新 DisplayList;软件 Canvas 路径仍可直接栅格化像素
  • 关键流程:
  • draw(Canvas):绘制 View 自身内容
  • onDraw(Canvas):子类重写实现具体绘制逻辑
  • dispatchDraw(Canvas):ViewGroup 用来绘制子 View
  • Perfetto 中的表现: draw 事件,在硬件加速下主要是构建 DisplayList

ViewRootImpl.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
// frameworks/base/core/java/android/view/ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

private void performTraversals() {
// ... 大量窗口/relayout/可见性/同步相关逻辑
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
// 可能触发 measureHierarchy / performMeasure
}

final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
performLayout(lp, mWidth, mHeight);
}

// 满足条件才会进入 draw;若取消绘制会重新 scheduleTraversals()
if (!isViewVisible) {
// Visibility/sync handling omitted.
} else if (cancelAndRedraw) {
scheduleTraversals();
} else {
performDraw(mActiveSurfaceSyncGroup);
// Return-value and sync handling omitted.
}
}

Android 16 关系示意:局部变量和外围分支未列出,不是可直接编译的完整方法。不可见、取消绘制与取消后重调度是不同分支;performDraw 也可能返回未绘制。

三阶段的执行条件:

  • Measure: 在首次绘制、窗口/Insets/配置变化或布局请求触发时执行,不是每帧固定执行
  • Layout: 当本帧存在 layoutRequested 且应用处于可绘制状态时执行
  • Draw: 满足绘制条件时执行;若被 cancelDraw/predraw 等分支中断,会重新调度下一次 Traversal

8. 同步 DisplayList 到渲染线程

  • Perfetto trace: syncAndDrawFrame,可见 “sync” 或 “syncAndDrawFrame” 事件(通常显示为主线程向渲染线程的数据传递点)
  • 流程说明:主线程通过 syncAndDrawFrame 将本帧 RenderNode/DisplayList 状态同步到 RenderThread。UI 线程会在 DrawFrameTask::postAndWait 等待关键同步点,解除阻塞后继续处理消息;屏幕呈现还要经过后续渲染与合成。

9. 渲染线程获取 Buffer

  • Perfetto trace:按实际插桩查 dequeueBuffer/queueBuffer 等 slice。DequeueBufferDuration / QueueBufferDuration 是 HWUI 帧统计字段,不保证以同名 slice 出现
  • 流程说明: 渲染线程在绘制提交阶段会通过 ANativeWindow/RenderPipeline 路径完成 buffer 申请与交换。是否等待、等待多久,直接影响这一帧是否赶上 deadline。

10. 处理渲染指令并 flush 到 GPU

  • Perfetto trace: drawing 相关块
  • 流程说明: RenderThread(运行在 CPU 上)通过 HardwareRenderer/CanvasContext 处理 UI 线程同步过来的 RenderNode 树,生成 GPU 命令并提交。提交关联的 fence 在 GPU 工作完成后 signal,供后续合成同步。

11. 提交 Buffer(可能 unsignaled)

queueBuffer 提交可携带尚未 signal 的 acquire fence,这是异步 GPU 管线允许的情况,不等于已经发生 unsignaled latch。前者是生产者提交,后者是 SurfaceFlinger 是否提前采用 buffer 的策略。仅有 queueBuffer slice 不足以确定 fence 状态,需对应 fence/图形数据。

12. 触发 Transaction 到 SurfaceFlinger

  • Perfetto trace: TransactionQueue 或 BLAST transaction 事件 ,一般在 queueBuffer 之后,有些 Trace 没有这个 Tag
  • 流程说明: App 侧通过 BLAST/SurfaceControl transaction 将 buffer 与层属性更新关联后提交到 SurfaceFlinger。SurfaceFlinger 再按 LatchUnsignaledConfig 等策略决定 latch 时机并完成合成显示。

在 Perfetto 中识别不同的渲染模式:

  • 手指滑动时:可先找 Input 与 Traversal,再确认是否进入 RenderThread。
  • 惯性滑动时:常见 Animation 与 Traversal,是否还有输入回调要看当次事件。
  • 静止状态时:没有新内容时,App 可以不提交新帧;后台动画或其他更新仍可能触发绘制。

软件绘制 vs 硬件加速

虽然现在基本都使用硬件加速渲染,但了解两种渲染模式的区别仍然有助于理解 Perfetto trace:

方面 软件绘制 硬件加速
绘制线程 普通 View 路径通常在主线程;其他 Canvas 用法可不同 主线程记录命令,RenderThread 组织后续工作,GPU 执行
绘制引擎 Skia (CPU) Skia 的 OpenGL/Vulkan 等 GPU 后端
Perfetto 特征 可能见主线程 draw 与栅格化/等待 主线程与 RenderThread 都可能耗时或等待
性能影响 绘制工作可能占用主线程 绘制任务可交给 RenderThread,但同步点仍可能阻塞主线程

上面介绍的是基本的渲染流程,更详细的 Choreographer 原理可以参考 Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程。


接下来我们重点讲解主线程和渲染线程的深入内容:

  1. 主线程的发展
  2. 主线程的创建
  3. 渲染线程的创建
  4. 主线程和渲染线程的分工

双线程渲染架构的演进

Android 的渲染系统经历了从单线程到双线程的重要演进过程。

单线程时代(Android 5.0 之前)

硬件加速从 Android 3.0(API 11)已支持,早于 Android 5.0 的 HWUI RenderThread。因此“5.0 前”不能等同于全部 CPU 软件绘制;这里仅讨论早期普通 View 路径。

在早期以软件绘制为主的 View 渲染路径中,主线程承担更多 UI 绘制工作:

  • 处理用户输入事件
  • 执行 measure、layout、draw
  • 执行软件绘制并提交 Buffer;其他图形路径可能另有线程。

这种设计的问题:

  1. 响应性差:主线程负载过重,容易出现 ANR
  2. 性能瓶颈:主线程绘制工作更容易与输入和业务消息相互挤占
  3. 帧率不稳定:复杂界面容易导致掉帧

双线程时代(Android 5.0 Lollipop 开始)

Android 5.0 引入了 RenderThread,实现渲染工作的分离:

主线程职责:

  • 处理用户输入和业务逻辑
  • 执行 View 的 measure、layout、draw
  • 构建 DisplayList(绘制指令列表)
  • 与渲染线程同步数据

渲染线程职责:

  • 接收并处理 DisplayList
  • 生成并提交 OpenGL/Vulkan 渲染命令,由 GPU 执行
  • 管理纹理和渲染资源
  • 与 SurfaceFlinger 交互

这种架构带来的优势:

  1. 分工:主线程记录绘制命令,RenderThread 处理后续渲染工作。
  2. 同步边界:主线程在 syncAndDrawFrame 的关键同步点仍会等待,不能把两条线程理解为完全独立。
  3. 并行机会:同步结束后,主线程可以继续处理消息;实际帧节奏取决于 RenderThread、GPU 与 BufferQueue。

主线程的创建过程

普通 Android App 进程通常由 Zygote fork 后专门化,USAP 等路径的准备时机可不同。fork 创建子进程,子进程保留调用线程;进程和主线程不是同一个概念。

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp 中的核心操作可概括为:

1
pid_t pid = fork();

ActivityThread 是运行在应用主线程上的框架对象,不继承 Thread,也不创建这个 Linux 主线程。它的入口准备主 Looper、实例和与系统的连接;H 处理应用生命周期等消息。应用自己的 Handler、Choreographer Handler 和 native FD 工作并不都经 H。

ActivityThread 的创建

下面是 Android 16 普通 Zygote 子进程的一条主路径(省略外围初始化和特殊分支):
ZygoteConnection.handleChildProc → ZygoteInit.zygoteInit → RuntimeInit.applicationInit → ActivityThread.main

Android 16 ZygoteConnection.java

1
2
3
4
5
6
7
8
9
10
11
private Runnable handleChildProc(ZygoteArguments parsedArgs, FileDescriptor pipeFd,
boolean isZygote) {
// ... 省略前置逻辑
if (!isZygote) {
return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
parsedArgs.mDisabledCompatChanges,
parsedArgs.mRemainingArgs, null /* classLoader */);
} else {
return ZygoteInit.childZygoteInit(parsedArgs.mRemainingArgs);
}
}

对于普通 App 进程,上面会走 zygoteInit 分支并最终进入 ActivityThread.main;childZygoteInit 是子 zygote 的分支,不是常规 App 主路径。

Android 16 ActivityThread.java

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
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

// 1. 初始化 Looper、MessageQueue
Looper.prepareMainLooper();

// 2. 初始化 ActivityThread
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();

// 3. 主要是调用 AMS.attachApplicationLocked,同步进程信息,做一些初始化工作
thread.attach(false, startSeq);

// 4. 获取主线程的 Handler,这里是 H ,负责应用生命周期等框架消息,不包揽所有 Handler 消息
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}

Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

// 5. 初始化完成,Looper 开始工作
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

以上是 Android 16 的简化节选。进入 Looper.loop(),主线程就算正式上线,开始分发消息了;正常运行时 main() 不会先返回。Android 17 中,面向 API 37 及以上的 App 使用新的无锁 MessageQueue 实现;Looper、Handler 的使用方式不因此改变,分析队列内部锁竞争时需核对 targetSdkVersion,依赖 MessageQueue 私有字段反射的监控也可能受影响。

ActivityThread 的功能

ActivityThread 的 Handler 负责分发许多应用生命周期消息。组件回调通常从主线程进入,但 Service、Receiver 等后续工作也可能转到其他线程,不能把四大组件的全部执行都算在主线程上。

1
2
3
4
5
6
7
class H extends Handler { // 仅示意相关消息类型,具体数值以目标分支为准
public static final int BIND_APPLICATION = 110; // 应用启动
public static final int CREATE_SERVICE = 114; // 创建Service
public static final int BIND_SERVICE = 121; // 绑定Service
public static final int RECEIVER = 113; // 广播接收
// ... 还有其他四大组件相关的消息类型
}

H 不是进程创建者。Activity 生命周期还涉及 EXECUTE_TRANSACTION 等分发;Service/Receiver 生命周期通常在主线程,但远程 Binder 服务方法、Provider 的 query/insert 等请求可在线程池执行,不能把所有组件方法归到 H。

渲染线程的创建和发展

HWUI RenderThread 从 Android 5.0 起承担一部分原来位于 UI 线程的工作。它是 CPU 线程;更早版本已有 GPU 硬件加速,不能把 RenderThread 的引入当成 Android 首次使用 GPU。

软件绘制

硬件加速在 target API 14 及以上默认启用,也可在 Application/Activity 等层级配置。下面属性放在已有 <application> 标签中;Activity 覆盖或其他图形组件仍可能使用硬件路径:

1
android:hardwareAccelerated="false"

这会影响普通 View 的绘制路径,不保证进程里完全没有 RenderThread,也不同于单个 View 的软件 layer。下图保留为历史渲染等待示例:它明确出现 RenderThread、DrawFrame 和 dequeueBuffer,不能作为关闭硬件加速后的纯软件绘制证据。

img

这张旧 Systrace 与开头的设备 Perfetto 图不是同条件测试。线程 slice 还包含等待,不能据其长度推导硬件加速的加速比或纯 CPU 绘制成本。

硬件加速绘制

普通硬件加速 View 路径在主线程记录或更新 RenderNode 中的 DisplayList,再经 syncAndDrawFrame 进入同步。主线程可能早于后续绘制完成被释放,也可能等到 draw/flush 等分支之后;不能保证等待总是短暂。

渲染线程初始化

Android 16 的 RenderProxy 构造函数会取得 RenderThread::getInstance(),该入口按需创建并启动进程内 HWUI 渲染线程。因此首次 draw 中的 initializeIfNeeded() 主要是 Surface/渲染器状态初始化,不等于线程一定在此时才创建。BLAST 更新又在 ViewRootImpl 的另一条路径中,下面两段不表示连续调用顺序。

android/view/ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
// ViewRootImpl: renderer/Surface initialization call (surrounding conditions omitted)
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);

// Separate Android 16 updateBlastSurfaceIfNeeded excerpt after recreation checks
mBlastBufferQueue = new BLASTBufferQueue(mTag, true /* updateDestinationFrame */);
mBlastBufferQueue.setApplyToken(mBbqApplyToken);
mBlastBufferQueue.update(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y,
mWindowAttributes.format);
// Callbacks, Surface creation/transfer and other state omitted.

这里创建的 BlastBufferQueue 将在后续的渲染过程中发挥关键作用:

  • 在客户端管理 BufferQueue 端点与 SurfaceControl transaction
  • 可合并 buffer 与层属性更新;是否减少调用或等待取决于具体路径
  • 在 Perfetto 中可观察到 QueuedBuffer 指标的变化

进入绘制时,可调用 ThreadedRenderer.draw;以下为 Android 16 方法:

Android 16 ThreadedRenderer.java

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
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();

updateRootDisplayList(view, callbacks);

// register animating rendernodes which started animating prior to renderer
// creation, which is typical for animators started prior to first draw
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
// We don't need this anymore as subsequent calls to
// ViewRootImpl#attachRenderNodeAnimator will go directly to us.
attachInfo.mPendingAnimatingRenderNodes = null;
}

final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();

int syncResult = syncAndDrawFrame(frameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
Log.w("HWUI", "Surface lost, forcing relayout");
// We lost our surface. For a relayout next frame which should give us a new
// surface from WindowManager, which hopefully will work.
attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
attachInfo.mViewRootImpl.requestLayout();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}

上面的 draw 先更新 DisplayList,之后调用 syncAndDrawFrame 进入关键同步阶段,完成 UI Thread 到 RenderThread 的数据对齐。

UI Thread 与 RenderThread 的 DisplayList 同步机制

在 syncAndDrawFrame 这个关键函数中,发生了以下重要的同步操作:

1
2
3
4
// android-16.0.0_r1, RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}

syncAndDrawFrame 的底层并不是完全非阻塞。Android 16 中 DrawFrameTask::drawFrame() 会执行 postAndWait():先把任务投递到 RenderThread 队列,再在条件变量上等待;RenderThread 在合适的同步点会 unblockUiThread()。因此它是“有等待但尽量早释放 UI”的设计,而不是“UI 完全不等待”。

1
2
3
4
5
6
7
8
9
10
11
12
13
// frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(SYSTEM_TIME_MONOTONIC);
postAndWait(); // 关键:UI 线程在这里等待
return mSyncResult;
}

void DrawFrameTask::postAndWait() {
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
mSignal.wait(mLock);
}

run() 中 canUnblockUiThread 为真时在后续 draw 前释放 UI;为假则在后续绘制或 flush/wait 分支之后释放。排队、资源同步和 fence 等待均可能延长 UI 的等待,不是固定时长。参见 Android 16 DrawFrameTask。

具体的同步过程包括:

  1. RenderNode 树的传递:主线程在 draw 过程中构建的 RenderNode 树(包含 DisplayList)会被传递给 RenderThread
  2. 属性同步:View 的变换矩阵、透明度、裁剪区域等属性会一并同步
  3. 资源共享:同步按 HWUI 的 staging/渲染状态与资源生命周期进行,不代表应用可并发修改任意 Path/Paint
  4. 渲染状态传递:当前帧需要的渲染状态信息传递给 RenderThread

这个同步过程是 Android 硬件加速渲染的核心,它实现了 UI Thread 专注于逻辑处理,RenderThread 专注于渲染的分工模式。

渲染线程的核心实现在 libhwui 库里面,其代码位于 frameworks/base/libs/hwui

RenderThread 与 BlastBufferQueue 的交互流程

RenderThread 收到同步的 DisplayList 后继续执行渲染和提交,并通过 BLASTBufferQueue 与 SurfaceFlinger 交换 Buffer:

1
2
3
4
5
6
7
// frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::draw(bool solelyTextureViewUpdates) {
// 1. 计算 dirty 区域并准备本帧
// 2. 通过 mRenderPipeline->draw(...) 生成绘制命令
// 3. 通过 mRenderPipeline->swapBuffers(...) 提交本帧
// 4. 记录 dequeue/queue 时延到 FrameInfo (DequeueBufferDuration/QueueBufferDuration)
}

注:旧版本常见的 dequeueBuffer/queueBuffer/flushTransaction 直观流程仍可作为理解模型,但在本篇核对的 Android 16 中,这些细节分布在 RenderPipeline/ANativeWindow 路径中,CanvasContext::draw() 本身已不是旧代码形态。

BlastBufferQueue 的关键特性:

  1. App 端管理:在这里比较的窗口路径中,BLASTBufferQueue 由客户端创建和管理
  2. 缓冲区获取:生产者和消费者在 App 侧建立,但没有可用 Buffer 时 dequeueBuffer 仍可能等待
  3. 缓冲区轮转:具体等待时间和高刷新率收益要以目标设备的 Trace 为准
  4. 异步提交:通过 transaction 机制提交 buffer 和 acquire fence 给 SurfaceFlinger,GPU 工作可能尚未完成
  5. 支持 unsignaled buffer:配合 SurfaceFlinger 的 unsignaled latch 策略,允许在特定条件下减少端到端延迟

关于 Latching Unsignaled Buffers 的深入探讨

这里讨论的是 App 提交的 Buffer 所带的 acquire fence:它约束 SurfaceFlinger/HWC 何时可以读取 Buffer。present fence 描述的是合成呈现完成时间,不能混用。AOSP 同步文档区分了两者。允许提前 latch 未 signal Buffer 的策略称为「Latching Unsignaled Buffers」。

  • 常规路径:SurfaceFlinger 按 acquire fence 的状态决定何时使用 Buffer;fence signal 前不能读取尚未完成写入的内容。

  • Latch Unsignaled 路径:满足策略条件时,SurfaceFlinger 可以先 latch acquire fence 尚未 signal 的 Buffer;到实际读取内容前仍须等待 fence。是否降低延迟要看设备实现和场景,不能只凭开关状态推断收益。

控制开关与策略 (Android 13+):

一个典型的策略是 AutoSingleLayer。按 AOSP 文档,Android 13 的默认模式只在一帧中单个 layer 更新、且更新仅包含 Buffer、没有同步事务或几何变化时允许提前 latch。多 layer 或其他事务条件不满足时,不走这条路径。文档说明该配置在启动时读取;具体设备还要核对分支与系统属性,不能当成所有版本通用的实时开关。

分析这类 Trace 时,先核对 acquire fence、layer 更新和 latch 时点;present fence 留给判断合成呈现完成时间。

主线程和渲染线程的分工

普通 View 路径由主线程处理输入、业务、布局和绘制命令记录,RenderThread 组织后续 GPU 工作与提交。主线程也可通过 ViewRootImpl/SurfaceControl 更新层属性和事务,不能用“只有 RenderThread 与 SurfaceFlinger 交互”划界。

RenderNode 包含绘制记录与变换、透明度等属性,DisplayList 是其中的命令记录,并非 RenderNode 的同义词。已有记录可复用,某些属性变化无须重新执行 onDraw;重新布局或内容改变仍可能要求更新。绘制优化必须保持先后顺序、裁剪和混合语义,不能任意把所有文字合成一次绘制。

BlastBufferQueue 的工作原理

这里比较旧式 SurfaceFlinger 消费窗口 BufferQueue 的路径与客户端 BLAST 路径;BufferQueue 还有相机、编解码器等用途,不是所有队列都由 SurfaceFlinger 创建。

Android 15 的常见窗口路径中,ViewRootImpl 管理 BLASTBufferQueue;本地消费者取得 buffer,再与 SurfaceControl transaction 关联提交。同步和合并会影响提交时点,有 buffer/fence 背压时仍可等待。以下 counter 解释按 Android 15 BLASTBufferQueue,不能仅凭使用 BLAST 就声称性能改善。

App 端的 QueuedBuffer 指标

  • Perfetto 显示:QueuedBuffer 数值轨道
  • AOSP 定义(BLASTBufferQueue):QueuedBuffer = mNumFrameAvailable + mNumAcquired - mPendingRelease.size()
  • 解释方式:它是一个“综合状态量”,反映可用帧、已获取帧、待 release 的平衡关系,不建议用固定常量偏移去做一刀切换算
  • 实战建议:重点看趋势和持续时间,而不是单个瞬时值

image-20250803170713946

QueuedBuffer 数值变化时机

上升图可见 queueBuffer/onFrameAvailable 与计数更新。Android 15 会更新可用帧等状态后输出组合值;同步、合并和释放分支可能影响变化,不能把每次函数调用机械换算为 +1。

image-20250803170852607

下降图可见 App Binder 线程上的 release 回调与计数下降。该样例支持二者相关,但回调到达不等于 release fence 已完成,也不保证 buffer 可立即重写。BLAST 还可能保留部分 pending release;counter 扣除这些项,不是简单的空闲 buffer 数。

image-20250803171008400

SurfaceFlinger 端的 BufferTX 指标

Android 15 Layer 对应字段是 mPendingBufferTransactions,输出名为 BufferTX - 加 layer 名。收到含有效 buffer 的事务时增加,latch 或 drop 路径减少;只有属性变化的事务不能一概计为 +1,减少也不代表呈现完成。

图中标出的 1、2 是该段观察值;轨道左侧纵轴范围不是通用队列容量或固定最大 3。实际行为还受图层与提交策略影响。

image-20250803171228146

App 端和 SF 端的协作流程

  1. BLAST 接收并按队列/同步状态取得 App 提交的 buffer。
  2. SurfaceControl transaction 关联 buffer、frameNumber 和层属性;与 doFrame token 是不同标识,需显式关联。
  3. SF 更新待处理计数,latch/drop 与后续合成/呈现分别处理。
  4. release callback/fence 与 BLAST 的 pending-release 管理控制回流,不能只看计数下降认定已上屏或已可复用。

关键性能观察点

counter 的趋势可提示进一步调查的位置,但持续高值可能来自有意的流水线、同步事务、GPU/fence 等待或消费延后;低值也可能只是内容不更新。先对齐 frameNumber/token/Layer,再结合 doFrame、DrawFrames、调度状态、fence 和最终呈现判断。单个数值或生产/消费高低不能唯一归因到 App、GPU 或 SF。

性能

同步数据库访问、锁等待等工作会占用主线程的消息分发时间。阻塞会延后输入和帧回调,但卡顿与 ANR 需要分别判断:

  1. 卡顿:按该帧期限和最终呈现分析;UI、RenderThread、GPU 可重叠,不能把墙钟耗时相加与固定 8.33 ms 比较。
  2. ANR:由具体输入/组件超时规则触发,不是任何主线程停顿几秒都必弹框。前后台、系统配置与故障类型影响处置,也不能笼统说“不弹框就 Crash”。详见ANR 系列。

ANR dump 可能晚于现场,不能因为有栈就认定容易定位。用 Perfetto/System Trace 观察等待和调度,再用 Android Studio 的方法采样/插桩工具补充函数证据,注意额外开销。用户遇到卡顿或卡死都不好受,这也是我写这个系列的初衷:先看懂两条线程怎样交接,再去找问题卡在哪一步。

Perfetto 独有的 FrameTimeline 功能

FrameTimeline 提供 App 与 SurfaceFlinger 的预期/实际帧记录,帮助筛选晚帧与延迟,再回到具体线程调查。相对旧 Systrace 查看方式,它提供了可关联的帧记录,也可由 Trace Processor 查询。

需要 Android 12 及以上支持,并实际启用相应数据源。轨道缺失可能是支持范围或采集问题,不能直接当作没有卡顿。

FrameTimeline 的核心概念

根据 Perfetto 官方文档,FrameTimeline 按预期/实际时序等信息分类;分类可含预测误差,不等于每次偏差都有同样的可感知卡顿。对受支持并被采集的帧,可观察两类轨道:

image-20250803172616453

1. Expected Timeline(预期时间线)

  • 作用: 显示系统分配给应用的渲染时间窗口
  • 开始时间: 调度器预测 Choreographer 回调应开始的时间,不是实际获得 CPU 的时刻
  • 含义: 为了避免系统卡顿,应用需要在这个时间范围内完成工作

2. Actual Timeline(实际时间线)

  • 作用: 显示应用完成帧的实际时间(包括 GPU 工作)
  • 开始时间: Choreographer#doFrame 或 AChoreographer_vsyncCallback 开始运行的时间
  • 结束时间: max(GPU 时间, Post 时间),其中 Post 时间是帧被提交到 SurfaceFlinger 的时间

点击 Actual Timeline 的切片,可以看到 App 帧的完成时间、jank 类型和 layer 等详情;要判断何时在屏幕呈现,还需沿帧关联线查看 SurfaceFlinger 侧。

image-20250803172911195

颜色编码系统

FrameTimeline 使用直观的颜色来标识不同的帧状态:

颜色 含义 说明
绿色 正常帧 没有观察到卡顿,理想状态
浅绿色 高延迟状态 帧率稳定但帧呈现延迟,导致输入延迟增加
红色 卡顿帧 当前进程导致的卡顿
黄色 应用无责任卡顿 帧出现卡顿但应用不是原因,SurfaceFlinger 导致的卡顿
蓝色 丢帧 SF 侧表示跳过该帧、选择更新帧;App 侧也可能表示 UI 状态更新未及时到达 RenderThread

点击不同颜色的 Actual Timeline 切片,详情区会显示 jank_type 等归类信息;具体阻塞点仍要回到相关线程与 fence:
image-20250803173304026

上面的表描述常见 UI 分类,颜色不是具体代码根因。上方详情示例来自 com.tencent.mm(token 54160848),与前面示例 App 的 token 8227… 不属于同一现场。图中 App Deadline Missed 与 Buffer Stuffing 同时出现,说明标签可组合;不能强行挑一个当唯一根因。

卡顿类型分析

FrameTimeline 可以识别多种卡顿类型:

应用端卡顿

  • AppDeadlineMissed: 应用运行时间超过预期
  • BufferStuffing:持续提交形成待呈现积压和额外延迟的状态;正常流水线中前帧呈现前准备下一帧,本身不够证明异常

SurfaceFlinger 卡顿

  • SurfaceFlingerCpuDeadlineMissed:SF 对应合成路径晚于期限,不是 ANR 超时或纯 CPU Running 时长
  • SurfaceFlingerGpuDeadlineMissed:CPU 部分按时,但后续 GPU 合成未及时完成
  • DisplayHAL:SF 按时交付后呈现仍错过预期;还需查 HAL 所获预算与实际工作
  • PredictionError: 调度器预测偏差

配置 FrameTimeline

这是要加入完整 Perfetto TraceConfig 的 textproto 数据源片段,不是 YAML,也不是含 buffer/时长的独立采集配置;完整示例见采集篇:

1
2
3
4
5
data_sources {
config {
name: "android.surfaceflinger.frametimeline"
}
}

Perfetto 中 Vsync 信号

在这份 Perfetto Trace 中,VSYNC-app 使用 Counter 类型显示,这与很多人的直觉认知不同:

  • 0 → 1 的变化:表示一个 Vsync 信号
  • 1 → 0 的变化:同样表示一个 Vsync 信号
  • 错误理解:很多人误以为只有变成 1 才是 Vsync 信号

正确的 Vsync 信号识别

下图中 1 、2、3、4 的时间点都是 Vsync 信号到达

image-20250803173809421

关键要点:

  1. 本图的每次数值变化对应一个已记录的 Vsync 标记:无论是 0→1 还是 1→0;这不是所有 Vsync 轨道的通用编码规则
  2. 信号频率:本例连续通知可测量相邻翻转的间隔;不能只由“支持 120 Hz”推出恒定 8.33 ms,实际模式、软件调度和 ARR 都会影响对应关系
  3. 多 App 场景:Counter 可能因为其他 App 的申请而保持活跃状态

分析技巧

SF 的 VSYNC-app counter 不够证明目标 App 收到通知。若采集包含 App 端 onVsync/相关插桩,可结合 PID、时间和 doFrame token 核对;没有名为 FrameDisplayEventReceiver.onVsync 的 slice 也可能只是未插桩或未采集。区分通知、队列分发、回调处理与最终呈现。

参考

  1. https://juejin.im/post/5a9e01c3f265da239d48ce32
  2. http://www.cocoachina.com/articles/35302
  3. https://juejin.im/post/5b7767fef265da43803bdc65
  4. http://gityuan.com/2019/06/15/flutter_ui_draw/
  5. https://developer.android.google.cn/guide/components/processes-and-threads

附件

本文涉及到的 Perfetto 跟踪文件也上传了,各位下载后可以在 Perfetto UI (https://ui.perfetto.dev/) 中打开分析

点此链接下载文章所涉及到的 Perfetto 跟踪文件

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球 :历史介绍(已停止运营)

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 本文目录
  • 系列文章目录
  • 基于 Perfetto 的渲染流程分析
    1. 1. 帧的概念和基本参数
    2. 2. 主线程和渲染线程的工作流程
      1. 2.1. 1. 主线程等待 Vsync 信号
      2. 2.2. 2. Vsync-app 信号传递过程
      3. 2.3. 3. SurfaceFlinger 唤醒 App 主线程
      4. 2.4. 4. 处理输入事件(Input)
      5. 2.5. 5. 处理动画(Animation)
      6. 2.6. 6. 处理 Insets 动画
      7. 2.7. 7. Traversal(测量、布局、绘制准备)
        1. 2.7.1. 7.1 Measure(测量阶段)
        2. 2.7.2. 7.2 Layout(布局阶段)
        3. 2.7.3. 7.3 Draw(绘制阶段)
        4. 2.7.4. ViewRootImpl.performTraversals 核心代码
      8. 2.8. 8. 同步 DisplayList 到渲染线程
      9. 2.9. 9. 渲染线程获取 Buffer
      10. 2.10. 10. 处理渲染指令并 flush 到 GPU
      11. 2.11. 11. 提交 Buffer(可能 unsignaled)
      12. 2.12. 12. 触发 Transaction 到 SurfaceFlinger
    3. 3. 软件绘制 vs 硬件加速
  • 双线程渲染架构的演进
    1. 1. 单线程时代(Android 5.0 之前)
    2. 2. 双线程时代(Android 5.0 Lollipop 开始)
  • 主线程的创建过程
    1. 1. ActivityThread 的创建
    2. 2. ActivityThread 的功能
  • 渲染线程的创建和发展
    1. 1. 软件绘制
    2. 2. 硬件加速绘制
    3. 3. 渲染线程初始化
      1. 3.1. UI Thread 与 RenderThread 的 DisplayList 同步机制
      2. 3.2. RenderThread 与 BlastBufferQueue 的交互流程
    4. 4. 主线程和渲染线程的分工
      1. 4.1. BlastBufferQueue 的工作原理
      2. 4.2. App 端的 QueuedBuffer 指标
      3. 4.3. QueuedBuffer 数值变化时机
      4. 4.4. SurfaceFlinger 端的 BufferTX 指标
      5. 4.5. App 端和 SF 端的协作流程
      6. 4.6. 关键性能观察点
  • 性能
    1. 1. Perfetto 独有的 FrameTimeline 功能
      1. 1.1. FrameTimeline 的核心概念
        1. 1.1.1. 1. Expected Timeline(预期时间线)
        2. 1.1.2. 2. Actual Timeline(实际时间线)
      2. 1.2. 颜色编码系统
      3. 1.3. 卡顿类型分析
        1. 1.3.1. 应用端卡顿
        2. 1.3.2. SurfaceFlinger 卡顿
      4. 1.4. 配置 FrameTimeline
    2. 2. Perfetto 中 Vsync 信号
      1. 2.1. 正确的 Vsync 信号识别
      2. 2.2. 分析技巧
  • 参考
  • 附件
  • 关于我 && 博客