Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Android 渲染架构与版本演进

一次触摸已经改变了 View 的状态,下一帧却没有按时出现在屏幕上。问题可能出在主线程遍历、RenderThread、GPU、应用侧 BufferQueue、SurfaceFlinger,甚至显示合成阶段。只看一段 draw 耗时,我们很难判断阻塞发生在哪里。

分析这类问题,我们先把两个问题问清楚:

  1. 谁在生产图形缓冲区,缓冲区交给了哪一个 Surface?
  2. 当前观察到的事件位于“生成内容”“提交缓冲区”“系统合成”还是“显示呈现”阶段?

源码基线:AOSP android-17.0.0_r1;内核 android17-6.18-2026-06_r6;历史部分覆盖 Android 3.0 到 Android 17。

本文从普通应用窗口出发,讲清 View、HWUI(Android 的 View 硬件加速渲染管线)、BufferQueue、BLAST(协调 buffer 与 SurfaceControl 事务提交的组件)、SurfaceFlinger、Hardware Composer(HWC) 和显示设备之间的关系;内核对象只用来解释同步与共享缓冲区。

先明确几个会贯穿全文的对象。buffer 保存一帧图形内容;Surface 是 Producer(生产者)提交 buffer 的目标接口;BufferQueue 让 buffer 在 Producer(生成 buffer 的一方) 与 Consumer(消费者)之间流转;Layer 则是 SurfaceFlinger 组织合成内容的单位。

latch(选定本帧使用的 buffer) 指 SurfaceFlinger 选中并取得某个可用 buffer,fence 是表示异步读写何时完成的同步信号。BLAST 用于协调窗口 buffer 与 SurfaceControl transaction(对 Layer 属性或 buffer 的一组原子更新) 的提交。

一帧从应用获得 VSync 开始,经过 UI 线程、RenderThread、GPU、BufferQueue 和 SurfaceFlinger,最后由 HWC 提交显示。版本变化主要改变调度、缓冲区和合成接口,但不管哪个版本,我们分析时都要回到 Producer、Consumer(读取 buffer 的一方)、layer 与 fence 这几个对象上。

帧生产、缓冲与系统合成

这一节我们沿一帧的完整路线走一遍:先分清内容走哪条输出路径,再依次看主线程、HWUI、BufferQueue、BLAST、SurfaceFlinger 与 HWC 各自负责什么,最后讲怎么用 Perfetto 把这些阶段串成一条可核对的链路。

1. 输出拓扑分类

拿到一段渲染 trace,我们先要分清内容从哪条路径画出来。这里的输出拓扑,指一类内容由哪个 Producer 生成、经过哪个 Surface 和 BufferQueue,并最终成为哪个 Layer。“Android 渲染”包含多种数据路径,它们可能同时出现在一个窗口里,Producer 和 BufferQueue 却未必是同一套。

内容类型主要生产者提交目标常见特征
普通 View / Compose UI应用进程中的 HWUI宿主 App Window 的 Surface主线程生成 UI 状态和绘制记录,RenderThread 驱动 Skia 绘制
TextureView 内容相机、解码器、OpenGL 等生产者SurfaceTexture,再作为 View 树纹理参与 HWUI 绘制外部内容最终进入宿主窗口缓冲区
SurfaceView 内容相机、解码器、游戏引擎等生产者独立 Surface / Layer内容有独立缓冲区队列,SurfaceFlinger 与宿主窗口一起合成
视频解码Codec / vendor 组件Surface 对应的 BufferQueue生产节拍、色彩格式和保护内容约束与普通 UI 不同
游戏或自建引擎EGL / Vulkan 应用代码NativeWindow / Surface应用自行组织渲染循环与 GPU 工作
WebView / Flutter 等框架框架自身加上 HWUI 或独立 Surface取决于具体实现和模式不能仅凭控件名称推断缓冲区拓扑

“标准应用窗口路径”指普通 View 或 Compose 内容经过承载 View 树的 App Window,再从该窗口的 HWUI Surface 输出。SurfaceView 走的是独立内容生产路径,Flutter、WebView、Camera、视频这些场景也各有自己的拓扑,都要按实际路径单独分析。

Perfetto 中出现多条 BufferQueue 或多个 SurfaceFlinger Layer 时,我们按生产者和 Surface 归属拆分,再逐条分析时序。

SurfaceView 与 TextureView 改变内容合成位置

图:SurfaceView 内容通常保留独立 Surface/Layer,与宿主窗口分别进入系统合成;TextureView 的外部 buffer 则由 HWUI 作为纹理采样,主体内容进入宿主 Window buffer。是否使用硬件叠加平面由整屏条件与设备能力决定。

2. 标准应用窗口的一帧经过哪些阶段

普通应用窗口的一帧可以概括为下面这条路径:

状态变化 / invalidate / requestLayout
    ↓
ViewRootImpl.scheduleTraversals()
    ↓
Choreographer:INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT
    ↓
ViewRootImpl.performTraversals()
    ↓
按需要执行 measure / layout / draw
    ↓
View 绘制操作录制到 RenderNode / DisplayList
    ↓
ThreadedRenderer → HardwareRenderer.syncAndDrawFrame()
    ↓
RenderThread:同步状态、Skia 绘制、提交 GPU 工作
    ↓
dequeueBuffer → draw → queueBuffer
    ↓
BLASTBufferQueue 将缓冲区放入 SurfaceControl Transaction
    ↓
SurfaceFlinger 接收事务、生成前端快照、latch 可用缓冲区
    ↓
HWC 协商合成方式(常规 validate;满足条件时可跳过)
    ↓
必要时由 RenderEngine 生成 client target
    ↓
HWC present:返回 present fence 与各 Layer 的 release fence

其中,client target 是 SurfaceFlinger 执行客户端合成时生成的中间输出缓冲区。present fence 标记接口约定的呈现边界:物理显示可能是开始呈现或开始向面板传输,虚拟显示则是输出 buffer 写入完成,release fence 保护相应 buffer 何时可以再次写入;两者对应的对象和帧号可能不同,signal 的先后顺序要按具体契约判断,见下文 Fence 一节。

这些阶段大多是并行推进的:主线程、RenderThread、GPU、SurfaceFlinger 和显示硬件常常重叠处理不同的帧。所以分析时我们要保留帧号、VSync 周期和 fence 依赖,各段耗时直接相加,得不出整帧的结论。

一帧至少跨越三个调度域

调度域代表线程或组件主要职责
应用 UImain / ViewRootImpl / Choreographer处理输入、动画、布局和绘制记录
应用渲染RenderThread + GPU 队列同步 RenderNode、执行 Skia 管线、产生窗口缓冲区
系统显示SurfaceFlinger / HWC / 显示设备选择缓冲区、确定合成策略并呈现

“应用主线程已经画完”通常只说明 UI 侧工作到达了某个提交点;GPU 是否完成、SurfaceFlinger 是否 latch、像素是否开始被面板扫描,都要另外确认。

3. 主线程:从状态变化到 Traversal

Traversal 是 ViewRootImpl 发起的一轮窗口级遍历,做哪些 measure、layout 和 draw 由当前状态决定;一轮 traversal 未必覆盖整棵 View 树,重绘范围跟着失效状态走。

3.1 scheduleTraversals() 合并同一轮请求

View 状态改变后,框架通常不会马上递归遍历整棵树。ViewRootImpl.scheduleTraversals() 通过 mTraversalScheduled 避免重复调度,并把遍历回调(traversal callback)交给 Choreographer:

void scheduleTraversals() {
    checkThreadCompat();
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        postTraversalBarrier();
        mChoreographer.postVsyncCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalCallback);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

代码路径位于 frameworks/base/core/java/android/view/ViewRootImpl.java。同一消息循环内多次 invalidate() 或 requestLayout() 可以汇入一次待执行的 traversal;合并的只是调度本身,这一帧要做多少工作,仍由被标记的区域、布局(layout)请求的传播范围和窗口状态决定。

3.2 Choreographer 的回调顺序

Choreographer 负责把输入、动画和 traversal 回调放到统一的帧节拍上分发,回调类型定义如下:

CALLBACK_INPUT
CALLBACK_ANIMATION
CALLBACK_INSETS_ANIMATION
CALLBACK_TRAVERSAL
CALLBACK_COMMIT

在一次 doFrame() 中,它们按上述顺序执行。这个顺序解释了几个常见现象:

  • 输入回调可以先改变状态,动画随后更新属性,traversal 再读取这些结果。
  • 主线程在 INPUT 或 ANIMATION 阶段耗时过长,会挤压同一帧留给 TRAVERSAL 的时间。
  • COMMIT(帧遍历后的提交回调阶段) 位于 traversal 之后;走到 COMMIT 只代表遍历收尾,缓冲区有没有上屏,要看后面的阶段。

3.3 performTraversals() 按条件执行三大流程

ViewRootImpl.performTraversals() 是窗口级遍历的核心入口。根据布局请求、尺寸、可见性、Surface 状态和脏区域,它可能执行:

  • 测量(measure):计算 View 所需尺寸;
  • 布局(layout):确定 View 在父容器中的位置;
  • 绘制(draw):录制或更新需要重绘的内容。

三者都有条件判断。android-17.0.0_r1 的 performTraversals() 没有提供同名的显式系统 trace 区间;应从 Choreographer#doFrame 的 traversal 回调范围,以及实际存在的 measure、layout、draw-VRI[...] 等区间判断工作。厂商或自定义 trace 即使标出方法名,也只说明这个方法被调用过,完整三步或整棵 View 树是否执行,要看实际存在的区间。

Measure:父子双方参与尺寸协商

父 View 通过 MeasureSpec 把尺寸约束传给子 View:

模式含义
EXACTLY尺寸已确定,子 View 应使用该尺寸
AT_MOST子 View 可以选择不超过上限的尺寸
UNSPECIFIED父容器没有给出这一方向的上限

