使用 FrameTimeline 检测 Android 掉帧

NOTE: FrameTimeline 需要 Android 12(S) 或更高版本

如果帧在屏幕上呈现的时间与调度器给定的预测呈现时间不匹配,则称该帧是掉帧的。

掉帧可能导致:

FrameTimeline 是 SurfaceFlinger 内的一个模块,它检测掉帧并报告掉帧的来源。SurfaceViews 目前不支持,但将来会支持。

UI

对于屏幕上至少有一帧的每个应用程序,都会添加两个新 track。

同样,SurfaceFlinger 也会获得这两个新 track,代表它预期完成的时间,以及它完成合成帧和在屏幕上呈现所花费的实际时间。在这里,SurfaceFlinger 的工作表示显示堆栈中其下方的所有内容。这包括 Composer 和 DisplayHAL。因此,Slice 代表 SurfaceFlinger 主线程从开始到屏幕更新的过程。

Slice 的名称表示从 choreographer 接收的令牌。你可以将实际 Timeline track 中的 Slice 与预期 Timeline track 中的相应 Slice 进行比较,以查看应用程序的表现与预期相比如何。此外,出于调试目的,令牌被添加到应用程序的 doFrameRenderThread Slice 中。对于 SurfaceFlinger,相同的令牌显示在 onMessageReceived 中。

选择实际 Timeline Slice

选择详细信息提供了有关帧发生了什么的更多信息。这些包括:

帧是早、按时还是晚。

应用程序是否按时完成了帧的工作?

是否观察到此帧有掉帧?如果是,这显示观察到的掉帧类型。如果没有,类型将为 None

当 FrameTimeline 接收到此帧时,预测是否已过期?如果是,这将显示 Expired Prediction。如果不是,则显示 Valid Prediction

布尔值,告诉帧是否由 GPU 合成。

帧呈现到的图层/表面的名称。某些进程会更新多个表面的帧。在这里,具有相同令牌的多个 Slice 将显示在实际 Timeline 中。图层名称是区分这些 Slice 的好方法。

布尔值,告诉帧是对应于缓冲区还是动画。

流事件

在应用程序中选择实际 Timeline Slice 还会绘制一条线回相应的 SurfaceFlinger Timeline Slice。

由于 SurfaceFlinger 可以将多个图层的帧合成为单个屏幕上的帧(称为 DisplayFrame),选择 DisplayFrame 会绘制箭头到所有被一起合成的帧。这可以跨越多个进程。

颜色代码

颜色 图像 描述
绿色 良好的帧。未观察到掉帧
浅绿色 高延迟状态。帧率平滑,但帧呈现较晚,导致输入延迟增加。
红色 掉帧的帧。Slice 所属的进程是掉帧的原因。
黄色 仅由应用程序使用。帧是掉帧的,但应用程序不是原因,SurfaceFlinger 导致了掉帧。
蓝色 丢失的帧。在 SurfaceFlinger 中,这意味着我们跳过了一帧,更喜欢更新的帧而不是此帧。在应用程序方面,这意味着 UI 线程的状态更新没有及时推送到 RenderThread,并且 RenderThread 在没有来自 UI 线程的状态更新的情况下绘制了帧。

掉帧说明

掉帧类型在 JankInfo.h 中定义。由于每个应用程序的编写方式不同,因此没有通用的方法来深入应用程序的内部并指定掉帧的原因。我们的目标不是这样做,而是提供一种快速的方法来判断应用程序是否掉帧或 SurfaceFlinger 是否掉帧。

None

一切都很好。帧没有掉帧。应该追求的理想状态。

应用程序掉帧

应用程序运行时间超过预期,导致掉帧。应用程序帧所花费的总时间是通过使用 choreographer 唤醒作为开始时间和 max(gpu,发布时间)作为结束时间来计算的。发布时间是帧发送到 SurfaceFlinger 的时间。由于 GPU 通常并行运行,gpu 可能在发布时间之后完成。

这更像是一种状态而不是掉帧。如果应用程序在上一个帧甚至呈现之前就不断向 SurfaceFlinger 发送新帧,就会发生这种情况。内部缓冲区队列被尚未呈现的缓冲区填充,因此名称为 Buffer Stuffing。队列中的这些额外缓冲区仅一个接一个地呈现,因此导致额外的延迟。这也可能导致没有更多缓冲区供应用程序使用的阶段,并且它进入出队阻塞等待。应用程序执行的实际持续时间可能仍在期限内,但由于填充的性质,无论应用程序完成其工作的速度如何,所有帧都将至少延迟一个 vsync 呈现。在此状态下,帧仍然平滑,但与延迟呈现相关联的输入延迟增加。

SurfaceFlinger 掉帧

SurfaceFlinger 有两种方式可以合成帧:

