EN

Android Systrace 基础知识 - SurfaceFlinger 解读

字数统计: 3.8k阅读时长: 14 min
2020/02/14
loading

本文是 Systrace 系列文章的第五篇,主要是对 SurfaceFlinger 的工作流程进行简单介绍,介绍了 SurfaceFlinger 中几个比较重要的线程,包括 Vsync 信号的解读、应用的 Buffer 展示、卡顿判定等,由于 Vsync 这一块在 Systrace 基础知识 - Vsync 解读 和 Android 基于 Choreographer 的渲染机制详解 这两篇文章里面已经介绍过,这里就不再做详细的讲解了。

本系列的目的是通过 Systrace 这个工具,从另外一个角度来看待 Android 系统整体的运行,同时也从另外一个角度来对 Framework 进行学习。也许你看了很多讲 Framework 的文章,但是总是记不住代码,或者不清楚其运行的流程,也许从 Systrace 这个图形化的角度,你可以理解的更深入一些。

本文目录

系列文章目录

  1. Systrace 简介
  2. Systrace 基础知识 - Systrace 预备知识
  3. Systrace 基础知识 - Why 60 fps ?
  4. Systrace 基础知识 - SystemServer 解读
  5. Systrace 基础知识 - SurfaceFlinger 解读
  6. Systrace 基础知识 - Input 解读
  7. Systrace 基础知识 - Vsync 解读
  8. Systrace 基础知识 - Vsync-App :基于 Choreographer 的渲染机制详解
  9. Systrace 基础知识 - MainThread 和 RenderThread 解读
  10. Systrace 基础知识 - Binder 和锁竞争解读
  11. Systrace 基础知识 - Triple Buffer 解读
  12. Systrace 基础知识 - CPU Info 解读
  13. Systrace 流畅性实战 1 :了解卡顿原理
  14. Systrace 流畅性实战 2 :案例分析: MIUI 桌面滑动卡顿分析
  15. Systrace 流畅性实战 3 :卡顿分析过程中的一些疑问
  16. Systrace 响应速度实战 1 :了解响应速度原理
  17. Systrace 响应速度实战 2 :响应速度实战分析-以启动速度为例
  18. Systrace 响应速度实战 3 :响应速度延伸知识
  19. Systrace 线程 CPU 运行状态分析技巧 - Runnable 篇
  20. Systrace 线程 CPU 运行状态分析技巧 - Running 篇
  21. Systrace 线程 CPU 运行状态分析技巧 - Sleep 和 Uninterruptible Sleep 篇

正文

版本说明(2026-10-14 核查):本文保留 2020 年的 Systrace 图和分析顺序。下方两段完整函数对应 AOSP android-10.0.0_r1;旧 Binder/BufferQueue 路径、HIDL 轨道名不宜直接套用到新系统。文中另补 android-16.0.0_r1 的 BLAST、commit/composite 路径,以及 Android 12(API 31)起的 FrameTimeline 分析入口。截图未提供系统构建号,不能仅凭相似函数名认定其精确源码版本。

这篇 SurfaceFlinger 文章需要和 Perfetto 8:Vsync 机制与性能分析 对照阅读。旧 Systrace 里主要看 SurfaceFlinger 主线程、BufferQueue、HWC 和 App Buffer 的对应关系;Perfetto 里仍然看这些对象,但要结合 FrameTimeline、Buffer、Display/HWC 轨道和 CPU 调度。

先定位问题帧及预期呈现时间,再检查 App 提交、GPU 就绪、SurfaceFlinger 锁存(latch)、合成和显示提交。超过 16.6 ms、出现长 Slice,都不能单独证明某一环节导致掉帧。

官方 SurfaceFlinger 文档介绍了基本职责:接收图层内容和状态,组织合成,并把结果交给显示链路。读下面的旧图时注意:

  1. 状态栏、导航栏、App 是常见例子,不是固定的三个图层。一个 App 可有多个 Surface,窗口和图层也不是简单的一一对应;窗口状态可以通过事务协调更新。
  2. 屏幕刷新率用 Hz,内容帧率用 fps。60 Hz 只是旧例子,不能把 58~62 或固定 16.6 ms 当作所有设备的范围和预算。App/SF 的调度还涉及预测和相位,不能把每个调度回调都理解成当下的硬件中断。
  3. 没有新 Buffer 时,图层可以继续显示旧内容;这不同于“从未提交过 Buffer”。纯色等非 Buffer 图层也不适用“没有 Buffer 就忽略”的简单规则。
  4. App 提交、GPU 完成和显示呈现是不同事件。各帧可以流水重叠;生产速度超过消费速度可能造成背压、额外延迟或丢弃,具体取决于队列模式和调度策略。