自定义 View 需要依据内容、建议最小尺寸、padding 和 MeasureSpec 调用 setMeasuredDimension()。测量次数变多时,我们沿嵌套权重、父容器多轮协商、窗口尺寸变化、布局请求传播这几个方向排查;一次 AT_MOST 本身给不出根因。

Layout:确定位置,不自动裁剪内容

Layout 通过 layout(l, t, r, b) 和 onLayout() 确定子 View 的边界。子 View 的位置和尺寸由父容器决定,但内容是否被裁剪属于绘制阶段行为,还会受 clipChildren、clipToPadding、outline、显式 clip 和变换影响。

因此,“子 View 位于父 View 边界内”与“超出父边界的像素会不会显示”要分开判断,后者由绘制阶段的裁剪逻辑决定。

Draw:生成绘制记录

硬件加速窗口中,View.draw() 及其相关分发逻辑通过 RecordingCanvas 记录绘制命令。它们描述“画什么”和状态变换;主线程通常不在这里当场把最终像素光栅化到显示缓冲区,光栅化留给后面的渲染阶段。

常见绘制顺序如下:

  1. 绘制背景;
  2. 保存或处理滚动、裁剪等状态;
  3. 调用 onDraw() 绘制自身内容;
  4. dispatchDraw() 绘制子 View;
  5. 绘制前景、滚动条等装饰。

具体分支受 View 标志、缓存、动画和硬件加速状态影响,这份顺序描述的是常见情况;每个 View 实际执行哪些步骤,要看它自己的标志和状态。

3.4 invalidate() 与 requestLayout() 的边界

API主要表达可能触发的工作
invalidate()某个区域的显示内容失效标记脏区域,调度绘制;不主动表达尺寸变化
requestLayout()当前 View 的测量结果或位置可能失效向父级传播布局请求,并调度 traversal

两者最终都可能通过 ViewRootImpl 汇入下一轮 traversal。requestLayout() 的传播和框架优化会影响实际遍历范围,“每次调用都完整重测整棵树”只是其中一种情况。

我们排查主线程问题时,建议同时看:

  • 调用频率:是否在循环或动画中反复请求布局;
  • View 树规模:深度、宽度以及复杂容器;
  • 自定义 onMeasure()、onLayout()、onDraw() 的耗时;
  • 是否发生窗口 relayout(重新协商尺寸和 Surface)、Insets(系统栏等占用区域)或配置变化;
  • traversal 相对 VSync 的开始时间和结束时间。

4. HWUI:从绘制命令到 RenderThread

4.1 RenderNode 保存可复用的绘制记录

硬件加速的 View 树会用 RenderNode 表示可独立更新和复用的渲染节点。主线程把 Canvas 操作录制为 DisplayList(可回放的绘制命令列表),后续帧若某个节点内容没有失效,HWUI 可以复用已有记录。

复用也有复用的成本:属性同步、树遍历、裁剪、合批(合并可一起提交的绘制操作)、资源生命周期和最终 GPU 工作仍可能涉及该节点。DisplayList 省下的,主要是重复执行 Java 绘制代码和重复生成绘制命令这部分开销。

native RenderNode 同时维护当前状态与暂存状态(staging state)。与 DisplayList 同步有关的字段和方法包括:

bool mNeedsDisplayListSync;
DisplayList mDisplayList;
DisplayList mStagingDisplayList;

void syncDisplayList(TreeObserver& observer, TreeInfo* info);
void pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info);

这些声明与实现位于:

  • frameworks/base/libs/hwui/RenderNode.h
  • frameworks/base/libs/hwui/RenderNode.cpp

主线程更新 staging 数据,RenderThread 在同步阶段把需要的变化推入渲染侧状态。这个双阶段模型能减少两个线程同时修改当前渲染状态所造成的锁竞争。

4.2 RecordingCanvas 负责录制

Java 层 RecordingCanvas 的绘制 API 最终进入 BaseRecordingCanvas 等实现。以绘制 RenderNode 为例,native 入口由框架内部桥接完成。阅读源码时要区分:

  • Java Canvas API;
  • JNI/native 方法;
  • Skia/HWUI DisplayList 操作;
  • RenderThread 上的执行。

看到 drawRect()、drawBitmap() 或 drawRenderNode() 返回,说明相应的调用已经完成;对硬件加速 Canvas 来说,GPU 的执行通常还在后面。

重复 UI 请求合并调度,再按状态录制与同步

图:mTraversalScheduled 合并的是待执行遍历的调度,实际 measure/layout/draw 仍由状态决定。硬件加速路径先录制 RenderNode/DisplayList,再同步到 RenderThread;录制返回时,GPU 的像素生成通常还在后面的阶段。

4.3 syncAndDrawFrame() 是 UI 到 RenderThread 的提交边界

ThreadedRenderer 通过 HardwareRenderer 把帧信息提交给 native 渲染代理,核心入口名为 syncAndDrawFrame()。它连接两类工作:

  1. UI 线程准备帧信息和 RenderNode 变化;
  2. RenderThread 同步状态并安排绘制。

frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp 中的 DrawFrameTask::run() 会执行帧状态同步,并按条件决定何时解除 UI 线程等待;随后由 CanvasContext 执行绘制和缓冲区交换。

syncAndDrawFrame() 的返回值或 slice 结束,只说明这次提交告一段落;GPU、BufferQueue 消费和屏幕呈现推进到哪一步,要看后续阶段的记录。

4.4 RenderThread 和 GPU 允许并行

RenderThread 负责准备并提交 GPU 命令。GPU 在独立队列中执行这些命令,因此可能出现:

主线程准备 Frame N+1
RenderThread 提交 Frame N
GPU 执行 Frame N-1
SurfaceFlinger 合成更早的一帧

这种重叠是图形管线维持吞吐量的基础。所以某条 GPU track(Perfetto 中记录 GPU 工作的时间轨道) 超过一个刷新周期时,先别急着下掉帧的结论:我们要结合依赖 fence、队列深度和 FrameTimeline,确认该 GPU 工作是否真的挡住了目标帧的 deadline(本周期必须完成的截止时间)。

不同执行域可以同时推进不同帧

图:轨道只是并行关系示意,宽度和相位按示意阅读即可,各域也未必总相差一帧。应使用目标帧号、fence 和 deadline 关联依赖;一段 GPU 工作跨刷新周期,还要结合依赖和 deadline 确认它是否真的挡住目标帧。

4.5 SkiaGL、SkiaVulkan 与渲染后端

Skia 是 Android 使用的二维图形引擎;渲染后端决定它通过哪套图形 API 把命令交给 GPU。HWUI 包含 Skia OpenGL 与 Skia Vulkan 两条管线:

  • SkiaOpenGLPipeline
  • SkiaVulkanPipeline

Properties::peekRenderPipelineType() 通过 use_vulkan() 读取只读产品属性 ro.hwui.use_vulkan:值为 true 时,debug.hwui.renderer 的默认值为 skiavk,否则为 skiagl。随后,debug.hwui.renderer 的值决定选择 SkiaVulkan 还是 SkiaGL。这个属性用于选择 HWUI 渲染后端,不提供实际 GPU 驱动名称。判断某台设备的默认后端,要同时检查产品属性和调试属性;只看 Android 版本,这两个属性就都漏掉了。

Vulkan 与 OpenGL ES 的 CPU 开销、驱动行为、着色器编译、内存压力和功耗表现依赖具体设备及负载;要判断某一帧的快慢,回到该帧的 trace 和计数器,后端名称本身给不出答案。

5. BufferQueue:共享缓冲区的流转

5.1 Producer 与 Consumer

绘制记录要变成屏幕上的像素,先得解决缓冲区在两边怎么流转。一条 BufferQueue 连接一个图形缓冲区生产者和一个消费者:

Producer                         Consumer
dequeueBuffer()
    ↓
满足 dequeue 返回的 fence 依赖(如有)
    ↓
写入或渲染 GraphicBuffer
    ↓
queueBuffer(fence)
                                acquireBuffer()
                                    ↓
                                等待 fence 后使用缓冲区
                                    ↓
                                releaseBuffer(releaseFence)

queueBuffer() 主要提交缓冲区槽位、元数据和同步信息。GraphicBuffer 背后的内存通过句柄共享,正常路径不需要在 queueBuffer() 时复制整张图片。

dequeueBuffer() 返回的 fence 约束 Producer 再次写入的时机:要等这个 fence signal 之后才能覆写 buffer;queueBuffer(fence) 携带的 fence 则交给 Consumer,约束读取本帧内容的时机。槽位交还了,上一轮异步使用却可能还在进行;前一项契约见 IGraphicBufferProducer.h。

queueBuffer() 成功只说明生产者把缓冲区置为可消费状态;SurfaceFlinger 有没有 latch、显示设备有没有呈现,要沿下游的 transaction 和 fence 继续看。

5.2 槽位状态

BufferQueue 中常见的槽位状态为:

状态含义
FREE当前可供生产者选择
DEQUEUED已由生产者取得,正在写入或准备
QUEUED已提交,等待消费者获取
ACQUIRED已由消费者获取,尚未释放

生产者能同时持有的 buffer 是有限的:最大已 dequeue 数、最大已 acquire 数、异步模式、消费者是否会阻塞以及当前可用槽位共同决定它要不要等待。

BufferQueueCore 的初始配置中可看到最大 acquired 和 dequeued 数的默认值;实际最大缓冲区数量由运行时条件计算,并不是全系统固定的三个,要以当时的队列状态为准。

BufferQueue 槽位流转与两次安全使用条件

图:常见流转是 FREE→DEQUEUED→QUEUED→ACQUIRED→FREE。dequeue 返回的 fence 保护生产者再次写入,queue 携带的 fence 则保护消费者读取;槽位回到可流转状态时,此前的异步工作可能仍在收尾。

5.3 “三缓冲”要按队列状态理解

