Skip to content

10 图形栈:VSYNC、BufferQueue 与 SurfaceFlinger ​

本页导读:屏幕上的每一帧都是一场接力:VSYNC 发令,应用绘制交棒,BufferQueue 传递接力棒,SurfaceFlinger 冲线合成。本页沿时间轴讲五个主角——VSYNC 的产生与传播、BufferQueue 生产消费模型、SurfaceFlinger 的合成策略、BLAST 时代的新路径、SyncFramework 的原子提交——并给出 jank 排查工具箱。基线 Android 16(API 36)。


读完本页你要能回答:一帧从指令到光子走了几步?每步掉队了去哪里抓?

一、VSYNC:节拍器从哪里来 ​

  • 源头是显示控制器(DDIC/DPU)的硬件中断,经显示 HAL(composer 回调 onVsync 类接口,AIDL 名以 hardware/interfaces/graphics/composer3/aidl 为准)进入 SurfaceFlinger;
  • SF 用 VsyncPredictor 建模周期与抖动,产出两路节拍:SF 自己的合成节拍与发给应用的 app-VSYNC;后者经 DisplayEventReceiver 送达各进程的 Choreographer;
  • 两路之间有可调 offset(提前唤醒应用、错峰合成),历史上由 debug.sf.*/SFConfig 控制,具体键名以 AOSP 为准;
  • 应用侧收到帧信号的是 Choreographer(frameworks/base/core/java/android/view/Choreographer.java):doFrame 消息以异步消息插队(第 05 页同步屏障的服务对象)。

一句话模型:VSYNC 不是中断直插应用,而是「硬件节拍 → SF 建模 → 软件分发」的三层复时钟——所以「掉帧」先问节拍没来,还是来了没画完。

建模细节三件:

  • VsyncPredictor 用历史样本回归周期与相位,容忍硬件抖动;节拍丢失(面板省电降频/切换模式)会短暂退化到软件定时器兜底;
  • app-VSYNC 与 SF-VSYNC 的相位差决定「应用有多少时间画完这一帧」,OEM 调参的核心就在 offset,不是把主频调高;
  • 折叠/多屏设备每块显示独立节拍,Choreographer 实例按显示路由(多屏 App 的帧回调要选对 display,以 AOSP 当前实现为准)。

二、Surface、SurfaceView、SurfaceControl:三件易混的「表面」 ​

  • Surface:生产端的票证——谁拿到 Surface,谁就能往这个 BufferQueue 塞帧(相机预览、视频解码、RenderScript 老通路都以它交接口);
  • SurfaceView:独立 Surface 的窗口——它在自己的层级里放了一个洞(hole punch),底层像素由独立 Layer 合成,不经过宿主窗口的 View 树;这是播放器首选(省电、无缩放损耗),代价是传统上不好做动画/变换(BLAST 后大幅缓解);
  • TextureView:把别的 Surface 的内容当纹理消费进自家 View 树,动画随便做,代价是多一次 GPU 拷贝;
  • SurfaceControl(API 29+):WMS/SF 世界的图层遥控器——位置/裁剪/透明度/层级全在事务里改,现代合成路径的逻辑入口(配合第 07 页)。

一句话区分:Surface 是「往哪画」,SurfaceView 是「另开一路画」,TextureView 是「把别人画的当素材」,SurfaceControl 是「摆画好的东西」。

再埋一个跨页彩蛋:SurfaceView 的输入仍然走宿主窗(第 09 页的输入窗是宿主),像素却走自己的层——「画面动了但点击坐标对不上」类 bug,根子就在这条「画/控分离」的设计上。

三、BufferQueue:经典的四级流水 ​

