本档旨在从底层 AOSP 源码视角,拆解 JankStats 捕获的每一项耗时指标的物理来源,并精确到具体的类、函数及源码路径。
1. 全链路耗时概览图
一帧的产生经历了:Vsync 信号(Choreographer) -> UI 线程处理(MainThread) -> Sync 同步 -> RenderThread 处理 -> GPU 渲染 -> Buffer 交换。

mermaid 代码
sequenceDiagram
participant C as Choreographer
participant M as MainThread
participant R as RenderThread
participant G as Graphics
Note over C, M: 1 VSync (INTENDED_VSYNC / VSYNC_TIMESTAMP)
C->>M: VSync 信号触发
rect rgb(240, 240, 240)
Note right of M: UNKNOWN_DELAY_DURATION
M->>M: 2 do other work (Handler/Broadcast)
end
rect rgb(230, 255, 230)
M->>M: 3 handle input (INPUT_HANDLING_DURATION)
M->>M: 4 play animation (ANIMATION_DURATION)
M->>M: 5 measure & layout (LAYOUT_MEASURE_DURATION)
M->>M: 6 draw (DRAW_DURATION)
end
M->>R: 7 sync and upload (SYNC_DURATION)
rect rgb(230, 230, 255)
R->>G: 8 dequeue buffer (阻塞点)
R->>R: 9 execute
R->>G: 10 flush command (COMMAND_ISSUE_DURATION)
R->>G: 11 swap buffer (SWAP_BUFFERS_DURATION)
end
G->>G: 12 compose (系统合成)
Note over G: GPU_DURATION (GPU 异步执行耗时)
Note over C, G: TOTAL_DURATION (1-11 阶段总和)
流程详细说明与 FrameMetrics 参数对应
| 流程编号 | 流程名称 | 详细说明 | 对应 FrameMetrics 参数 |
|---|---|---|---|
| 1 | VSync | Choreographer 接收到硬件 VSync 信号,触发主线程。 |
INTENDED_VSYNC_TIMESTAMP (预期) / VSYNC_TIMESTAMP (实际) |
| 2 | do other work | 主线程在处理 UI 前执行的其他任务(如 Handler 消息、广播)。 | UNKNOWN_DELAY_DURATION |
| 3 | handle input | 处理触摸、按键等输入事件。 | INPUT_HANDLING_DURATION |
| 4 | play animation | 执行属性动画、帧动画等。 | ANIMATION_DURATION |
| 5 | measure & layout | 对 View 树进行测量和布局,确定每个控件的大小和位置。 | LAYOUT_MEASURE_DURATION |
| 6 | draw | 录制绘制指令 (DisplayList),将 Canvas 绘制转化为 Native 指令。 | DRAW_DURATION |
| 7 | sync and upload | 将主线程录制的绘制信息和资源(如 Bitmap)同步到渲染线程。 | SYNC_DURATION |
| 8 | dequeue buffer | 渲染线程从 BufferQueue 申请缓冲区。此处是著名的阻塞点。 |
(包含在 TOTAL_DURATION 中,或由 DequeueBufferDuration 统计) |
| 9-10 | execute & flush | 渲染线程回放绘制指令,调用 GPU 渲染并刷新指令队列。 | COMMAND_ISSUE_DURATION |
| 11 | swap buffer | 将渲染完成的 Buffer 提交给 SurfaceFlinger。 |
SWAP_BUFFERS_DURATION |
| 12 | compose | SurfaceFlinger 进行画面合成并显示到屏幕。 |
(属于系统层,不计入应用内耗时) |
FrameMetrics 详细指标参考表
| FrameMetrics 的常量值 | 含义 | 可能导致耗时多久的情况 |
|---|---|---|
UNKNOWN_DELAY_DURATION |
UI 线程延迟处理绘制任务的耗时 | 主线程消息队列中正在执行的非绘制任务耗时太久,导致开始执行绘制任务太晚 |
INPUT_HANDLING_DURATION |
输入事件处理函数的耗时 | 点击事件耗时太久 |
ANIMATION_DURATION |
动画回调函数的耗时 | 动画太多或者回调函数耗时太久 |
LAYOUT_MEASURE_DURATION |
整个 View 树的测量和布局的耗时 | 布局层级复杂,频繁改变尺寸或位置(比如复杂的属性动画) |
DRAW_DURATION |
布局绘制函数的耗时 | draw/onDraw 里执行了耗时操作 |
SYNC_DURATION |
主线程同步 DisplayList 到 RenderThread 的耗时 | 过度绘制,导致要更新的 DisplayList 过多 |
COMMAND_ISSUE_DURATION |
RenderThread 发送绘制命令到 GPU 的耗时 | 绘制内容复杂 |
SWAP_BUFFERS_DURATION |
将绘制的 buffer 交换到前台显示的耗时 | — |
TOTAL_DURATION |
绘制一帧的总耗时 | 前面任意一个阶段耗时,最终这个值都会变大 |
FIRST_DRAW_FRAME |
这帧是否是当前窗口的第一帧 | — |
INTENDED_VSYNC_TIMESTAMP |
预期接收到 VSync 信号(也就是这帧开始执行)的时间戳 | — |
VSYNC_TIMESTAMP |
真正接收到 VSync 信号的时间 | 主线程的耗时任务太多,会导致主线程响应 VSync 信号过慢 |
GPU_DURATION |
GPU 完成这帧绘制的耗时 | — |
其他核心参数解析
TOTAL_DURATION: 从预期 VSync 信号发出到帧绘制完成(Swap Buffer 结束)的总时间。即 1-11 阶段的总和。GPU_DURATION: GPU 执行渲染指令的实际耗时(从指令提交完成到 GPU 硬件执行完毕)。反映了 GPU 的真实压力。DEADLINE: 系统为该帧设定的截止时间。如果TOTAL_DURATION > DEADLINE,通常意味着发生了卡顿。FIRST_DRAW_FRAME: 标记该帧是否为窗口可见后的第一帧。由于需要初始化资源,首帧耗时通常较高。
2. 第一阶段:起跑线前的等待 (Schedule & Input)
核心指标:UNKNOWN_DELAY_DURATION(未知延迟时长)
- 物理意义:从系统产生 Vsync 信号到 UI 线程真正开始执行
doFrame任务的时间差。 - 精确路径:
- 起点:
DisplayEventReceiver收到底层信号,通过 JNI 回调发送消息到 Looper。- 源码:
frameworks/base/core/java/android/view/DisplayEventReceiver.java
- 源码:
- 关键函数:
Choreographer$FrameDisplayEventReceiver.run()被 Looper 唤醒,调用doFrame。- 源码:
frameworks/base/core/java/android/view/Choreographer.java
- 源码:
- 终点:
Choreographer.doFrame()开始执行。
- 起点:
- 计算逻辑:
UNKNOWN_DELAY = FrameInfo[HANDLE_INPUT_START] - FrameInfo[INTENDED_VSYNC] - 解析:
- INTENDED_VSYNC:理想的帧开始时间戳(Vsync 信号发生的时刻)。
- HANDLE_INPUT_START:UI 线程 Looper 实际分发到该渲染消息并开始处理的时刻。
- 参数价值:反映 UI 线程的消息队列积压情况。如果此值大,说明 UI 线程正在执行非 UI 的耗时任务(如长耗时的
Handler消息或过重的业务逻辑)。
3. 第二阶段:UI 线程的奔跑 (Logic & Construction)
核心指标:INPUT_HANDLING(输入处理) + ANIMATION(动画) + LAYOUT_MEASURE(布局测量) + DRAW(绘制)
- 物理意义:UI 线程执行业务逻辑并构建绘制指令的过程。
- 精确路径:
- Input Handling:
Choreographer.doCallbacks(Choreographer.CALLBACK_INPUT)->InputEventReceiver.consumeEvents()。- 源码:
frameworks/base/core/java/android/view/InputEventReceiver.java
- 源码:
- Animations:
Choreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION)-> 执行所有已注册 of theValueAnimator等回调。 - Layout & Measure:
Choreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL)->ViewRootImpl.performTraversals()。- performTraversals 核心步骤解析:
- relayoutWindow:通过 IPC 与 WindowManagerService 通信,获取或调整 Surface,确定窗口的物理大小(Frame Size)。
- **performMeasure()**:从顶层 DecorView 开始深度优先遍历 View 树。每个 View 根据父容器的
MeasureSpec计算自己的MeasuredWidth和MeasuredHeight。 - **performLayout()**:根据测量结果确定 View 在窗口中的最终位置坐标(Left, Top, Right, Bottom),并触发 View 的
onLayout()。 - **performDraw()**:启动绘制阶段,将 View 树的视觉信息转化为渲染指令。
- 源码:
frameworks/base/core/java/android/view/ViewRootImpl.java
- performTraversals 核心步骤解析:
- **Draw (Recording)**:
ViewRootImpl.performDraw()->ThreadedRenderer.draw()->View.updateDisplayListIfDirty()。- 源码:
frameworks/base/core/java/android/view/ThreadedRenderer.java - 注意:此处是在录制指令到 DisplayList,而非真正渲染。
- 源码:
- Input Handling:
- 参数价值:反映 布局复杂度 (Over-nesting) 和 主线程计算压力。
4. 第三阶段:交接棒 (Handoff / Sync)
核心指标:SYNC_DURATION(同步时长)
- 物理意义:UI 线程阻塞等待,将绘制指令和 Bitmap 资源同步给 RenderThread。
- 精确路径:
- Java 端入口:
ThreadedRenderer.nSyncAndDrawFrame()(JNI 调用)。- 源码:
frameworks/base/core/java/android/view/ThreadedRenderer.java
- 源码:
- Native 端核心:
CanvasContext::prepareTree()。- 源码:
frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
- 源码:
- 资源上传:在此阶段,新创建的 Bitmap 会被上传到 GPU 纹理缓存。
- Java 端入口:
- 计算逻辑:记录从 UI 线程发起同步请求到 RenderThread 成功接收并完成资源拷贝,且 UI 线程被唤醒的时间。
- 参数价值:Bitmap 优化的核心指标。如果 SYNC 耗时高,通常是由于该帧上传了过大的 Bitmap 或触发了大量的资源回收与重新分配。
5. 第四阶段:渲染线程的冲刺 (Recording & Issue)
核心指标:COMMAND_ISSUE_DURATION(指令发布时长)
- 物理意义:RenderThread 将 DisplayList 转换为真正的 GPU 指令(OpenGL/Vulkan)并提交给驱动。
- 精确路径:
- RenderThread 循环:
RenderThread::threadLoop()。- 源码:
frameworks/base/libs/hwui/renderthread/RenderThread.cpp
- 源码:
- 核心执行:
CanvasContext::draw()调用渲染管线。 - 渲染管线:
SkiaPipeline::renderFrame()(Android 8.0+ 默认使用 Skia)。- 源码:
frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp
- 源码:
- RenderThread 循环:
- 参数价值:反映 绘制指令的复杂程度。DrawCalls 越多、Path 路径越复杂、或者使用了复杂的 Shader,此项耗时越高。
6. 第五阶段:终点线 (GPU & Swap)
核心指标:SWAP_BUFFERS_DURATION(缓冲区交换时长)
- 物理意义:RenderThread 调用
eglSwapBuffers后,等待 GPU 渲染完成并交换缓冲区的阻塞时间。 - 精确路径:
- 核心函数:
CanvasContext::swapBuffers()。 - 底层调用:
EglManager::swapBuffers()->eglSwapBuffers()。- 源码:
frameworks/base/libs/hwui/renderthread/EglManager.cpp
- 源码:
- 系统反馈:底层 SurfaceFlinger 消费 Buffer 的速度(涉及 BufferQueue)。
- 核心函数:
- 解析:如果 GPU 还没算完,或者系统显示队列已满(存在背压),此调用会阻塞 RenderThread,直到有可用的 Buffer。
- 面试价值:反映 GPU 渲染压力 (Overdraw) 或 系统三重缓冲 (Triple Buffering) 的饱和度。也是衡量 SurfaceFlinger 消费压力的关键点。
7. 第六阶段:最终审判 (Judgment)
核心指标:TOTAL_DURATION(总耗时) & frameOverrunNanos(帧超时时间) (API 31+)
- 物理意义:一帧从计划开始到最终出屏全链路总耗时及其与截止日期的偏离量。
- 精确路径:
- Java 层:
android.view.FrameMetrics- 源码:
frameworks/base/core/java/android/view/FrameMetrics.java
- 源码:
- Native 层:
google::hwui::FrameInfo- 源码:
frameworks/base/libs/hwui/FrameInfo.cpp
- 源码:
- Java 层:
- 卡顿判定准则:
- 经典准则:
TOTAL_DURATION > 16.6ms(针对 60Hz)。 - **JankStats 准则 (API 31+)**:
frameOverrunNanos > 0。即该帧物理上错过了 VSync 的 Deadline(最准确的掉帧判定)。
- 经典准则:
Q&A
- Q:如果 UI 线程只用了 2ms,为什么还是掉帧了?
- A:通过
FrameMetrics分析,可能是UNKNOWN_DELAY极大(消息队列排队严重)或SWAP_BUFFERS阻塞严重(GPU 瓶颈或渲染队列满了)。
- A:通过
- Q:SYNC_DURATION(同步时长)过高怎么排查?
- A:重点检查该帧是否有大图加载、
ImageView设置了巨大的 Bitmap,或者是否在onDraw之外频繁触发了硬件资源更新。
- A:重点检查该帧是否有大图加载、
- Q:为什么硬件加速下,RenderThread 卡顿会影响 UI 线程?
- A:因为
SYNC阶段是同步的。UI 线程必须在CanvasContext::prepareTree期间等待 RenderThread 接收数据,只有等 RenderThread “接手”成功后,UI 线程才能继续处理下一个 Vsync。
- A:因为
Android 帧绘制耗时全链路源码溯源
本文档详细记录了从 Java 层到 Native 层的 Android 帧刷新全链路核心函数(Android 15),并对源码关键逻辑进行了中文注释。
一、 Java 层:UI 线程任务调度 (frameworks/base/core/java/android/view/)
1. 帧回调起点:Choreographer.doFrame()
这是主线程响应 VSYNC 信号的总入口。
1 | void doFrame(long frameTimeNanos, int frame, |
补充 VsyncEventData
1 | #pragma once |
接过来自底层 DisplayEventReceiver.cpp 传上来的“发令枪响时间” (frameTimeNanos)。
记录:把主线程此时此刻的“起跑时间” (startNanos) 记录下来。
UNKNOWN_DELAY_DURATION = startNanos - frameTimeNanos
2. 任务分发:Choreographer.doCallbacks()
这是 Choreographer 内部用于依次执行各阶段任务(输入、动画、遍历、提交)的核心逻辑。
1 | /** |
这在 Choreographer 的底层逻辑中是一个非常重要的保护机制,用于解决因严重的掉帧或主线程卡顿导致的 “逻辑时间偏差”。
1. 核心意图:为什么要进行这一步计算?
在 Android 的动画(Animation)和图形渲染中,所有计算都是基于 frameTimeNanos 这个基准的。计算逻辑通常如下:
$$\text{progress} = \frac{\text{currentTime} - \text{startTime}}{\text{duration}}$$
如果系统发生了严重的卡顿(例如主线程阻塞了 500ms),frameTimeNanos 依然停留在 500ms 之前。如果不进行校正,当代码恢复执行时,程序会使用这个“过时”的 frameTimeNanos。这会导致:
- 动画“跳变”:动画会瞬间计算出很大的 delta,导致元素直接从起点“瞬移”到终点,产生视觉上的闪烁。
- 物理引擎异常:如果你的代码里有基于物理模拟的逻辑,巨大的时间差会导致计算出的速度、位移爆炸。
2. 代码逻辑拆解
这段代码在 CALLBACK_COMMIT 阶段触发,其逻辑是在检测到极度严重掉帧时,强行将“当前的逻辑帧时间”重置到最近的一个有效时间点。
**
jitterNanos(抖动/延迟量)**:$$jitterNanos = now - frameTimeNanos$$
这是当前物理时间(
now)距离该帧预期执行时间(frameTimeNanos)的偏移量。如果该值很大,说明我们在这一帧上滞后了很多。**阈值判断 (
jitterNanos >= 2 \* frameIntervalNanos)**:这是为了避免误判。只有当延迟超过 2 个帧周期(在 60Hz 下约 > 33.3ms)时,才会介入。这说明系统已经错过了至少一个 VSYNC 信号,进入了严重的掉帧状态。
**重新对齐
frameTimeNanos**:Java
1
2final long lastFrameOffset = jitterNanos % frameIntervalNanos + frameIntervalNanos;
frameTimeNanos = now - lastFrameOffset;这里将
frameTimeNanos设置为now减去一个偏移量。这个偏移量被强制限制在frameIntervalNanos到2 * frameIntervalNanos之间。效果:它将
frameTimeNanos“强制拉回”到距离当前now最近的、符合渲染周期节奏的时间点。
3. 可视化理解
上图展示了当发生卡顿时,虚拟时间轴如何被校准以平滑后续动画。
| 状态 | 逻辑时间点 | 结果 |
|---|---|---|
| 正常情况 | frameTimeNanos 是旧的信号时间 |
动画平滑,delta 很小。 |
| 严重卡顿 | frameTimeNanos 滞后 > 33ms |
如果不修正,动画下一帧会因为 currentTime - frameTimeNanos 过大而发生“ teleport” (瞬移)。 |
| 修正后 | frameTimeNanos 被重置到 now - interval |
动画认为上一帧是 16.6ms 前,从而继续平滑过渡,掩盖了卡顿期间的时间断层。 |
3. 核心负载:Choreographer.FrameData
封装了 VSYNC 信号的时间戳和多组呈现时间线(Frame Timeline),这是现代 Android 系统平滑显示的核心数据结构。
1 | /** |
4. 遍历起点:ViewRootImpl.performTraversals()
1 | private void performTraversals() { |
5. 指令录制:ThreadedRenderer.draw()
将 UI 线程的 Java 代码绘制逻辑录制为 Native 层的 DisplayList。
1 | /** |
二、 Native 层:RenderThread 异步渲染 (frameworks/base/libs/hwui/)
1. 资源同步:CanvasContext::prepareTree()
在正式绘制前,将 UI 线程录制的变化应用到 Native 渲染树中。
1 | /** |
2. 绘制与提交:CanvasContext::draw()
1 | /** |
三、 底层图形系统 (frameworks/native/libs/gui/)
关键阻塞点:Surface::dequeueBuffer()
该函数在 CanvasContext::draw() 执行过程中被调用(对应源码中的 Frame frame = getFrame(); 这一行),用于向 BufferQueue 索要一个可写的图形缓冲区。
- 阻塞逻辑: 如果当前所有的 Buffer 都在
SurfaceFlinger端等待显示,或者 GPU 上一帧还没跑完导致没有空闲 Buffer,渲染线程会在此处挂起。这是全链路中最著名的潜在阻塞点。 - 监控: 通过
dumpsys gfxinfo查看到的Dequeue Buffer耗时(或 Trace 中的DequeueBufferDuration)即代表此处的等待时长。较高的数值通常意味着系统负载极重、SurfaceFlinger 合成压力大或 GPU 性能达到瓶颈。
四、 性能指标解析
1. UNKNOWN_DELAY_DURATION 溯源
在 FrameMetrics 中,该指标反映了帧处理流程开始前的不明延迟。
- 计算公式:
UNKNOWN_DELAY_DURATION = Index.HANDLE_INPUT_START - Index.INTENDED_VSYNC - 含义: 代表从系统预期发送 Vsync 信号到应用真正开始处理输入事件之间的空隙。这通常是因为主线程被其他非 UI 任务(如广播处理、Handler 消息)占用,导致无法第一时间响应渲染信号。
五:JankTracker 源代码(Android 15)
1 |
|