双缓冲或三缓冲常被用来描述生产、消费和显示可以同时占用多个缓冲区,但 Android 的 BufferQueue 深度具有动态性:

  • Surface 尺寸、格式或 usage(缓冲区用途属性)改变会触发重新分配;
  • 最小未释放数量与 consumer 配置有关;
  • async(异步入队) / shared buffer(共享同一缓冲区) 等模式会改变规则;
  • dequeue/acquire 上限(分别对应 Producer 的 DEQUEUED buffer 和 Consumer 的 ACQUIRED buffer)会限制可并行持有的缓冲区;
  • fence 未完成时,槽位即使逻辑上可流转,也未必能安全复用。

多一个可用缓冲区有时能减少生产者因暂时无槽位而停顿,但队列持续堆积也可能增加输入到显示的延迟。“三缓冲”对卡顿和延迟的净效果,我们要按队列状态逐帧看。

5.4 队列背压的表现

背压(backpressure)指 Consumer 的处理或释放速度反过来限制 Producer,通常就体现为 Producer 等待可用槽位:队列有上限,压力以等待的形式传回生产侧。

应用生产速度超过消费者处理速度时,常见结果包括:

  • dequeueBuffer() 等待可用槽位;
  • 应用被 VSync 节奏约束,生产节奏有上限;
  • 队列中的缓冲区在特定模式下按规则被替换或跳过;
  • SurfaceFlinger 选择满足 latch 条件的缓冲区;
  • Choreographer 检测 buffer stuffing(缓冲区持续积压)并调整恢复节奏。

不同 Surface 类型、present mode(缓冲区如何排队与呈现的模式) 和 BufferQueue 配置都会改变行为;“多画的帧都会被丢掉”这类笼统说法,要放回这些具体条件下才能评判。

6. BLAST:把窗口缓冲区与 SurfaceControl 事务对齐

6.1 标准 App Window 中的 BLASTBufferQueue

在现代 Android 的普通应用窗口路径中,应用进程内的 BLASTBufferQueue 承担应用侧 BufferQueue Consumer 角色,并把取得的缓冲区包装进 SurfaceComposerClient::Transaction。它通过 setBuffer() 把 fence、帧号和 release callback(用于交还 buffer 并传递 release 信息的回调) 等信息提交给 SurfaceFlinger。

这条路径可以简化为:

HWUI Producer
    │ queueBuffer
    ▼
应用进程内 BLASTBufferQueue
    │ acquire buffer
    │ Transaction.setBuffer(...)
    ▼
SurfaceFlinger 中对应 Layer 的待处理事务

这里我们提两个容易踩的坑:

  • 事务传递的是缓冲区句柄、同步对象和元数据,SurfaceFlinger 并不通过 Binder 接收整帧像素副本。
  • BLAST acquire 到缓冲区之后,还要经过事务应用、Layer 状态更新、latch、合成与 present,BLAST 事件离显示完成还有一段距离。

6.2 BufferTX - <layerName> 的含义

SurfaceFlinger 的 Layer 追踪中可看到形如 BufferTX - <layerName> 的计数器,命名定义位于 frameworks/native/services/surfaceflinger/Layer.h。

它表达 SurfaceFlinger 侧待处理的 buffer transaction 数量:buffer 到达服务端时递增,被 latch 或丢弃时递减。计数下降时,仍需结合 latchBuffer、setBuffer 等事件判断,画面到没到屏幕,要看后续的 present 记录。普通应用 BufferQueue 的槽位状态计数、排队等待扫描输出的显示帧数各有自己的含义,混用会看错对象。

分析时应把下面几类指标分开:

指标所在边界
dequeue / queue / acquire / release某条 BufferQueue 的槽位生命周期
BufferTX - layerNameSurfaceFlinger Layer 的缓冲区事务积压
FrameTimeline expected / actual分别记录 App 或 SurfaceFlinger 的预期与实际工作区间;App 区间结束不等于已经上屏
HWC validate/ present显示合成与提交阶段

7. Fence:说明“什么时候可以安全使用”

图形管线用 fence 表达跨 CPU、GPU、合成器和显示设备的异步完成关系。几类 fence 名称相近,等待的方向却不一样,我们按下表区分:

Fence谁等待表达的条件
acquire fenceConsumerProducer 对该缓冲区的写入何时完成,Consumer 何时可以安全读取
release fence后续复用该缓冲区的一方当前 Consumer 何时不再使用该缓冲区,何时可以安全重写
present fenceSurfaceFlinger / 显示时序追踪物理显示开始呈现或传输本帧的边界;虚拟显示则表示输出 buffer 写入完成

从应用进程到 SurfaceFlinger 这条队列上,应用是 producer,BLAST/系统侧消费逻辑接收带有 acquire fence(保护 buffer 读取时机的同步对象) 的缓冲区。到了 HWC 和显示设备一侧,SurfaceFlinger 又要处理 client target、各 Layer 和 display present 相关的 fence。

7.1 Present fence 的边界

hwcomposer2.h 的 presentDisplay 契约 区分了三种情况:物理显示的视频模式面板在本帧开始出现的 VSync 发出信号;命令模式面板在开始向面板内存传输时发出信号;虚拟显示在输出 buffer 写入完成、可以安全读取时发出信号。物理显示的 present fence 不表示整屏扫描或所有像素的光学响应已经完成。

HWC 返回的某个 Layer release fence 则约束该 Layer 上一帧 buffer 的读取结束时机。SurfaceFlinger 还可能合并客户端合成的 GPU 读取依赖,见 Output::presentFrameAndReleaseLayers()。排查复用阻塞时,要核对 release fence 对应的 Layer、buffer 和帧号;本帧 buffer 何时可覆写,以对应的 release fence 为准。

Android 17 的 Composer3 还允许特定 TV 图像处理路径返回 LayerPresentFence,记录某层相对 Display present 的额外延迟;PresentFence.aidl 明确注明,这种行为偏离 SurfaceControl.Transaction 的通常契约,仅允许用于需要该功能的 TV 设备。分析这类设备时,还需区分 Display 与 Layer 的呈现时刻。

对于“输入到用户看到变化的总延迟”,应用 trace、FrameTimeline 和 present fence 只能覆盖其中一部分,还需结合触摸采样、显示扫描、像素响应、显示后处理和外部显示设备特性。

7.2 fence 等待的上游原因

一次 fence wait 只能说明依赖尚未满足。上游原因可能是:

  • GPU 工作排队或执行时间长;
  • producer 提交过晚;
  • 前一消费者仍持有缓冲区;
  • HWC 或显示设备尚未释放资源;
  • 跨进程事务与目标 VSync 错位。

我们要沿 fence 的生产者方向追上去,才能确定责任阶段。

8. SurfaceFlinger:接收事务、选择内容并组织合成

SurfaceFlinger 管理系统可见 Layer 的状态,接收来自 WindowManager、应用和系统组件的 SurfaceControl transaction(对 Layer 状态的一组原子更新)。现在的实现已经把 Layer 前端状态、快照生成、调度、合成规划和显示设备交互分到多个模块,早已超出“收到 VSync 后遍历所有 Layer 并画到屏幕”这一个函数的规模。

一轮显示处理涉及的核心概念包括:

  1. 接收并应用 SurfaceControl 事务;
  2. 更新 Layer 前端状态与可见性;
  3. 为当前组合生成快照,也就是供本轮合成决策使用的不可变状态视图;
  4. 判断缓冲区是否满足 latch 条件;
  5. 结合 damage(本帧相对上一帧变化的区域)、几何、色彩、protected content(受保护内容)等信息准备合成;
  6. 与 HWC 协商合成方式,常规路径调用 validate;
  7. 对需要客户端合成的部分调用 RenderEngine,准备 client target 及其 acquire fence;
  8. 向 HWC 提交 present,并传播 present 与 release 同步信息。

具体执行路径受 Scheduler、显示设备、Layer 状态、预测结果和 HWC 返回值影响。我们在 trace 里分析 SurfaceFlinger 时,围绕当前帧的事务、Layer、FrameTimeline 与 HWC 事件建立对应关系。

8.1 Latch 的选择条件

SurfaceFlinger 选择缓冲区时要考虑:

  • acquire fence 是否满足;
  • 缓冲区期望呈现时间;
  • Layer 与事务状态;
  • 当前调度和 latch 策略;
  • 当前版本与场景是否允许在严格条件下处理尚未 signal(完成) 的 fence。

Android 13 引入 AutoSingleLayer,允许在受限条件下 latch acquire fence 尚未完成的 buffer;unsignaled 表示“尚未完成”。在 android-17.0.0_r1 中,至少要核对以下条件:

  • debug.sf.auto_latch_unsignaled 启用;
  • 事务只更新一个 Layer,且满足 isSimpleBufferUpdate();几何变化和跨 Layer 同步事务不适用;
  • 当前处理的是首个事务(firstTransaction);
  • 调度器未处于 early VSync 配置,避免 RenderEngine 等待未完成的 buffer 等情况造成卡顿。

这些判断位于 SurfaceFlinger.cpp,fence 检查处还会联合判断 isSimpleBufferUpdate()。官方文档 对旧 Always 模式提示了显示冻结等风险,并注明该模式可能弃用;Android 17 的 getLatchUnsignaledConfig() 只返回 AutoSingleLayer 或 Disabled,配置要按这两个取值来读,旧 debug.sf.latch_unsignaled 的用法已经对不上当前实现。Perfetto 中可搜索 fence unsignaled try allowLatchUnsignaled,再结合 Layer、事务与 fence 时间确认实际命中的路径。

8.2 SurfaceFlinger VSync 与应用 VSync

应用和 SurfaceFlinger 都受显示节拍驱动,但调度器可以给它们设置不同的唤醒相位与 deadline(完成截止时间)。调度目标是让应用先生产,SurfaceFlinger 再在合适的时间消费并提交显示。

高刷新率、可变刷新率和多显示设备让“固定 16.67 ms、应用与 SurfaceFlinger 同时唤醒”的模型失效。这时我们直接读当前显示模式,以及 FrameTimeline 给出的 expected(预期) / actual(实际) 时序。