text
生产者(应用 RenderThread / 摄像头 HAL…)      消费者(SF / 编解码器…)
 dequeueBuffer ──拿到空闲槽 slot──▶ GPU 绘制 ──queueBuffer──▶ acquireBuffer ──▶ 合成/编码 ──▶ releaseBuffer
  • 槽位(slot)状态机:FREE → DEQUEUED → QUEUED → ACQUIRED → FREE,双端以「槽位号 + Fence」同步,不搬数据本体;
  • Fence 是关键胶水:GPU 画完发 release fence,显示读完发 release fence,acquire fence 保证「下一个读的人等上一个写完」——跨 GPU/显示/DSP 的异步硬件靠它排程;
  • 接口家族名记:生产端 IGraphicBufferProducer(应用里的 Surface 是它的包装),消费端 IGraphicBufferConsumer;BLAST 之后消费端角色收编进 SF,但 dumpsys 仍按这套词表输出;
  • 双缓冲/三缓冲:延迟敏感用双缓冲(低延迟低吞吐),动画掉帧严重时 SF 会提示三缓冲(BufferQueue 的 setAsyncMode/setMaxDequeuedBufferCount 类策略,接口名以 AOSP 为准);
  • 背压机制:dequeue 阻塞即「你画太快了,等屏幕」——这是「RenderThread 阻塞在 dequeueBuffer」栈帧的官方解释;
  • 一对多约束:一个 BufferQueue 同一时刻只服务一对生产/消费者;「既想预览又想录制」的标准解法是复制分发(Camera 的预览+录像双 OutputConfiguration,或中间加转发 Surface),而不是共享 BufferQueue;
  • 生产节奏:queueBuffer 带时间戳与 expectedPresent,SF 按「预计上屏时间」排序呈现——同一帧重放、跨显示路由都靠这两个元数据;
  • 槽数经济学:槽越多吞吐越稳但输入延迟越大,游戏低延迟模式会主动压槽数(setMaxDequeuedBufferCount/async 模式,API 名以 AOSP 为准)。

四、SurfaceFlinger:合成决策器 ​

SF 每帧回答一个问题:这一帧怎么拼最省。合成类型二选一:

  • DEVICE(HWC/DPU 直出):图层交给硬件合成器(composer HAL),GPU 不参与——省电省热,绝大多数正常 UI 走这条;
  • CLIENT(GPU 合成):SF 用 RenderEngine(Skia GL/Vulkan)把图层画成一张再提交——圆角/模糊/复杂变换/调试覆盖层等 DPU 干不了的活;
  • 决策由 CompositionEngine 逐层试算,HWC 也会用 validateDisplay 告诉 SF「我干不完,你补几张」——DEVICE 与 CLIENT 混排是常态(以 AOSP 实际策略为准)。

图层树与 WMS 容器树一一对应(第 07 页):Layer 持几何/alpha/变换,LayerSnapshot 记录本轮合成属性;dumpsys SurfaceFlinger 里的层序、composition 类型、display mode 是画面问题取证第一站。

SF 主循环节拍:合成由「vsync 事件 + 事务到达 + 页面 flip 回执」三类消息驱动;合成中等待 GPU(CLIENT 层)不阻塞下一轮事务受理。线程速记:主线程决策合成、RenderEngine 线程干活、binder 线程池收事务——「SF 卡住」极罕见,卡通常是等 fence(GPU/显示没还笔)。

五、BLAST:把 BufferQueue 变成 Binder 事务 ​

Android 11 引入、12 默认的现代路径:

  • 应用侧 BLASTBufferQueue(frameworks/native/libs/gui/BLASTBufferQueue.cpp)取代裸 BufferQueue 直连 SF:新帧打包成 SurfaceControl 事务(Transaction.setBuffer + setReleaseBuffer)发给 SF——Buffer 生命周期随事务走;
  • 收益一:与窗口属性变更同流(第 07 页 relayout 与首帧同一事务,消除尺寸竞态);收益二:SF 侧统一按事务排序,无需旧 BufferQueue 的双端协商;
  • 旧时代(P 及以前)的 Surface+IGraphicBufferProducer 直连已被完全替代;SurfaceView、视频解码输出等也统一走 BLAST(AOSP 迁移细节以其为准);
  • 背压换了形式但没消失:SF 端按队列深度节制 acquire,生产过快依旧会等;「dequeue 阻塞」在 BLAST 时代表现为应用侧 BLASTBufferQueue 的等待;
  • 调试可见:dumpsys SurfaceFlinger 层信息里 BLAST 层与 transaction 队列状态直接可读;
  • 安全侧:DRM 安全层走保护内存(secure surface),截屏得到黑块是设计而非 bug(FLAG_SECURE 同源语义)。

六、SyncFramework:跨进程原子提交 ​