这张示意图同时画了 GPU 合成与直接交给显示硬件的路径。图中重复出现的 GPU 不是多个独立 GPU,也不能据此认定每层都经过 SurfaceFlinger 的 GPU 合成。接下来按 App、BufferQueue、SurfaceFlinger、HWComposer 四部分读图;这是依赖顺序,不是所有线程和硬件严格串行的时间线。

App 部分

关于 App 部分,可以先读 MainThread 和 RenderThread 解读。本文截图使用传统 View/HWUI 渲染路径:

App 通过 Surface 等生产者接口提交图像 Buffer;不是把 drawcall 存进图像 Buffer 再交给 SurfaceFlinger 执行。主线程记录绘制操作、RenderThread 提交 GPU 工作、GPU 写入图像内容,是不同阶段。图中第 8 步的“绘制好”应理解为提交了 Buffer 和同步依赖,不保证 GPU 已完成。

沿着图中的三个交互点看:Vsync 回调、dequeueBuffer、queueBuffer。

Vsync 信号的接收和处理

Choreographer 渲染机制解释了帧回调的请求和执行。收到回调后,应用开始处理这一帧的输入、动画和遍历等工作;回调时间戳与线程实际得到 CPU 的时间可能不同。

图中绿色 F 标记是旧查看器的帧分析标记,不能直接当作“这帧已按时上屏”的证据;仍需关联对应 Buffer 与显示帧。

RenderThread 的 dequeueBuffer

dequeueBuffer 为生产者取得可写的 Buffer 槽位及同步信息。旧图中,BufferQueue 的消费端位于 SurfaceFlinger,App 经 Binder 请求;它可能复用已分配的 Buffer,不是每次分配或复制整张图像。

App 端的 Systrace 如下所示
-w1249

SurfaceFlinger 端的 Systrace 如下所示
-w826

图中的 SF Binder 线程处理出队请求;这与 SF 主线程执行合成是两件事。出队或绘制前还可能等待槽位、release fence 等条件,要结合线程状态判断等待原因。

新路径补充:在 Android 16 BLASTBufferQueue 源码中,BLAST 创建本地 BufferQueue,取得排队的 Buffer,再通过 SurfaceControl Transaction 把 Buffer、fence 和图层状态交给 SurfaceFlinger。采用这条路径时,不能再要求每次 dequeueBuffer 都对应一次 App→SF 的 Binder 调用;也不能把所有 BufferQueue 都画在 SF 进程内。

RenderThread 的 queueBuffer

queueBuffer 把生产者持有的 Buffer 提交给消费者,并携带 fence、时间戳等元数据。这里的 eglSwapBuffersWithDamageKHR → queueBuffer 是图中的 OpenGL ES 路径,不是 Vulkan、软件 Canvas 等所有生产方式的共同调用链。

App 端的 Systrace 如下所示
-w1165

图中提交后还有 waiting for GPU completion 轨道。同步机制允许 GPU 工作尚未完成时先传递 Buffer 和 fence;queueBuffer 返回不能证明 GPU 完成,更不能证明已显示。等待区间也不等于 GPU 始终在执行。

SurfaceFlinger 端的 Systrace 如下所示
-w1295

图中对应 Layer 的计数表示待消费队列项,入队增加、消费取走后减少;不是全部已分配 Buffer 数,也不是已经 GPU 就绪的 Buffer 数。左侧一次取走与右侧后续入队属于先后不同事件,不能把右侧新提交解释为已被左侧提前消费。

前面几步对应这张图的生产者一侧:
-w410

BufferQueue 部分

Triple Buffer 解读介绍了队列和槽位状态。BufferQueue 连接生产者与消费者;一个进程可使用多个队列,两端也可以同进程。它不是“每个有界面的进程一个队列”。

旧 SF 消费路径中,App 是生产者,SF 是消费者;BLAST 等路径的消费者位置不同,但这四步的职责仍可用于理解:

  1. dequeueBuffer():生产者取得可用槽位,按尺寸、格式和 usage 等要求取得或复用 Buffer,写入前满足返回的同步依赖。
  2. queueBuffer():生产者提交 Buffer 和 acquire fence;提交不等于异步写入已经完成。
  3. acquireBuffer():消费者取得排队项;读取内容必须满足 acquire fence,或把该依赖继续传给后续硬件。
  4. releaseBuffer():消费者归还槽位并附带 release fence;生产者再次写入还要等此前读取完成。