9. HWC 与 RenderEngine:每帧决定如何合成

9.1 HWC validate

Hardware Composer HAL 连接 SurfaceFlinger 与设备显示合成能力。SurfaceFlinger 为 Layer 提供候选 composition type(合成方式),HWC 在 validate 阶段可以接受或要求调整。

独立的 validate 调用不一定每帧出现,是否跳过取决于合成路径、时间条件和 HWC 返回结果。Android 17 的 HWComposer::getDeviceCompositionChanges() 在没有客户端合成、且满足最早呈现时间约束时,会尝试 presentOrValidate();HWC 若接受跳过 validate,便直接完成 present 提交,否则返回需要验证的结果。常规客户端合成路径则要先准备 client target,再提交 present;GPU 写入是否完成由传给 HWC 的 acquire fence 约束。分析 trace 时,应结合这两条路径判断;缺了单独的 validate slice,先看这帧是否走了 presentOrValidate() 直通路径。

合成决策可能受以下因素影响:

  • Layer 数量和硬件 plane 数量;plane 是显示控制器可以独立缩放、叠加并输出的一路硬件图层;
  • 像素格式、压缩格式与 buffer usage(缓冲区用途标志);
  • 缩放、旋转、裁剪和其他变换;
  • alpha、混合模式和遮挡关系;
  • dataspace(色彩空间等颜色描述)、HDR、色彩转换和显示能力;
  • protected content(要求安全显示路径的受保护内容) 与安全显示路径;
  • 设备厂商的 HWC 实现和当前资源占用;
  • 多显示器、虚拟显示器及 client target 约束。

“Layer 太多或有透明混合才会转 GPU 合成”只覆盖了其中少数几种,归因时要回到上面这些条件上。

9.2 Device composition 与 Client composition

类型主要执行者说明
设备合成(Device composition)显示控制器 / HWC 能力Layer 可由硬件 plane 等资源直接组合
客户端合成(Client composition)SurfaceFlinger 的 RenderEngine先把相应 Layer 合成为 client target(供 HWC 使用的中间输出),再交给 HWC

同一帧可以混合使用两者。某个 Layer 的 composition type 也可能随帧变化,因此我们以该帧的 HWC/SurfaceFlinger 数据为准;按应用或控件类型做永久归类,会漏掉这些变化。

presentOrValidate 成功时不再重复 present

图:无客户端合成且满足时序条件时可尝试 presentOrValidate;若直接 PresentSucceeded,本轮已完成提交,不再追加一次 present。若返回已验证,或走普通 validate,则读取类型与请求,按需准备 client target 后再 present。

9.3 RenderEngine 与应用 HWUI 是两个组件

两者都可能使用 Skia、OpenGL ES 或 Vulkan 相关图形能力,但职责不同:

  • 应用 HWUI 把 View/Compose 绘制记录生成应用窗口缓冲区;
  • SurfaceFlinger RenderEngine 在客户端合成、模糊、色彩处理等系统合成任务中生成输出。

应用 GPU slice 短,SurfaceFlinger 的 client composition(由 RenderEngine/GPU 先生成 client target) 未必短,反过来也一样;所以我们在 Perfetto 里按进程和上下文区分 GPU 工作来源。

9.4 色彩处理位于多个可能阶段

色彩空间转换、色调映射、显示颜色变换可能由 RenderEngine、HWC、显示处理单元或面板侧能力负责,位置取决于 Layer、输出显示和设备实现。这一步未必是显示前的收尾,成本也可能相当可观。

10. 从应用缓冲区到显示输出的各段缓冲区

把前面各阶段放到一起看,链路上同时存在好几份缓冲区:普通窗口的 App BufferQueue 保存应用生成的窗口缓冲区,SurfaceFlinger 若执行 client composition,还会生成 client target。显示控制器和面板扫描又可能有各自的内部缓冲与流水结构。

这些对象属于不同阶段:

App Window GraphicBuffer
    ↓ 作为一个 Layer 的输入
SurfaceFlinger / HWC composition
    ↓ 可能产生 client target
Display present
    ↓
显示控制器扫描与面板响应

把它们统一称作“前台、后台、显示中三个缓冲区”,会把应用 BufferQueue、HWC client target 与 scanout(显示控制器的扫描输出)混在一起。排查时我们应明确当前 buffer 的 owner(持有者)、queue、slot(BufferQueue 管理 buffer 的槽位)、frame number(Producer 分配的递增帧号) 和 fence。

11. 硬件加速与软件绘制

11.1 硬件加速窗口

硬件加速窗口中,View 绘制命令通常录制进 DisplayList,由 HWUI、RenderThread 和 GPU 生成 GraphicBuffer。优势来自命令复用、GPU 并行和图形管线,但性能仍受内容特征影响:

  • 大量离屏层(先画到中间缓冲区再合成)和过度绘制会增加像素工作;
  • 复杂 path、阴影、模糊和滤镜可能引入额外 rendering pass(一次独立的渲染处理);
  • 位图上传和纹理缓存失效会增加内存与传输成本;
  • shader(着色器) 编译或图形管线缓存未命中可能造成耗时抖动;
  • 大量 RenderNode 更新也会增加 CPU 同步成本。

11.2 setLayerType() 的准确含义

View.setLayerType(View.LAYER_TYPE_SOFTWARE, ...) 在硬件加速窗口里让该 View 的 layer 内容由软件生成,再由硬件管线把它当作纹理参与合成。它不会把整个 Window 切换成软件渲染。

窗口级是否启用硬件加速由 manifest(应用清单文件)、Window flag 和系统条件决定;View 级 layer type 主要影响该 View 的缓存和绘制策略。两级开关要分开看。

11.3 软件渲染与硬件渲染的适用条件

软件 Canvas 由 CPU 执行光栅化,某些小型、低频或特定操作可能成本可控;复杂动画、大面积更新和高分辨率内容通常更容易受 CPU 与内存带宽限制。硬件渲染也可能受驱动、GPU 队列和资源管理影响。

选择渲染方式要基于目标设备、内容和 trace;“GPU 核心更多”或固定倍数,推不出速度与功耗的结论。

12. 用 Perfetto 建立证据链

把某个阶段的结束当成整帧完成,很容易误判渲染问题。我们从用户感知的异常帧开始反向追踪。

12.1 第一步:锁定目标帧

先查看:

  • FrameTimeline 的 expected timeline(系统给 App 或 SurfaceFlinger 预留的工作区间);
  • actual timeline 和 jank classification(该帧晚到的原因分类);
  • 对应的 application frame(应用生产侧记录) 与 SurfaceFlinger frame(系统合成侧记录) 记录;
  • 当前刷新率、VSync 周期和 deadline。

帧率描述的是一段时间的吞吐量;定位单帧时,我们以时间线和 deadline 为主。

12.2 第二步:检查应用主线程

关注:

  • Choreographer doFrame 是否启动过晚;
  • INPUT、ANIMATION、TRAVERSAL 哪一段占用时间;
  • performTraversals 内是否发生 measure、layout、draw;
  • Binder、锁、I/O、GC 或调度延迟是否阻塞主线程;
  • requestLayout() 或 invalidate() 是否异常频繁。

主线程很早完成时,我们还要继续检查 RenderThread 与后续阶段。

12.3 第三步:检查 RenderThread 和 GPU

关注:

  • DrawFrame / syncAndDrawFrame 的同步等待;
  • dequeue 是否等待可用缓冲区;
  • Skia 绘制与 swapBuffers;
  • GPU queue、GPU completion(完成时间)与相关 fence;
  • 资源上传、shader(GPU 着色程序)/pipeline(图形 API 的渲染状态与处理阶段组合) 编译和缓存抖动。

RenderThread 的 CPU slice 长度说明的是 CPU 侧这段工作;GPU 侧要看 GPU 实际执行时长(GPU duration),二者可能重叠,也可能通过 fence 形成依赖。

12.4 第四步:检查 BufferQueue 与 BLAST

沿目标 Surface 查看:

  • dequeue、queue、acquire、release 的时间;
  • slot 和 frame number 是否对应;
  • acquire fence 何时 signal;
  • BufferTX - layerName 是否积压;
  • BLAST transaction 何时进入 SurfaceFlinger;
  • 队列深度是否带来背压或延迟。

一个进程里有多条 Surface 时,我们先确认 Layer 名称与生产者,避免把 SurfaceView、视频或宿主窗口混在一起。

12.5 第五步:检查 SurfaceFlinger 与 HWC

关注:

  • 事务应用与 Layer 快照;
  • 目标缓冲区是否按期 latch;
  • SurfaceFlinger 是否错过自己的 deadline;
  • HWC validate 返回的 composition type;
  • 是否发生 RenderEngine client composition;
  • present fence 和 release fence 的时间;
  • 多显示器、刷新率切换或显示模式变化。

SurfaceFlinger 的预算要按当前显示模式、调度相位、预测和 FrameTimeline 现算;“只剩几毫秒”这类固定值,套不上真实的调度场景。

12.6 常见错误推断

观察不能直接推出还需要的证据
已确认 traversal 回调执行完整 measure/layout/draw 都已执行实际 measure/layout/draw 区间、布局标志、脏区域
syncAndDrawFrame 返回GPU 或显示已完成GPU fence、queueBuffer、FrameTimeline
queueBuffer 成功画面已经显示BLAST/SF latch、HWC present
GPU Track 超过一个周期该工作必然导致当前帧掉帧GPU 依赖、frame id、deadline
BufferTX 增加App BufferQueue 一定塞满对应 Layer 事务与 BQ slot 状态
HWC 使用 CLIENT一定是 Layer 太多每层 composition reason 与设备能力
present fence signal面板像素完成响应面板扫描和硬件测量数据

13. 一个可复用的排查例子