一帧里同时涉及多个进程(WMS、SystemUI、App),如果各自 apply 事务会出现「键盘动了、背景没动」的撕裂。SyncFramework 的思路:

  • 一个逻辑变更登记进同一个同步组(SurfaceSyncGroup,Java 侧在 frameworks/base/services/core/java/com/android/server/wm/SurfaceSyncGroup.java;native/客户端对应 android/window/SyncEventThread 线,目录以 AOSP 为准);
  • 每个参与者拿到自己的子事务,干完合并回组;发起方 markSurfaceDrawn/apply 时合并成一笔总事务交给 SF;
  • 同步组有超时保护:参与者迟迟不交卷宗,SF 会强制合并提交并打日志(「谁拖了同步组」在 dumpsys 与 logcat 可见,关键字以 AOSP 为准);动画「定住一下再跳」常是这里超时兜底;
  • 第 07 页的转场、insets 动画、本页的 BLAST 提交共享这套骨架——理解「一次可见性变化 = 一个事务」就理解了现代 Android 画面一致性的全部前提。

七、刷新率与显示模式 ​

  • 显示支持多模式(60/90/120Hz、折叠内外屏不同模式)时,DisplayManagerService+SF 按内容投票选模式(displayModeManager/帧率 vote 体系,Android 12+;接口名以 AOSP 为准);
  • 应用可表达偏好(preferredRefreshRate/FrameRateScheduler 类 API,见官方文档),但决定权在 SF 的合成器策略(App 内 Surface.setFrameRate 的 hint 语义);
  • LTPO/自适应刷新(面板侧 VRR)在 Android 15+ 支持度增强;framework 侧投票与 idle 降频策略以 AOSP 为准;
  • 省电联动:降模式/降亮度是第 11 页功耗账本的常客。

实操速记:dumpsys SurfaceFlinger display 看当前 mode/刷新率;视频播放时留意 3:2 pulldown(24fps 内容配 60Hz 的节拍对齐),对不齐就出现 cadence jank(周期性轻微顿挫),换 120Hz 或 VRR 设备即愈——这是「电影播放不流畅」的常见答案。

八、jank 排查工具箱(含判例) ​

层次工具典型读数
节拍perfetto sched/vsync 轨道app-VSYNC 到 doFrame 的唤醒延迟
应用绘制dumpsys gfxinfo <pkg>帧直方图、Draw/Sync/Upload/swap 四段分解
渲染线程FrameMetrics/JankStats(API 31+ Mainline)逐帧分段耗时、jank 归因
合成侧dumpsys SurfaceFlinger层 composition 类型、present 延迟
排队/背压BufferQueue dumpsys(dumpsys SurfaceFlinger --dump? 以子命令为准)或 perfettodequeue 阻塞时长、queued 积压
全局对比Winscope(capturetrace 后比对)WMS/SF 两端状态差异定位责任
节拍层dumpsys SurfaceFlinger --latency <层名>该层逐帧 present 时间序列

工具版本提示:Winscope 以插件形态集成于 Android Studio/独立发行,trace 采集用其自带 capturetrace;gfxinfo 与 FrameMetrics 的口径在 API 31 后有细微差别(jank 归因字段见官方 JankStats 说明)。

排查心法:先分「没画出来」还是「画出来没合成」——前者查主线程/RenderThread/节拍,后者查 SF/HWC/显示链路;gfxinfo 四段(Draw=录制、Sync=同步上传、Upload=纹理上传、swap=交 Buffer)天然给出了分段归因。

小判例:列表滚动掉帧但 gfxinfo 四段都正常 → 十有八九节拍没来(掉 vsync,查 SF 合成超时);视频 SurfaceView 动画撕裂 → BLAST 事务未入同步组(第三方播放器自管提交的典型坑);大屏旋转闪黑边 → relayout-finish 与 SF 提交三端时序竞态(第 07 页第三节)。

九、截屏、录屏与虚拟屏 ​

  • 截屏链路:screencap 经 SF captureScreen/captureLayers,安全层与 FLAG_SECURE 窗口输出为黑(设计);SF 内部按合成结果抓帧,因此截屏永远「所见即所合成」;
  • 录屏:MediaProjection 授权后创建 VirtualDisplay——SF 把真实屏合成结果镜像进一路虚拟显示输出,喂给编码器;所以录屏看到的撕裂/黑块与真机观感可能有帧差;
  • 虚拟显示(外接屏模式/投屏)本质是 SF 多输出:每个 display 一套合成与节拍,折叠内外屏同机制;
  • 取证组合:dumpsys SurfaceFlinger displays 列全部 display 与模式;「外接屏黑屏/模式谈判失败」优先查 composer 能力协商(第 12 页 HAL 视角)。

十、术语速查 ​

