EN

Android Perfetto 系列 7:MainThread 和 RenderThread 解读

Word count: 8.6kReading time: 33 min
2025/08/02
loading

一次滑动卡顿,主线程可能在处理布局,也可能早已把绘制命令交给 RenderThread。这篇用同一份 Perfetto Trace 对照两条线程、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/tree/master/Android_Perfetto/demo_app_aosp_scroll.perfetto-trace ,需要的可以自取。

版本说明:本文以 Android 17(API 37)为当前技术参照,Android 12 及以上设备可按同一分析思路核对。文中的源码摘录按写作时的 AOSP 主线整理,截图来自附件中的设备 Trace;示意摘录不等于 Android 17 发布分支的逐行源码,具体签名和轨道名称以目标分支及设备 Trace 为准。

基于 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:渲染线程,执行 GPU 渲染命令,与 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 信号传递过程

  • Perfetto trace: vsync-app 相关事件,SurfaceFlinger app 线程活动
  • 流程说明: 当硬件产生 Vsync 信号时,会先传给 SurfaceFlinger。SurfaceFlinger 的 app 线程被唤醒,负责管理和分发 Vsync 信号给需要渲染的应用程序。这个中间层设计允许系统级的 Vsync 调度和优化

重要说明:

  • Vsync-app 是按需申请的:只有 App 主动请求时才会收到 vsync-app 信号,不申请就没有
  • 多 App 共享机制:同时可能有多个 App 申请 vsync-app 信号
  • 信号归属问题:SurfaceFlinger 中的 vsync-app 信号可能是其他 App 申请的,当前分析的 App 如果没有申请,就不会有帧输出,这是正常现象

3. SurfaceFlinger 唤醒 App 主线程

  • Perfetto trace: FrameDisplayEventReceiver.onVsync
  • 流程说明: SurfaceFlinger 通过 FrameDisplayEventReceiver 机制将 Vsync 信号发送给已注册的 App。App 的 Choreographer 接收到信号后开始启动一帧绘制流程

4. 处理输入事件(Input)

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

5. 处理动画(Animation)

  • Perfetto trace: Animation 块
  • 流程说明: 仅在有动画需要更新时才执行,更新动画状态和当前帧的动画值
  • 触发条件:
  • 有 Animation 回调:惯性滑动阶段、属性动画运行时、列表 item 创建和内容变化、页面转场动画等
  • 无 Animation 回调:界面静止状态、纯 Input 交互阶段(无动画效果时)
  • 注意: Animation 回调同样由前一帧 post 的回调决定当前帧是否执行

6. 处理 Insets 动画

  • Perfetto trace: Insets Animation 块
  • 流程说明: 仅在有窗口插入变化时才执行,处理窗口边界动画
  • 触发条件:
  • 有 Insets Animation 回调:键盘弹出/收起、状态栏显示/隐藏、导航栏变化等
  • 无 Insets Animation 回调:窗口边界稳定状态,大部分普通交互场景

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

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

7.1 Measure(测量阶段)

  • 作用: 确定每个 View 的尺寸大小
  • 过程: 从根 View 开始,递归测量所有子 View 的宽高
  • 关键概念:
  • MeasureSpec:封装了父容器对子 View 的尺寸要求(EXACTLY、AT_MOST、UNSPECIFIED)
  • onMeasure():每个 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 的内容绘制到画布上
  • 现代实现: 不直接绘制像素,而是构建 DisplayList(绘制指令列表)
  • 关键流程:
  • 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
// 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()
performDraw(mActiveSurfaceSyncGroup);
}

注:最新 AOSP 中 performTraversals 的分支远比示例复杂,涉及 relayout、surface 变化、同步组、取消重绘、窗口可见性等;这里仅保留与 Measure/Layout/Draw 直接相关的主干逻辑。