假设列表滑动时出现一次明显停顿,我们按下面的顺序检查:

  1. 在 FrameTimeline 中锁定实际晚到的帧,不要只找最长 slice。
  2. 查看应用 doFrame 是否晚启动;若晚启动,继续看 Runnable、Binder、锁、GC 和调度。
  3. 若 doFrame 按时启动,检查 INPUT、ANIMATION、TRAVERSAL 的耗时与条件分支。
  4. 若 UI 侧按时提交,检查 RenderThread 是否在同步、dequeue、绘制或 swap 阶段等待。
  5. 若应用已按时 queueBuffer,按 Surface 和 frame number 追踪 BLAST transaction。
  6. 检查 SurfaceFlinger 是否及时 latch,以及 acquire fence 是否满足。
  7. 检查 HWC composition type、RenderEngine 工作和 present 时序。
  8. 最终把根因归到“生产晚、GPU 完成晚、队列背压、系统合成晚或显示呈现晚”中的具体一项。

按这个顺序走,即使主线程出现一次长调用,我们也应把后续异步阶段检查完再下结论。

标准应用窗口的七段边界

Android 渲染性能远不止“主线程画图,再由 GPU 显示”这一句话。标准应用窗口至少包含:

  1. Choreographer 与 ViewRootImpl 驱动的 UI 遍历;
  2. RenderNode DisplayList 的录制与同步;
  3. RenderThread、Skia 和 GPU 生成 GraphicBuffer;
  4. BufferQueue 与 BLAST 提交缓冲区和事务;
  5. SurfaceFlinger 更新 Layer、latch 缓冲区并组织合成;
  6. HWC、RenderEngine 和显示设备完成 present;
  7. acquire、release、present fence 维护各阶段的安全依赖。

定位问题时,我们先确认输出拓扑,再按 frame id、Surface、BufferQueue slot、fence 和 FrameTimeline 逐段核对,区分应用生产晚、GPU 执行晚、队列背压、SurfaceFlinger 合成晚和显示呈现晚,避免用单个 slice 为整帧下结论。

14. 源码阅读索引

应用主线程与 HWUI Java 层

  • frameworks/base/core/java/android/view/View.java
  • frameworks/base/core/java/android/view/ViewRootImpl.java
  • frameworks/base/core/java/android/view/Choreographer.java
  • frameworks/base/core/java/android/view/ThreadedRenderer.java
  • frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
  • frameworks/base/graphics/java/android/graphics/BaseRecordingCanvas.java

HWUI native 层

  • frameworks/base/libs/hwui/RenderNode.h
  • frameworks/base/libs/hwui/RenderNode.cpp
  • frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
  • frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
  • frameworks/base/libs/hwui/Properties.cpp
  • frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp
  • frameworks/base/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
  • frameworks/base/libs/hwui/pipeline/skia/SkiaVulkanPipeline.cpp

BufferQueue 与 BLAST

  • frameworks/native/libs/gui/BufferQueueCore.cpp
  • frameworks/native/libs/gui/BufferQueueProducer.cpp
  • frameworks/native/libs/gui/BufferQueueConsumer.cpp
  • frameworks/native/libs/gui/BLASTBufferQueue.cpp

SurfaceFlinger、HWC 与 RenderEngine

  • frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
  • frameworks/native/services/surfaceflinger/Layer.h
  • frameworks/native/services/surfaceflinger/FrontEnd/
  • frameworks/native/services/surfaceflinger/Scheduler/
  • frameworks/native/services/surfaceflinger/DisplayHardware/HWComposer.cpp
  • frameworks/native/services/surfaceflinger/DisplayHardware/HWC2.cpp
  • frameworks/native/services/surfaceflinger/DisplayHardware/ComposerHal.h
  • frameworks/native/libs/renderengine/

版本演进改变了哪些责任边界

主路径稳定之后,版本差异就要落到具体接口和线程上看。Project Butter、RenderThread、Treble、BLAST 和 FrameTimeline 分别改变了帧调度、绘制和观测方式。

Android 渲染史不适合只记成一串版本号。拿到一份 Perfetto,我们要能回答三个问题:

  1. 这一版由哪个线程记录 View 绘制命令,又由哪个线程准备和提交 GPU 工作?
  2. App buffer 经过哪种队列与事务交给 SurfaceFlinger,页面里是否还有独立 Surface?
  3. 当时有哪些 VSync、帧截止时间(deadline)和 jank 数据可以使用?

这三条线的变化速度不同:Android 5.0 增加 RenderThread,页面仍要经过 SurfaceFlinger 显示;Android 11 引入 BLAST,BufferQueue 依然存在;Android 12 增加 FrameTimeline,Trace 里标红的卡顿帧也还是要一帧一帧去查根因。

本节的旧版本只用来解释每条线从哪里来;当前的类名与行为,以 Android 17 的实现为准。

三个版本节点改变三种不同责任边界

图:RenderThread 改变 HWUI 的执行线程分工,BLAST 改变窗口 buffer 与事务的配合,FrameTimeline 增加帧之间的关联和结果观测。后者加入后前面的机制仍然存在;设备是否启用还需核对版本分支和条件。

时间线速查

先约定表中的缩写:HWUI 是 Android 的 View 硬件加速渲染管线;NDK 是供 C/C++ 应用使用的 Native Development Kit;BLAST 把 buffer 更新与 Surface transaction 按帧组织。

FrameMetrics 按 Window 报告一帧各阶段的时间,FrameTimeline 则关联应用帧、显示帧及其预期与实际时间;AGSL 是 Android Graphics Shading Language;ARR 是 Adaptive Refresh Rate(自适应刷新率);ANGLE 是把 OpenGL ES 调用映射到其他图形 API 的兼容层;WebGPU 是独立发布的现代 GPU 接口。

HAL 指 framework 与厂商硬件实现之间的接口,QPR 是 Android 的季度平台更新。launch device 指出厂时就搭载该 Android 版本的设备,不是后来通过 OTA 升级到该版本的设备。

版本已验证的里程碑分析 Trace 时的影响
Android 3.0 / API 11View 2D 硬件加速、显示列表模型、View.setLayerType()同一个 Canvas API 可能走软件或 HWUI 路径
Android 4.0 / API 14对 target API 14 及以上应用默认启用硬件加速不能只按设备版本判断某个 Window 是否硬件加速
Android 4.1 / API 16Project Butter 流畅性工程、Choreographer、VSync 驱动的 UI 节拍、三重缓冲策略帧工作开始与显示节拍建立明确关系
Android 5.0 / API 21HWUI RenderThreadUI 线程记录与同步、RenderThread 执行需要分开看
Android 6.0 / API 23Choreographer 增加 COMMIT 回调阶段现代回调顺序开始接近当前形态
Android 7.0 / API 24Vulkan NDK API、FrameMetrics原生渲染多一种底层 API;App 可按 Window 取得帧阶段数据
Android 8.x / API 26–27AOSP HWUI 可通过 debug.hwui.renderer 选择 SkiaGL/SkiaVulkan,未设置时仍使用旧 OpenGL renderer看到 Skia 后端类不等于该版本默认启用
Android 9 / API 28AOSP 将 debug.hwui.renderer 的默认值改为 skiagl不要把旧 OpenGLRenderer 类名套到 Android 9 以后的 AOSP 默认主路径
Android 11 / API 30主窗口路径开始使用 BLASTBufferQueuebuffer 与 SurfaceControl transaction 的关系更紧
Android 12 / API 31FrameTimeline;FrameMetrics 增加 GPU duration 与 deadline可关联 App SurfaceFrame 和 DisplayFrame,并区分 CPU/GPU/显示责任
Android 13 / API 33Choreographer.FrameData / FrameTimeline 公共 API;AGSL RuntimeShaderApp 可获取候选帧时间线;Canvas 增加运行时 shader
Android 15 / API 35ARR 在支持 Android 15 QPR1 与相应 HAL 的设备上可用;ANGLE 成为可选的 GLES-on-Vulkan(把 OpenGL ES 调用映射到 Vulkan)层VSync 间隔和内容提交帧率不能再假设固定;GLES 后端需看设备选择
Android 16 / API 36Display.hasArrSupport()、getSuggestedFrameRate(category);RuntimeColorFilter / RuntimeXfermode;实现文档列出 Vulkan 1.4App 能查询 ARR;AGSL 可用于滤镜与混合;Vulkan 兼容要求需另核对 CDD,不能由版本号推定 1.4
Android 17 / API 37Jetpack WebGPU;GLES prefer_angle manifest 请求;Display.getFrameRateVelocityMapping()WebGPU 增加现代 GPU 接口但仍需独立依赖;ANGLE 仍是偏好请求;滚动帧率可结合速度映射

表中“引入”标记的是平台或 API 的版本分界;升级到该版本的设备是否拥有同样的 GPU、显示 HAL、驱动能力和默认后端,还要逐台确认。

Android 3.0–4.0:View 硬件加速成为标准路径

Android 3.0:HWUI 与显示列表

Android 3.0 以前,普通 View 的 Canvas 主路径使用 Skia 软件光栅化。应用仍可以通过 OpenGL ES 等 API 自行使用 GPU,“Android 2.x 所有图形都由 CPU 绘制”的说法只覆盖了默认路径。

API 11 开始,Android 2D View 管线支持硬件加速。硬件加速 Window 中,View 的绘制操作会记录到显示列表,也就是一组可由渲染管线重复执行的绘制指令;未失效的 View 可以复用已有记录,位置、缩放、旋转或 alpha 等属性也可以作为 RenderNode 状态处理。RenderNode 是 HWUI 保存绘制指令与合成属性的节点。早期实现与现代 RenderNode、RenderThread 架构不同,拿 Android 17 的类关系去解释 Android 3.x 的行为会出错。

我们在这里要区分两件事:

  • View.draw() / onDraw() 的 Java 调用负责描述绘制;
  • GPU 何时把这些命令光栅化为像素,取决于 HWUI 后续回放、buffer 和驱动调度。

按显示列表模型,canvas.drawRect() 这类调用先进入记录,GL 命令何时执行由后续回放决定。