术语一句话
VSYNC / app-VSYNC硬件节拍 / 软件化后的应用开工令
BufferQueue / slot双端环形队列 / 队列里的缓冲区格子
Fence异步硬件之间的「你写完了吗」票据
DEQUEUED/QUEUED/ACQUIRED已取走 / 已交件 / 消费者正用
DEVICE/CLIENT 合成DPU 直出 / GPU 代工
BLAST以 SurfaceControl 事务搬运 Buffer 的现代路径
SyncFramework多进程变更合并为一笔原子事务
leash动画期间容器的替身图层句柄(第 07 页)
LTPO/VRR面板侧可变刷新率能力
cadence jank帧率与节拍错拍导致的周期性不流畅
doFrameChoreographer 每帧的总调度入口

十一、常用命令小抄 ​

bash
adb shell dumpsys SurfaceFlinger --list            # 层清单(子命令支持以工具版本为准)
adb shell dumpsys SurfaceFlinger --latency <layer> # 帧延迟历史
adb shell dumpsys SurfaceFlinger display           # 显示模式/能力
adb shell dumpsys gfxinfo <pkg>                    # 帧统计四段
adb shell dumpsys gpu                             # GPU 记忆(子命令名以版本为准)
adb shell cmd -l | grep -i surface                # 摸一把设备实际支持的子命令

先 --help/-l 探设备再敲命令,比背参数活得久;图形问题的最终裁判永远是 perfetto 时间线,dumpsys 只负责缩小包围圈。

十二、版本注记 ​

  • Android 4.1~5(API 16~21):Project Butter——VSYNC 统一节拍、Triple Buffer、Choreographer 登场,现代模型成型。
  • Android 8~9(API 26~28):HWComposer HAL 化(HIDL);图形栈进 Treble 边界。
  • Android 10~11(API 29~30):SurfaceControl 事务化前奏;BLAST 落地。
  • Android 12(API 31):BLAST 全默认;帧率投票体系(FrameRateVote);JankStats 类统计 Mainline 化。
  • Android 13~14(API 33~34):Game Mode/低时延渲染管线增强;composer HAL AIDL 化完成;截屏 API 收编入 SurfaceControl.captureDisplay/captureLayers 稳定面。
  • Android 15~16(API 35~36):VRR/LTPO 适配深化、16KB 页对图形 native 库重编要求(第 12 页);合成主循环十年未换骨架。

十三、自测清单 ​

  • 能说出 app-VSYNC 与 SF-VSYNC 的分工与 offset 意义;
  • 能画 BufferQueue slot 状态机并解释 fence 的两端语义;
  • 能回答 DEVICE/CLIENT 合成的选择逻辑与混排常态;
  • 能解释 BLAST 相比旧 BufferQueue 直连的两点收益;
  • 能用 gfxinfo 四段把一个掉帧归因到具体阶段;
  • 能解释为什么 SurfaceView 省电而 TextureView 灵活;
  • 能说出 HDR/宽色域内容在 dumpsys 层的哪个字段可见;
  • 能解释录屏(MediaProjection)为什么必须经过用户授权与可见指示;
  • 能解释为什么 dumpsys SurfaceFlinger 的层序与 dumpsys window 的窗口序基本同构。
  • 能画出一帧的完整流水线并标出五个可观测点(doFrame、queueBuffer、事务 apply、合成、present)。

本页小结 ​

本页小结:记住一条接力棒——VSYNC 令旗、应用绘制、BufferQueue 交接、SF 拍板合成、BLAST 把交接变成事务、SyncFramework 把多进程事务拧成一笔。Jank 排查永远先问:断在「没画完」还是「没合成」?节拍、四段耗时、层类型三组读数足够把责任定到进程级。

参考 ​

  • 图形栈(AOSP):source.android.com/docs/core/graphics
  • SurfaceFlinger 源码:cs.android.com/android/platform/frameworks/native/+/main/services/surfaceflinger
  • BLASTBufferQueue 源码:cs.android.com/android/platform/frameworks/native/+/main/libs/gui
  • ViewRootImpl 源码(应用侧图形入口):cs.android.com/android/platform/frameworks/base/+/main/core/java/android/view/ViewRootImpl.java
  • 帧统计与动画性能:developer.android.com/topic/performance
  • composer HAL(AIDL):hardware/interfaces/graphics/composer3/aidl(cs.android.com 搜 composer3)
  • Choreographer 源码:cs.android.com/android/platform/frameworks/base/+/main/core/java/android/view/Choreographer.java

基于 Android 16 / API 36 · 内容为原创整理,转载请注明出处