这里传递的是 Buffer 的引用/句柄及同步信息,不是四次像素拷贝;队列是否替换旧项、允许同时持有多少 Buffer,也不是这张图能决定的。

SurfaceFlinger 部分

工作流程

在旧路径里,SF 按调度处理事务和候选 Buffer,决定是否需要一次合成;没有新内容不必每个 Vsync 都重复工作。图中可见 handlePageFlip → latchBuffer,随后进入 handleMessageRefresh:

-w1296

下面代码对照 AOSP android-10.0.0_r1 的 SurfaceFlinger.cpp。onMessageReceived 只保留相关分支,省略了背压、显示模式切换、refreshNeeded 判断和 signalRefresh 等逻辑,不可作为完整实现复制;其后两个函数保留该 tag 的实现。

  1. MessageQueue::INVALIDATE:处理事务、尝试更新图层,并按条件安排 REFRESH。
  2. MessageQueue::REFRESH:进入 handleMessageRefresh。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
void SurfaceFlinger::onMessageReceived(int32_t what) NO_THREAD_SAFETY_ANALYSIS {
ATRACE_CALL();
switch (what) {
case MessageQueue::INVALIDATE: {
// ... omitted ...
bool refreshNeeded = handleMessageTransaction();
refreshNeeded |= handleMessageInvalidate();
// ... omitted ...
break;
}
case MessageQueue::REFRESH: {
handleMessageRefresh();
break;
}
}
}

//handleMessageInvalidate 实现如下
bool SurfaceFlinger::handleMessageInvalidate() {
ATRACE_CALL();
bool refreshNeeded = handlePageFlip();

if (mVisibleRegionsDirty) {
computeLayerBounds();
if (mTracingEnabled) {
mTracing.notify("visibleRegionsDirty");
}
}

for (auto& layer : mLayersPendingRefresh) {
Region visibleReg;
visibleReg.set(layer->getScreenBounds());
invalidateLayerStack(layer, visibleReg);
}
mLayersPendingRefresh.clear();
return refreshNeeded;
}

//handleMessageRefresh 实现如下, SurfaceFlinger 的大部分工作都是在handleMessageRefresh 中发起的
void SurfaceFlinger::handleMessageRefresh() {
ATRACE_CALL();

mRefreshPending = false;

const bool repaintEverything = mRepaintEverything.exchange(false);
preComposition();
rebuildLayerStacks();
calculateWorkingSet();
for (const auto& [token, display] : mDisplays) {
beginFrame(display);
prepareFrame(display);
doDebugFlashRegions(display, repaintEverything);
doComposition(display, repaintEverything);
}

logLayerStats();

postFrame();
postComposition();

mHadClientComposition = false;
mHadDeviceComposition = false;
for (const auto& [token, displayDevice] : mDisplays) {
auto display = displayDevice->getCompositionDisplay();
const auto displayId = display->getId();
mHadClientComposition =
mHadClientComposition || getHwComposer().hasClientComposition(displayId);
mHadDeviceComposition =
mHadDeviceComposition || getHwComposer().hasDeviceComposition(displayId);
}

mVsyncModulator.onRefreshed(mHadClientComposition);

mLayersWithQueuedFrames.clear();
}

handleMessageRefresh 可以按职责阅读:

  1. 准备:preComposition()、rebuildLayerStacks()、calculateWorkingSet()。
  2. 按显示处理:beginFrame()、prepareFrame()、调试闪烁处理和 doComposition()。
  3. 后续处理:logLayerStats()、postFrame()、postComposition(),以及合成类型、Vsync 调制和队列状态的更新。

最后一步包含提交后的同步和统计工作,函数返回不等于面板呈现已经完成。也不要把这些 CPU 方法的长度直接当作 GPU 时间。

Android 16 对照入口:在 android-16.0.0_r1 中,commit() 更新图层快照、处理事务并决定是否需要 composite;composite() 准备合成参数,再调用 CompositionEngine::present。后者对各 output 执行 prepare/present,部分 HWC present 调用还可转交其他线程并等待结果。新系统应从这些入口、实际轨道和设备源码追踪,不再要求出现旧版两个 Message 名称。这里仅是阅读入口,不包含全部显示模式、背压和多显示器分支。