API 11 同时提供 View.setLayerType()。LAYER_TYPE_HARDWARE 可以把稳定内容放入硬件层,便于后续做合成属性动画;它会占用图形内存。内容失效后仍要重新绘制并更新 layer,尺寸或渲染上下文变化时还可能重新分配;要不要长期给所有 View 强制开启,先把这笔图形内存的账算进去。

Android 4.0:默认值与 target API 有关

硬件加速从 API 11 可用,从 target API 14 起默认启用;target API 是应用声明自己已适配的 Android API 级别。以下两种情况下都可能出现软件 Canvas:

  • 应用或 Activity 显式关闭硬件加速;
  • 硬件加速 View 被绘制到 Bitmap 等软件 Canvas。

自定义 View 判断当前绘制路径时,应看 Canvas.isHardwareAccelerated()。View.isHardwareAccelerated() 只表示 View 附着的 Window 是否硬件加速;软件 Canvas 则由 CPU 把绘制结果写入 Bitmap 等内存目标。

Android 4.1:Project Butter 建立 VSync 驱动的帧节拍

Project Butter 是 Android 4.1 面向交互流畅度的一组系统改进,它把输入、动画和 View traversal(View 树的测量、布局、绘制)放到统一的帧节拍中,也包含触摸响应优化。Choreographer 在 API 16 成为公共 API,早期 AOSP 的回调队列如下:

INPUT → ANIMATION → TRAVERSAL

Android 6.0 加入 COMMIT(帧遍历后的提交阶段),后续又加入 INSETS_ANIMATION(Insets 动画阶段)。Android 17 的顺序如下:

INPUT → ANIMATION → INSETS_ANIMATION → TRAVERSAL → COMMIT

这些队列运行在 Choreographer 所属的 Looper 线程;Looper 是从消息队列中持续取出并分发任务的线程循环。应用主 ViewRoot 通常在主线程上,但 native engine(C/C++ 渲染引擎)、SurfaceFlinger 客户端或其他 Looper 也可以拥有不同用途的 Choreographer。

VSYNC-app 与 VSYNC-sf 的调度含义

Perfetto 里的 VSYNC-app、VSYNC-sf 或相关预测轨道描述的是调度节拍:现代系统根据硬件 VSync 样本、显示模式与工作时长,预测 App 和 SurfaceFlinger 的唤醒时间;把它们逐条解释成“应用直接收到一次硬件中断”,会出偏差。

旧 AOSP 资料常出现 DispSync,Android 17 的主线使用 Scheduler 与 VsyncPredictor 等组件。内部预测器跨版本持续变化,我们分析 Trace 时关注本帧的 expected/actual timeline(预期与实际时间线)、deadline 和 present 就够了,不要靠预测器类名判断问题。

三重缓冲与流水线容量

Project Butter 将三重缓冲作为流畅性策略之一。它允许 Producer、GPU 与 Consumer 在更多情况下并行推进,减少“前一块 buffer 未释放,下一帧无处可画”的概率。我们把这里的“三重缓冲”理解成管线并行策略就好,具体 BufferQueue 怎么配置还是要单独查。

三重缓冲留下的余量也有限度:它并没把所有 Surface 固定成三个 slot(缓冲区槽位),卡顿之后也可能还有额外延迟。现代 BufferQueue 的 slot 数、dequeue 上限、异步模式和使用者约束共同决定可用容量。Producer 持续快于显示消费时,队列会积压,FrameTimeline 可能标记 buffer stuffing(缓冲区持续积压,旧帧尚未及时消化),用户看到的输入延迟也会增加。

Android 5.0–6.0:RenderThread 接管 HWUI 的渲染执行

Android 5.0 的 libs/hwui/renderthread/RenderThread.cpp 已包含独立 RenderThread。该版本源码中的普通硬件加速 View 路径为:

  1. UI 线程执行输入、动画、measure、layout,并更新失效 View 的显示列表;
  2. ThreadedRenderer 通过 native RenderProxy(Java 与 native 渲染器之间的代理)把帧任务交给 RenderThread;
  3. DrawFrameTask::syncFrameState() 准备 RenderNode 树和资源状态,DrawFrameTask::run() 随后按条件解除 UI 线程等待;
  4. RenderThread 上的 CanvasContext 使用当时的 OpenGLRenderer 回放 RenderNode,提交 OpenGL ES 绘制;
  5. EglManager::swapBuffers() 经 EGL 提交窗口缓冲区,后续由 BufferQueue、SurfaceFlinger 和显示栈处理消费与合成。

对应实现见 Android 5.0 的 DrawFrameTask.cpp 和 CanvasContext.cpp。这一阶段的 HWUI 使用旧 OpenGL renderer;Skia GPU 后端和 BLAST buffer transaction 分别属于后续的版本变化。

主线程与 RenderThread 的衔接比“首尾相接的两段”更松散:同步阶段必须处理本帧状态,条件满足时,UI 线程可以在 RenderThread 完成本帧 GPU 工作前继续运行;阻塞点在不同 Android 版本与场景里也各不相同。

分析现代设备的 Perfetto 时,DrawFrame 很长只能说明 RenderThread 这段跨度较长。它可能包含:

  • RenderThread 的 CPU 工作;
  • RenderNode 或资源同步;
  • shader / pipeline 准备与纹理上传;
  • dequeueBuffer、fence 或 buffer back-pressure(下游不释放 buffer,反压上游);
  • GPU 命令提交与 GPU 完成等待。

要进一步确认 GPU 工作量,要用 GPU slices(Trace 中记录 GPU 工作的时间片)、completion fence、FrameMetrics.GPU_DURATION、设备 counter(硬件计数器)或 Android GPU Inspector(AGI)。

RenderThread 动画有明确范围

RenderNodeAnimator 和基于 CanvasProperty(渲染线程可更新的绘制属性)的部分动画可以在 RenderThread 上推进。普通 ValueAnimator、大部分 ObjectAnimator 和业务状态更新仍由 UI 线程的 Choreographer 驱动。所以看到 RenderThread 存在,主线程卡住时动画未必都能继续。

Android 5.0 的 Choreographer 源码仍有 INPUT、ANIMATION、TRAVERSAL 三类回调;Android 6.0 增加 COMMIT。这个差异会影响阅读早期源码,但不改变 RenderThread 由 HWUI 自己管理这一事实。

Android 7.0–10:观测 API 与 GPU 后端扩展

这一阶段还有两处结构变化。Android 8.0 的 Project Treble 把 Android framework 与 vendor 实现之间的接口进一步标准化,HWC HAL 的系统侧与厂商侧分工更清晰,设备合成能力仍由具体硬件与 vendor 实现决定。Android 10 / API 29 的 SurfaceControl 事务和渲染相关公开 API 继续扩展,系统合成与应用内容提交的关系更容易通过 trace 观察。

Android 7.0:FrameMetrics

API 24 增加 Window.addOnFrameMetricsAvailableListener() 和 FrameMetrics。FrameMetrics 按 Window 报告 UI/HWUI 一帧在各阶段的时间里程碑:

指标主要含义
UNKNOWN_DELAY_DURATIONUI 线程未及时开始处理帧的等待
INPUT_HANDLING_DURATION输入回调
ANIMATION_DURATION动画回调
LAYOUT_MEASURE_DURATIONmeasure/layout
DRAW_DURATION记录或更新显示列表
SYNC_DURATION显示列表与 RenderThread 同步
COMMAND_ISSUE_DURATION向 GPU 发出绘制命令
SWAP_BUFFERS_DURATION把帧 buffer 交给显示子系统
TOTAL_DURATION从 intended VSync(该帧原本对应的 VSync)到 frame completed(应用侧完成该帧)的跨度

这些阶段可能重叠或并行,TOTAL_DURATION 是完整跨度,把其他 duration 直接相加得不到它;COMMAND_ISSUE_DURATION 长说明的是 CPU 侧发命令的阶段长,GPU execution 要另外看。

API 31 又增加:

  • GPU_DURATION:该帧在 GPU 上完成所需时间;
  • DEADLINE:系统分配给 App 生产该帧的时间预算。

API 36 增加 FRAME_TIMELINE_VSYNC_ID,可与 compositor(SurfaceFlinger 等显示合成组件) 的帧时间线数据关联。Listener 回调还会给出自上次回调以来丢弃的报告数量;FrameMetrics 对象会被重复利用,需要跨线程保存时应先复制。

Android 7.0:Vulkan 进入 NDK

Vulkan 从 API 24 可供 native 应用使用。它减少驱动隐式管理,把 command buffer(记录并提交 GPU 命令的对象)、资源同步和内存管理交给应用显式控制。代价是同步、生命周期和跨设备能力判断更复杂。

Vulkan API 可用只覆盖“公共 API”这一层;应用自带的 Vulkan renderer、HWUI SkiaVulkan、ANGLE-on-Vulkan 是三条不同路径,View/HWUI 走哪条要分别确认。

Android 8.x–9:HWUI 转向 Skia GPU 后端

早期 HWUI 自己维护大量 OpenGL renderer 逻辑。Android 8.0/8.1 的 Properties.cpp 已识别 skiagl 和 skiavk,但 debug.hwui.renderer 未设置时仍回退旧 OpenGL renderer;Android 9.0 的同一文件把属性默认值明确改为 skiagl。绘制 API 仍是 View/Canvas/RenderNode,底层由 HWUI 直接管理 GL 的路径逐步转向 Skia GPU backend(连接图形 API 与驱动的后端)。

Android 17 的 libs/hwui/pipeline/skia/ 仍有 SkiaOpenGLPipeline、SkiaVulkanPipeline 和公共 SkiaGpuPipeline。设备按产品配置、驱动和调试设置选择后端;判断某台设备走哪个 backend,查它的实际配置比看 Android 版本可靠。

Skia 项目中的 Graphite 是新一代 GPU 后端研发方向。android-17.0.0_r1 的 HWUI pipeline 目录没有 Graphite pipeline 或默认启用路径,因此不将它列为 Android 平台里程碑。