需要注意的一点是,执行设备合成是主线程上的阻塞调用。然而,GPU 合成是并行进行的。SurfaceFlinger 执行必要的绘制调用,然后将 gpu 栅栏交给显示设备。显示设备然后等待栅栏发出信号,然后呈现帧。

SurfaceFlinger 预期在给定的期限内完成。如果主线程运行时间超过该时间,则掉帧为 SurfaceFlingerCpuDeadlineMissed。SurfaceFlinger 的 CPU 时间是在主线程上花费的时间。如果使用了设备合成,这包括整个合成时间。如果使用了 GPU 合成,这包括编写绘制调用和将帧交给 GPU 的时间。

SurfaceFlinger 主线程在 CPU 上花费的时间 + GPU 合成时间一起超过了预期。在这里,CPU 时间仍然会在期限内,但由于 GPU 上的工作未按时准备好,帧被推到下一个 vsync。

DisplayHAL 掉帧是指 SurfaceFlinger 完成其工作并按时将帧发送到 HAL,但帧未在 vsync 上呈现的情况。它在下一个 vsync 上呈现。可能是 SurfaceFlinger 没有给 HAL 工作足够的时间,或者 HAL 工作确实有延迟。

SurfaceFlinger 的调度器提前计划呈现帧的时间。然而,这种预测有时会偏离实际的硬件 vsync 时间。例如,帧的预测呈现时间可能为 20ms。由于估计中的漂移,帧的实际呈现时间可能为 23ms。这在 SurfaceFlinger 的调度器中称为预测误差。调度器会定期自我纠正,因此这种漂移不是永久的。然而,具有预测漂移的帧仍将被分类为掉帧以进行跟踪。

孤立的预测误差通常不会被用户感知,因为调度器很快会适应并修复漂移。

未知掉帧

顾名思义,在这种情况下掉帧的原因是未知的。这里的一个例子是 SurfaceFlinger 或应用程序运行时间超过预期并且错过了期限,但帧仍然提前呈现。这种掉帧发生的概率非常低,但并非不可能。

SQL

在 SQL 级别,fremetimeline 数据在两个表中可用

select ts, dur, surface_frame_token as app_token, display_frame_token as sf_token, process.name from expected_frame_timeline_slice left join process using(upid)
ts dur app_token sf_token name
60230453475 20500000 3135 3142 com.google.android.apps.nexuslauncher
60241677540 20500000 3137 3144 com.google.android.apps.nexuslauncher
60252895412 20500000 3139 3146 com.google.android.apps.nexuslauncher
60284614241 10500000 0 3144 /system/bin/surfaceflinger
60295858299 10500000 0 3146 /system/bin/surfaceflinger
60297798913 20500000 3147 3150 com.android.systemui
60307075728 10500000 0 3148 /system/bin/surfaceflinger
60318297746 10500000 0 3150 /system/bin/surfaceflinger
60320236468 20500000 3151 3154 com.android.systemui
60329511401 10500000 0 3152 /system/bin/surfaceflinger
60340732956 10500000 0 3154 /system/bin/surfaceflinger
60342673064 20500000 3155 3158 com.android.systemui
select ts, dur, surface_frame_token as app_token, display_frame_token, jank_type, on_time_finish, present_type, layer_name, process.name from actual_frame_timeline_slice left join process using(upid)
ts dur app_token sf_token jank_type on_time_finish present_type layer_name name
60230453475 26526379 3135 3142 Buffer Stuffing 1 Late Present TX - com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0 com.google.android.apps.nexuslauncher
60241677540 28235805 3137 3144 Buffer Stuffing 1 Late Present TX - com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0 com.google.android.apps.nexuslauncher
60252895412 2546525 3139 3142 None 1 On-time Present TX - NavigationBar0#0 com.android.systemui
60252895412 27945382 3139 3146 Buffer Stuffing 1 Late Present TX - com.google.android.apps.nexuslauncher/com.google.android.apps.nexuslauncher.NexusLauncherActivity#0 com.google.android.apps.nexuslauncher
60284808190 10318230 0 3144 None 1 On-time Present [NULL] /system/bin/surfaceflinger
60296067722 10265574 0 3146 None 1 On-time Present [NULL] /system/bin/surfaceflinger
60297798913 5239227 3147 3150 None 1 On-time Present TX - NavigationBar0#0 com.android.systemui
60307246161 10301772 0 3148 None 1 On-time Present [NULL] /system/bin/surfaceflinger
60318497204 10281199 0 3150 None 1 On-time Present [NULL] /system/bin/surfaceflinger
60320236468 2747559 3151 3154 None 1 On-time Present TX - NavigationBar0#0 com.android.systemui

TraceConfig

Trace Protos: FrameTimelineEvent

Datasource:

data_sources { config { name: "android.surfaceflinger.frametimeline" } }