三阶段的执行条件:

  • 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: 常见可观察 DequeueBufferDuration / QueueBufferDuration 相关信息(具体 tag 因版本和厂商实现不同)
  • 流程说明: 渲染线程在绘制提交阶段会通过 ANativeWindow/RenderPipeline 路径完成 buffer 申请与交换。是否等待、等待多久,直接影响这一帧是否赶上 deadline。

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

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

11. 提交 Buffer(可能 unsignaled)

  • Perfetto trace: queueBuffer(可观察 acquireFence 状态)
  • 流程说明: 帧提交会经过 BufferQueue/BLAST 机制进入 SurfaceFlinger,某些场景下会出现 unsignaled fence 相关行为(由系统策略决定是否允许提前 latch),目标是降低端到端延迟。

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:

方面 软件绘制 硬件加速
绘制线程 主线程 RenderThread
绘制引擎 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 之前)

在早期以软件绘制为主的 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 的进程是基于 Linux 的,其管理也是基于 Linux 的进程管理机制,所以其创建也是调用了 fork 函数

frameworks/base/core/jni/com_android_internal_os_Zygote.cpp

1
pid_t pid = fork();

Fork 出来的进程,我们这里可以把他看做主线程,但是这个线程还没有和 Android 进行连接,所以无法处理 Android App 的 Message ;由于 Android App 线程运行基于消息机制 ,那么这个 Fork 出来的主线程需要和 Android 的 Message 消息绑定,才能处理 Android App 的各种 Message

这里就引入了 ActivityThread。更准确地说,把它理解成 ProcessThread 会更贴切一些。ActivityThread 负责把 Fork 出来的进程和 App 的 Message 机制连接起来,它会初始化 MessageQueue、Looper、Handler 等关键对象,而它自己的 Handler 又负责处理大部分主线程消息。所以我们习惯上会把 ActivityThread 叫成主线程,但更准确地说,它是主线程上的核心逻辑单元。

ActivityThread 的创建

App 进程 fork 出来之后,主路径是:
ZygoteConnection.handleChildProc → ZygoteInit.zygoteInit → RuntimeInit.applicationInit → ActivityThread.main

com/android/internal/os/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/app/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 ,基本上 App 的 Message 都会在这个 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");
}

注释里面都很清楚,这里就不详细说了,main 函数处理完成之后,主线程就算是正式上线开始工作。Android 17 中,面向 API 37 及以上的 App 使用新的无锁 MessageQueue 实现;Looper、Handler 的使用方式不因此改变,分析队列内部锁竞争时则要核对目标 App 的 targetSdkVersion。

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; // 广播接收
// ... 还有其他四大组件相关的消息类型
}

从这些 Message 类型就能看出,进程创建、Activity 启动、Service 管理、Receiver 管理、Provider 管理这些流程都会在这里处理,然后进入具体的 handleXXX

渲染线程的创建和发展

主线程讲完了我们来讲渲染线程,渲染线程也就是 RenderThread ,最初的 Android 版本里面是没有渲染线程的,渲染工作都是在主线程完成,使用的也都是 CPU ,调用的是 libSkia 这个库,RenderThread 是在 Android Lollipop 中新加入的组件,负责承担一部分之前主线程的渲染工作,减轻主线程的负担

软件绘制

我们一般提到的硬件加速,指的就是 GPU 加速,这里可以理解为用 RenderThread 调用 GPU 来进行渲染加速 。 硬件加速在目前的 Android 中是默认开启的, 所以如果我们什么都不设置,那么我们的进程默认都会有主线程和渲染线程(有可见的内容)。我们如果在 App 的 AndroidManifest 里面,在 Application 标签里面加一个

1
android:hardwareAccelerated="false"

关闭应用的硬件加速后,普通 View 绘制路径会更多依赖主线程上的软件绘制;进程里是否还有 RenderThread 要看其他 Surface 或组件,不能由这个开关单独判断。下面是旧版 Systrace 中的软件绘制示例:

img

和这篇文章开头开启硬件加速的 Perfetto 图对比,主线程由于要承担渲染工作,执行时间会明显变长,也更容易出现卡顿;同时帧与帧之间的空闲间隔也会变短,使得其他 Message 的执行时间被压缩。在 Perfetto 中,这种差异可以通过线程活动的时间长度和密集程度直接观察到。

硬件加速绘制

正常情况下,硬件加速是开启的,主线程的 draw 主要是在构建/更新 DisplayList(RenderNode 树),然后通过 syncAndDrawFrame 同步给 RenderThread。UI 线程会在同步关键路径上短暂等待,随后尽快返回继续处理主线程消息;RenderThread 再继续执行后续渲染与提交。

渲染线程初始化

硬件加速的 ViewRootImpl 在需要绘制内容时,会检查 ThreadedRenderer 是否已经初始化。具体触发时机随窗口和版本变化,下面从 initializeIfNeeded 的调用点继续看。

android/view/ViewRootImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
// 渲染线程初始化
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);

// 初始化 BlastBufferQueue - App 端缓冲区管理器
if (mBlastBufferQueue == null) {
mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y,
mWindowAttributes.format);
mBlastBufferQueue.update(mSurfaceControl,
mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
}

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

  • 为 RenderThread 提供高效的 Buffer 管理
  • 支持批量 Transaction 提交,减少与 SurfaceFlinger 的交互开销
  • 在 Perfetto 中可观察到 QueuedBuffer 指标的变化

后续直接调用 draw

android/view/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
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);

void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
choreographer.mFrameInfo.markDrawStart();

// 更新 RootDisplayList,构建 RenderNode 树
updateRootDisplayList(view, callbacks);

// 处理动画 RenderNode
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();
attachInfo.mPendingAnimatingRenderNodes = null;
}

// 同步并绘制帧,这里会触发 RenderThread 工作
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);

// 处理各种结果状态
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
setEnabled(false);
attachInfo.mViewRootImpl.mSurface.release();
attachInfo.mViewRootImpl.invalidate();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}

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

UI Thread 与 RenderThread 的 DisplayList 同步机制

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

1
2
3
4
5
6
// frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
// 1. 将 UI Thread 的 DisplayList 同步到 RenderThread
// 这里会把主线程构建的 RenderNode 树传递给渲染线程
return mDrawFrameTask.drawFrame();
}

syncAndDrawFrame 的底层并不是完全非阻塞。最新 AOSP 中 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);
}

具体的同步过程包括:

  1. RenderNode 树的传递:主线程在 draw 过程中构建的 RenderNode 树(包含 DisplayList)会被传递给 RenderThread
  2. 属性同步:View 的变换矩阵、透明度、裁剪区域等属性会一并同步
  3. 资源共享:纹理、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 直观流程仍可作为理解模型,但在最新主干里这些细节被收敛在 RenderPipeline/ANativeWindow 路径中,CanvasContext::draw() 本身已不是旧代码形态。

BlastBufferQueue 的关键特性:

  1. App 端管理:不同于传统的 BufferQueue 由 SurfaceFlinger 创建,BlastBufferQueue 是由 App 端创建和管理
  2. 缓冲区获取:生产者和消费者在 App 侧建立,但没有可用 Buffer 时 dequeueBuffer 仍可能等待
  3. 缓冲区轮转:具体等待时间和高刷新率收益要以目标设备的 Trace 为准
  4. 异步提交:通过 transaction 机制异步地将完成的帧提交给 SurfaceFlinger
  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 或其他事务条件不满足时,不走这条路径。具体设备还要核对 LatchUnsignaledConfig 和系统属性。

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

主线程和渲染线程的分工