Vulkan 版本要求要看 launch-device 条件

官方 Vulkan 实现文档给出的主要版本阶段是:

  • Android 7:Vulkan 1.0;
  • Android 9:Vulkan 1.1;
  • Android 13:Vulkan 1.3;
  • Android 16:Vulkan 1.4。

上面列的是实现文档的 API 版本对应关系,同期设备的最低兼容要求要看 CDD 条款和设备上报的能力。该文档的 Choose Vulkan support 又要求 64 位、非 low-memory(低内存设备)的 Android 16 launch device 支持 Vulkan 1.4,但这一表述与 Android 16/17 CDD 没有对齐,具体条款差异见下文。新机或 OTA 升级设备是否具备 Vulkan 1.4,仍要按设备各自上报的版本来确认。

应用应查询 FEATURE_VULKAN_HARDWARE_VERSION、extension(扩展能力)、profile(成组定义的一套 Vulkan 能力)与 dEQP level(图形兼容性测试覆盖级别)。实现文档还注明:Android 14 及以上支持 Vulkan 的设备需要 Vulkan 1.1 驱动,并满足 Android Baseline 2021 profile;较新版本的 launch-device 条件面向新机,升级设备的兼容要求依旧适用。

VP_ANDROID_16_minimums、Android Vulkan Baseline Profile 和 Compatibility Definition 解决的问题不同:前两者描述成组的 Vulkan 能力,CDD(兼容性定义文档)规定设备兼容要求,应用的最低 Vulkan version/extension 则由自己的渲染器决定。这里的 Vulkan Baseline Profile 与用于优化应用启动的 ART Baseline Profile 不是同一个概念。

Android 11:BLAST 改变 buffer 与 transaction 的配合

Android 11 的 AOSP 已包含 BLASTBufferQueue.cpp,ViewRootImpl 也已有由系统标志控制的 BLAST 接入分支;具体设备、具体 Surface 类型是否启用,还要按实际观察确认。BLAST 把窗口 buffer 与几何属性等更新放进同一帧的 Surface transaction。

它没有删除 BufferQueue。BLAST 内部仍使用 Producer/Consumer buffer queue,并把 buffer update 变成 SurfaceControl::Transaction 的一部分,便于把 buffer、frame number、crop(裁剪范围)、transform(旋转、缩放等几何变换) 和窗口几何状态按帧关系提交给 SurfaceFlinger。

现代 App Window 的简化路径是:

RenderThread
  → Surface / BufferQueue producer
  → BLASTBufferQueue consumer
  → Transaction::setBuffer() / apply()
  → SurfaceFlinger FrontEnd

这条路径说明 BLAST 位于 App Producer 与 SurfaceFlinger transaction 之间。queueBuffer() 只表示 Producer 提交了一块 buffer;SurfaceFlinger 还要收到 transaction、检查 acquire fence、选择并 latch buffer,随后完成本轮显示合成。

Android 12 以后 BLAST 覆盖更多窗口与 Surface 场景,但旧 BufferQueue 类型和非 BLAST 队列继续存在。我们在 Perfetto 中按 Layer、connection(Producer 与 Consumer 的队列连接)、transaction 和 buffer id 识别对象,不要只搜索某个固定 slice 名称。

Android 12–14:FrameTimeline 把 App 帧与显示帧关联起来

Android 12:SurfaceFrame 与 DisplayFrame

FrameTimeline 在系统侧维护 App SurfaceFrame 与 SurfaceFlinger DisplayFrame 的 expected/actual timing(预期与实际时间)。SurfaceFrame 同时记录正常帧和卡顿帧,是否卡顿由 jank type 等分类信息表达:

  • SurfaceFrame 观察应用向某个 Layer 提交的一帧是否按选定时间线完成;
  • DisplayFrame 观察 SurfaceFlinger/HWC 组织的一次显示帧是否按时 present;
  • VSyncId / token 是跨轨道关联 App 帧和显示帧的标识;
  • jank type 是系统根据时间关系给出的卡顿分类,用于区分 App deadline、SurfaceFlinger CPU/GPU、Display HAL、prediction error 和 buffer stuffing(Producer 提交过快导致 buffer 堆积) 等方向。

Actual Timeline 上的迟到帧是诊断入口。Perfetto 文档说明 App 帧结束会考虑 buffer post(应用提交 buffer) 和 GPU completion;SurfaceFlinger 侧还可能因 Layer readiness(Layer 内容尚未满足合成条件)、client composition、HWC 或 present 迟到。

Perfetto 的 FrameTimeline 文档 标注该功能从 Android 12 起可用,并注明 SurfaceView 尚不支持。目标设备是否为某个独立 Surface 生成完整时间线,要按 Layer 和实际采集结果确认;缺少记录时,继续用 BufferQueue、事务与 fence 对照下游显示帧。

私有 TimelineItem、SurfaceFrame 和 token 保存策略会随版本调整。应用开发者应依赖 Perfetto schema(Trace 数据字段及其关系的定义)、公共 API 和目标版本源码;旧版本的私有结构体定义随时会变,只适合当阅读材料。

Android 13:App 可以读取候选帧时间线

API 33 增加:

  • Choreographer.postVsyncCallback();
  • Choreographer.FrameData;
  • Choreographer.FrameTimeline;
  • getDeadlineNanos()、getExpectedPresentationTimeNanos() 和 getVsyncId()。

一份 FrameData 可以包含多个候选 timeline,并标出平台偏好的 timeline;不同候选项代表系统允许应用瞄准的不同呈现时刻。native 应用有对应的 AChoreographer frame callback data API,可以为 Surface transaction 选择 frame timeline。

这些 API 提供的是时间目标;应用是否真的在 deadline 前完成,还是要看 buffer/fence/present 的直接观测。

Android 13–16:AGSL 扩展 Canvas 效果

Android 13 的 RuntimeShader 让应用用 Android Graphics Shading Language(AGSL)编写运行时 shader,并作为 Canvas Shader 使用。Android 16 增加 RuntimeColorFilter 和 RuntimeXfermode,把 AGSL 扩展到颜色过滤和 source/destination(新绘制内容与目标中已有内容) 混合。

AGSL 属于 Canvas/HWUI 效果接口。复杂 shader、离屏 layer、模糊或多个 render pass(围绕一个渲染目标组织的一组 GPU 绘制) 的成本仍由实际内容与 GPU 决定,API 版本本身不提供性能保证。

Android 15–16:ARR、ANGLE 与 Vulkan 1.4

ARR 改变了“固定 VSync 间隔”的假设

ARR 从 Android 15 开始提供,官方文档把可用条件写为支持相应 HAL 且运行 Android 15 QPR1 及以上。它可以让显示刷新节奏按内容 render rate(应用产生新内容的帧率) 以离散的 VSync 步进变化,减少不必要的高刷新率驻留和 mode switch(在不同显示模式之间切换)。

Android 16 / API 36 增加 Display.hasArrSupport() 和 getSuggestedFrameRate(category)。后者要求传入 Display.FRAME_RATE_CATEGORY_NORMAL 或 Display.FRAME_RATE_CATEGORY_HIGH,返回显示配置建议的帧率;查询本身不提交帧率请求。应用还可以通过 View、Surface、ANativeWindow 的 frame-rate API 提交帧率偏好。系统会综合同一显示上的多个 View、Layer、系统 UI、触摸和功耗策略;API 调用成功只表示请求已被接收,不保证显示器立刻切到指定 Hz。

在 ARR 设备上看到 8.33 ms、16.67 ms 或更长的 VSync 间隔变化时,我们先查选定时间线、deadline、requested frame rate(应用请求的帧率) 和 active display mode(当前生效的显示模式),再判断它是不是异常。

ANGLE 是可选 GLES-on-Vulkan 路径

Android 15 起,ANGLE 作为可选兼容层把 OpenGL ES(GLES)调用映射到 Vulkan。它能改善兼容性,并可能改变 CPU/GPU 工作分布,但性能结果取决于设备、驱动和负载。

GLES 应用、原生 Vulkan 应用和 HWUI 页面是三类对象,要分开对待;ANGLE 是否启用,也回答不了应用源码有没有迁移到 Vulkan API 的问题。

Android 16 launch device 的 Vulkan 1.4

Implement Vulkan 写有 Android 16 及以上 launch device 的 Vulkan 1.4 要求,但截至 2026-10-22,Android 16 CDD §7.1.4.2 和 Android 17 CDD 的同一节 都要求支持 Vulkan 的设备为每个枚举出的 VkPhysicalDevice 完整实现 Vulkan 1.1(C-1-3);对支持 GLES 3.1 或带屏幕/视频输出的设备,Vulkan 1.3 是 STRONGLY RECOMMENDED(强烈推荐,C-SR-1 / C-SR-2)。这两节没有把 Vulkan 1.4 写成 MUST(强制要求)。

两版 CDD 的 §2.2.1 还对支持任一 64 位 ABI、且 ActivityManager.isLowRamDevice() 返回 false 的手持设备规定 Vulkan 1.1 要求(7.1.4.2/H-2-1),Android 17 的措辞为“1.1 或更高”。因此,实现文档的 1.4 表述与 CDD 已确认的统一最低版本,应当分开看待。GPU 驱动由 SoC 厂商或 IHV(独立硬件供应商)提供,OTA 也不会自动改变硬件能力;应用要根据设备上报的 Vulkan 版本、扩展和所需 Vulkan feature set(功能与限制的能力集)决定能否启用渲染器。

Android 17:WebGPU、ANGLE 偏好与滚动帧率映射

WebGPU

Android 17 的发布说明把 WebGPU 列为图形新能力。公开接口来自独立发布的 Jetpack androidx.webgpu:webgpu,不属于 android.* framework API。截至 2026-10-20,发布页列出的版本为 2026-10-07 发布的 1.0.0-alpha06;alpha 表示仍在早期预览阶段,接口可能变化。