显示系统的细节非常多,我这里就不一一展开了。如果只是想熟悉流程,先把 SurfaceFlinger 的主要工作逻辑看明白就好;如果工作就在这一块,再沿着对应版本的源码把这些分支和依赖逐个弄清楚。

掉帧

判断掉帧,先确认哪一层的哪一帧没有达到预期呈现时间,再沿依赖向前找原因:

  1. 核对刷新模式、目标帧率和帧身份。静止页面或低于屏幕刷新率的内容,没有每个 Vsync 都合成是正常情况。
  2. 有待更新内容却未及时合成时,检查 SF 调度、事务、锁存条件及此前帧的背压;再沿 CPU 调度、Binder、GPU/HWC fence 查明阻塞,而不是只看长 Slice。
  3. SF 合成了,也可能沿用目标 App 的旧 Buffer,或只更新了其他图层。队列计数为 0 可能是刚被取走,也可能尚未提交;它不能单独证明 App 出错,App 的延迟也可能来自下游背压。
  4. Android 12(API 31)起,在采集数据支持时用 FrameTimeline关联 App 的 SurfaceFrame 和 SF 的 DisplayFrame,比较 Expected/Actual、present type 和 jank type,再检查具体线程/硬件证据。Actual Timeline 不是 CPU Running 时间;缺失轨道也不能当作“没有掉帧”。

旧 Systrace 的对应分析见 Triple Buffer:掉帧判断。单凭本文裁剪截图无法重新统计完整掉帧率。

HWComposer 部分

Hardware Composer HAL根据图层状态和设备能力协商合成方案:

  1. Client composition:SurfaceFlinger 经 RenderEngine 合成相应图层,把 client target 交给 HWC。Android 16 RenderEngine支持 GL/Vulkan 后端选择,不能把 client 一律写成 GLES。
  2. Device composition:交由设备 HWC 实现处理。Overlay 是常见方式;可用平面、缩放、旋转、混合、格式等限制取决于硬件和驱动,不存在 Android 4.4 起统一“四个平面”的保证。
  3. 硬件合成可能减少 GPU 工作;静态内容的复用、内存带宽和混合成本也会影响策略。因此 client composition 本身不是性能故障,层数多也不能直接换算成掉帧数。

接口版本:下图是历史 android.hardware.graphics.composer@2.1-service 的 HIDL 路径。Android 13 起提供 AIDL composer3,旧 HIDL 2.1~2.4 已被官方标为 deprecated;但 Android 13 当时并未要求所有厂商立即切换。分析设备时仍需确认实际 HAL。

-w1149

图中 SF 的 executeCommands 经 Binder 到 HWC,下面可见 Present/Commit/ATOMIC 等区间。选中的 642 μs 是图上的时间范围,不是已测得的 GPU 执行时间,也不是最终显示完成时间。HWC 返回后,硬件仍可等待 fence 或继续处理。

这对应原流程图的后半段:
-w563

分析时要分清:acquire fence 表示读取内容前的依赖,release fence 控制何时可安全复用旧 Buffer,display present fence 提供呈现完成的同步信息。比如 NDK API 29 的事务统计分别提供 present 和 previous-release fence;不支持 present fence 的设备可返回无效值,不能把它解释成零耗时。HAL 方法返回或 SF latch 时间都不等于 present fence 的信号时间。

HWC 相关等待可能影响后续帧,参见 Android 中的卡顿丢帧原因概述 - 系统篇。进一步阅读 Android P 图形显示系统(一)硬件合成 HWC2及其系列时,按 Android 9/P 的历史背景对照,不把旧接口名当作当前实现。

参考文章

  1. Android P 图形显示系统(一)硬件合成 HWC2(历史资料)
  2. Android P 图形显示系统(历史系列)
  3. SurfaceFlinger and WindowManager
  4. SurfaceFlinger 分析文档(历史资料)

关于我 && 博客

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

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

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

微信扫一扫

CATALOG
  1. 1. 本文目录
  • 系列文章目录
  • 正文
    1. 1. App 部分
      1. 1.1. Vsync 信号的接收和处理
      2. 1.2. RenderThread 的 dequeueBuffer
      3. 1.3. RenderThread 的 queueBuffer
    2. 2. BufferQueue 部分
    3. 3. SurfaceFlinger 部分
      1. 3.1. 工作流程
      2. 3.2. 掉帧
    4. 4. HWComposer 部分
  • 参考文章
  • 关于我 && 博客