主线程负责处理进程 Message、处理 Input 事件、处理 Animation 逻辑、处理 Measure、Layout、Draw ,更新 DisplayList ,但是不涉及与 SurfaceFlinger 直接打交道;渲染线程负责渲染相关的工作,包括与 BlastBufferQueue 的交互、GPU 渲染命令的提交,以及与 SurfaceFlinger 的最终交互。GPU 命令由 GPU 执行。

当启动硬件加速后,在 Measure、Layout、Draw 的 Draw 这个环节,Android 使用 DisplayList 进行绘制而非直接使用 CPU 绘制每一帧。DisplayList 是一系列绘制操作的记录,抽象为 RenderNode 类,这样间接的进行绘制操作的优点如下

  1. DisplayList 可以按需多次绘制而无须同业务逻辑交互
  2. 特定的绘制操作(如 translation、scale 等)可以作用于整个 DisplayList 而无须重新分发绘制操作
  3. 当知晓了所有绘制操作后,可以针对其进行优化:例如,所有的文本可以一起进行绘制一次
  4. 可以将对 DisplayList 的处理转移至另一个线程(也就是 RenderThread)
  5. 主线程在关键同步点解除等待后可以处理其他消息,但是否再次阻塞要看后续任务。
  6. BLASTBufferQueue 把 Buffer 与 SurfaceControl transaction 关联;是否减少等待需看具体 Trace。

BlastBufferQueue 的工作原理

BlastBufferQueue 是现代 Android 渲染架构中的关键组件,它改变了传统的缓冲区管理方式:

传统 BufferQueue vs BlastBufferQueue:

  1. 创建主体不同:
  • 传统 BufferQueue:由 SurfaceFlinger 创建和管理
  • BlastBufferQueue:由 App 端(ViewRootImpl)创建和管理
  1. 缓冲区获取机制:
  • 传统方式:没有可用 Buffer 时,dequeueBuffer 可能阻塞
  • BlastBufferQueue:App 端建立本地 BufferQueue,但没有可用 Buffer 时仍可能阻塞
  1. 提交机制:
  • 传统方式:通过 queueBuffer 直接提交给 SurfaceFlinger
  • BlastBufferQueue:通过 transaction 机制批量提交,减少 Binder 调用开销

在 Perfetto 中观察 BlastBufferQueue:

在 Perfetto 跟踪中,BlastBufferQueue 的状态通过以下关键指标显示:

App 端的 QueuedBuffer 指标

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

image-20250803170713946

QueuedBuffer 数值变化时机

QueuedBuffer +1 的时机:

  • 常见触发:生产侧有新 buffer 到达 BLAST,并进入可处理状态
  • Perfetto 表现:QueuedBuffer 轨道上升
  • 含义:App 端到 SF 端之间的“待处理帧压力”增加

image-20250803170852607

QueuedBuffer -1 的时机:

  • 触发条件:收到 SurfaceFlinger 的 releaseBufferCallback
  • Perfetto 表现:可观察到 releaseBuffer 相关事件
  • 含义:某个 buffer 完成消费或被处理后释放,队列压力下降

image-20250803171008400

SurfaceFlinger 端的 BufferTX 指标

  • Perfetto 显示:SurfaceFlinger 进程中的 BufferTX 数值轨道
  • AOSP 定义(Layer):该值对应每层 mPendingBuffers 的跟踪;buffer 到达 server 侧会增加,buffer 被 latch 或 drop 会减少
  • 触发条件:与 transaction 和 buffer 生命周期共同相关,不建议简单等价为“收到 transaction 就 +1”
  • 注意:它不是通用“固定最大 3”的指标,受图层类型、生产消费节奏和系统策略影响

image-20250803171228146

App 端和 SF 端的协作流程

  1. App 端:RenderThread 提交新帧后,QueuedBuffer 常见上升
  2. 跨进程:BLAST/SurfaceControl transaction 关联 frameNumber 并进入 SF 侧处理
  3. SF 端:BufferTX 随 pending buffer 变化(到达增、latch/drop 减)
  4. 回流:releaseBufferCallback 到 App 端后,QueuedBuffer 下降