独立库的适用范围要按依赖版本确认。alpha06 将最低 SDK 从 API 24 提高到 API 26(Android 8.0);该库的最低系统要求按依赖版本确认,与 Android 17 的发布归类是两回事。该版本还移除了描述符和配置结构构造函数上的 @JvmOverloads;依赖旧构造函数重载的 Java 代码需按迁移说明改用嵌套 Builder。升级前应先检查应用的 minSdk 与这些调用点。

alpha05 与 alpha06 的发布说明 均提示:库文档由 Google Gemini 生成,可能有误。涉及具体 API 时,应同时核对所用版本的源码与官方 WebGPU 规范;alpha06 的 GPUSurface 源文件也保留了这条提示。

该库提供 Kotlin/Java 绑定,并以比 Vulkan 更高层的对象组织工作:adapter 表示可选 GPU 实现,device 是应用取得的逻辑 GPU 设备,queue 接收提交,command buffer(批量记录并提交 GPU 命令的对象) 保存待执行命令,WGSL 是 WebGPU Shading Language。

WebGPU 不会让 View/Compose、WebView 或现有 GLES 应用自动换后端。分析使用 WebGPU 的应用时,应把它按独立 GPU API 和工作提交路径处理:离屏 compute(通用 GPU 计算)只跟踪 buffer、texture 与 queue;绘制到屏幕时,再沿 GPUSurface 的 current texture(当前可供渲染的一张交换链图像)、present()、目标 ANativeWindow、BufferQueue 和最终 SurfaceFlinger Layer 追踪。

prefer_angle 只表达偏好

Android 17 起,游戏可以在 manifest 中请求优先使用 ANGLE 作为 GLES driver。下面配置的作用只是声明偏好:

<application android:appCategory="game">
    <meta-data
        android:name="com.android.graphics.driver.prefer_angle"
        android:value="true" />
</application>

平台用不了 ANGLE 时会回到厂商 GLES driver。排查问题时我们把实际 renderer/driver 记下来,manifest 只是偏好的起点。

API 37 的 frame-rate/velocity mapping

Display.getFrameRateVelocityMapping() 返回当前 Display 的滚动速度阈值与可行 frame rate 组成的只读、非空映射。例如映射里的一个点可以表达“速度超过 300 dp/s(每秒移动 300 个密度无关像素) 时使用 120 fps”。官方契约主要面向 RecyclerView、ScrollView、AbsListView、NestedScrollView 等 fling(惯性滚动)场景。设备从内屏切到外屏,或收到 DisplayListener.onDisplayChanged() 后,需要针对当前 Window 所在 Display 重新查询。

这些点只适用于当前 Display,属于策略输入,系统不会据此替 App 自动完成帧率切换。调用方仍要按速度选择映射点,并通过 View、Surface 或其他 frame-rate API 表达请求;列表也仍需在每个选定 deadline 前完成 UI、RenderThread、GPU 与 buffer 提交。

Android 17 的标准 HWUI 主线

回到当前版本的普通 View/Compose App Window,我们按以下对象分析:

Choreographer / UI Thread
  → View traversal + RenderNode display list
  → HardwareRenderer / RenderProxy
  → RenderThread / DrawFrameTask / CanvasContext
  → SkiaOpenGL or SkiaVulkan pipeline
  → App Window BLAST buffer transaction
  → SurfaceFlinger FrontEnd RequestedLayerState / LayerSnapshot
  → CompositionEngine / HWComposer / Composer3
  → display present

页面有 SurfaceView、TextureView、视频、Camera、WebView、Flutter、游戏或 React Native 时,要先确认 Producer、Consumer 与最终 Layer。SurfaceView 常有独立 child Surface;TextureView 会先被宿主 HWUI 采样进 App Window;Surface 拓扑要按实际页面确认,框架名本身给不出答案。

这里的 child Surface 是挂在宿主窗口层级之下、拥有自己 buffer 提交路径的 Surface。主线末端的 CompositionEngine 为每个 Display 组织可见 Layer,HWComposer 再通过 Composer HAL 与显示硬件协商并 present。

怎样用版本信息读 Perfetto

1. 记录设备与构建信息

记录:

  • Build.VERSION.SDK_INT、build fingerprint(系统构建版本的唯一标识)和 vendor image(厂商分区镜像);
  • app target SDK、图形 API 与实际使用的 renderer/driver;
  • display mode、刷新率、ARR 支持;
  • 页面里的 Window、SurfaceView、TextureView 和其他独立 Surface;
  • Perfetto/Android Studio/AGI 版本与采集配置。

Track 是 Perfetto 中按线程、计数器或数据源组织的时间轴,slice 是其中带起止时间的事件;atrace 是 Android 代码写入这类 Trace 事件的传统插桩接口。Track 名和 atrace slice 属于实现细节,厂商也会增加或改名。旧教程里的名字找不到了,机制本身往往还在。

2. Android 5.0+ 分开 UI 与 RenderThread

UI 线程慢时看 input、animation、traversal、measure/layout 和 display-list record。RenderThread 慢时继续分辨 CPU、资源、buffer、fence、GPU submit 和 completion。仅凭“哪个 slice 最长”是给不出 GPU/CPU 结论的。

3. Android 11+ 同时看 BLAST、BufferQueue 和 transaction

需要回答四个问题:

  1. Producer 何时 queue 哪个 frame number;
  2. BLAST 何时把 buffer 放入哪次 transaction;
  3. SurfaceFlinger 何时认为 transaction ready 并 latch;
  4. 对应 DisplayFrame 何时 present。

BufferTX - <layerName> 只表示 SurfaceFlinger server 侧待处理的 buffer transaction 数量发生了变化;屏幕有没有显示,要看后续的 present 记录。

4. Android 12+ 用 FrameTimeline 锁定帧

选中目标 janky SurfaceFrame(FrameTimeline 判定的卡顿帧)后,再跟到对应 DisplayFrame。App deadline missed、SF deadline missed 和 Display HAL 问题各有各的判断依据。多 Surface 页面只看宿主 App Window 的一条 timeline 会漏掉另一半。

5. Android 15+ 分开渲染帧率与显示刷新率

App 可能以 30 fps 更新,显示以 60/90/120 Hz 或 ARR 步进工作;多个 Layer 也可能按不同 cadence(内容更新与显示刷新的节奏关系)运行。帧是否准时,按选定的 timeline 和 deadline 判断就好;把 16.67 ms 当成所有设备、所有帧的通用预算,在刷新率可变的设备上会出错。

API、平台实现与设备能力是三层约束

层次例子正确检查方式
公共 APIFrameMetrics、FrameTimeline、WebGPU、hasArrSupport()看 API level、feature flag(可动态启停功能的开关)与官方契约
AOSP 平台实现RenderThread、BLAST、SurfaceFlinger FrontEnd、Skia pipeline看目标 tag(源码版本标签)的源码和系统属性
设备能力GPU/Vulkan extension、ANGLE、HWC plane、ARR HAL、counter看运行时查询、dumpsys(系统服务状态快照)、driver 与目标设备 Trace

“Android 17 支持某 API”只覆盖第一层;“AOSP 有某 pipeline”只覆盖第二层。具体手机是否启用、性能如何,仍由第三层决定。

Kernel 与厂商边界

kernel 侧的源码基线是 android17-6.18-2026-06_r6。其中 drivers/dma-buf/dma-buf.c 管理可在设备和进程间共享的 buffer 对象,dma-fence.c 定义异步任务的完成依赖,sync_file.c 把 fence 封装成可跨进程传递和等待的文件描述符;通用 kernel 还提供线程调度、内存回收和频率框架等基础机制。

HWUI backend、Vulkan/GLES driver、GPU job(提交给 GPU 的一组工作)调度、图形内存分配、DPU(显示处理单元)/HWC plane 与 ARR HAL 含有大量厂商实现。一个 framework 版本里程碑不保证 vendor driver 同步采用相同策略。Kernel fence 只能说明异步依赖是否完成;要解释迟到原因,还需找到 fence owner(负责让该 fence 完成的组件)、提交者和对应硬件工作。

源码与官方资料

历史版本锚点

Android 17 / android-17.0.0_r1

Kernel / android17-6.18-2026-06_r6

  • dma-buf.c:共享 buffer 对象与设备 attachment/map(设备附着与地址映射)边界。
  • dma-fence.c、sync_file.c:异步完成依赖与 sync_file fd(文件描述符)。

官方文档

常见误区

“BLAST 已经替代 BufferQueue”

BLAST 使用并管理 BufferQueue,把 buffer update 纳入 Surface transaction。现代系统里两者同时存在。

“RenderThread 的 DrawFrame 长就是 GPU 慢”

DrawFrame 跨度包含 RenderThread CPU、资源、buffer、fence 和 GPU 相关阶段。需要 GPU completion 或直接 GPU 证据。

“Android 16/17 设备都用 SkiaVulkan 或 Graphite”

Android 17 AOSP 提供 SkiaOpenGL 与 SkiaVulkan pipeline,产品选择与设备配置有关;当前 tag 没有 HWUI Graphite pipeline。

“Vulkan 1.4 要求适用于所有升级设备”

实现文档的 launch-device 表述与 CDD 条款要分开引用。Android 16/17 CDD 的 Vulkan 1.1 强制要求和 Vulkan 1.3 强烈推荐,与实现文档中的 1.4 要求没有对齐;新机和 OTA 升级设备是否支持 Vulkan 1.4,以各自上报的版本与扩展为准,系统版本号推不出来。

“ARR 让 VSync 随意变化”

ARR 按设备支持情况、各 View/Surface 提交的帧率偏好和离散步进工作。间隔变化可以是正常策略,也可能是 mode switch 或调度问题,需要结合 display mode 与 FrameTimeline。

“FrameMetrics 可以解释 SurfaceFlinger”

FrameMetrics 是 App Window 的渲染里程碑。SurfaceFlinger、HWC 和多 Layer 显示问题需要 Perfetto、layer state、composition type 与 present/fence 证据。