关键性能观察点

在分析性能时,重点关注:

  • App 端 QueuedBuffer 趋势:连续上升且长时间不回落,通常表示生产/消费节奏失衡;结合主线程 performTraversals 与 RenderThread DrawFrames 判断瓶颈在 App 还是 SF/GPU 侧
  • SurfaceFlinger 端 BufferTX 趋势:长期偏高常见于消费侧压力大;长期偏低且 App 又频繁 miss deadline,往往是生产侧供给不足

性能

如果主线程需要处理所有任务,则执行耗时较长的操作(例如,网络访问或数据库查询)将会阻塞整个界面线程。一旦被阻塞,线程将无法分派任何事件,包括绘图事件。主线程执行超时通常会带来两个问题

  1. 卡顿:120 Hz 的刷新间隔约 8.33 ms,但主线程、RenderThread 与 GPU 是流水线工作,不能把两条线程的耗时简单相加后与 8.33 ms 比较。应按 FrameTimeline 的截止时间和最终呈现结果判断。
  2. 卡死:如果界面线程被阻塞超过几秒钟时间(根据组件不同, 这里的阈值也不同),用户会看到 “应用无响应“ (ANR) 对话框(部分厂商屏蔽了这个弹框,会直接 Crash 到桌面)

对于用户来说,这两个情况都是用户不愿意看到的,所以对于 App 开发者来说,两个问题是发版本之前必须要解决的,ANR 这个由于有详细的调用栈,所以相对来说比较好定位;但是间歇性卡顿这个,可能就需要使用工具来进行分析了:Perfetto + Trace View (Android Studio 已经集成),所以理解主线程和渲染线程的关系和他们的工作原理是非常重要的,这也是本系列的一个初衷。

Perfetto 独有的 FrameTimeline 功能

FrameTimeline 把 App 与 SurfaceFlinger 的预期和实际帧时间线放在一起,便于先定位晚帧,再回到线程和 Buffer 轨道查原因。

注意: FrameTimeline 需要 Android 12 或更高版本支持

FrameTimeline 的核心概念

根据 Perfetto 官方文档,当帧在屏幕上的实际呈现时间与调度器预期的呈现时间不匹配时,就会产生卡顿。FrameTimeline 为每个有帧在屏幕上显示的应用添加了两个新的轨道:

image-20250803172616453

1. Expected Timeline(预期时间线)

  • 作用: 显示系统分配给应用的渲染时间窗口
  • 开始时间: Choreographer 回调被调度运行的时间
  • 含义: 为了避免系统卡顿,应用需要在这个时间范围内完成工作

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

卡顿类型分析

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

应用端卡顿

  • AppDeadlineMissed: 应用运行时间超过预期
  • BufferStuffing: 应用在前一帧呈现前就发送新帧,导致 Buffer 队列堆积

SurfaceFlinger 卡顿

  • SurfaceFlingerCpuDeadlineMissed: SurfaceFlinger 主线程超时
  • SurfaceFlingerGpuDeadlineMissed: GPU 合成时间超时
  • DisplayHAL: HAL 层呈现延迟
  • PredictionError: 调度器预测偏差

配置 FrameTimeline

在 Perfetto 配置中启用 FrameTimeline:

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. 信号频率:120Hz 设备上约每 8.33ms 会有一次变化(实际可能因系统调度略有差异,这里指的是连续出帧场景)
  3. 多 App 场景:Counter 可能因为其他 App 的申请而保持活跃状态

分析技巧

判断 App 是否接收到 Vsync:

  • 正确方法:查看 App 进程中是否有对应的 FrameDisplayEventReceiver.onVsync 事件
  • 错误方法:仅凭 SurfaceFlinger 中的 vsync-app counter 变化来判断

参考

  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. 分析技巧
  • 参考
  • 附件
  • 关于我 && 博客