测试界面性能

界面 (UI) 性能测试可确保您的应用不仅满足其功能要求,而且用户与应用之间的交互顺畅无比,能够持续以每秒 60 帧(为什么选择 60fps?)的帧速运行,而不会出现任何帧丢失或延迟的现象,也就是我们通常所说的“卡顿”。本文档介绍了可用于衡量界面性能的工具,并提出一种将界面性能衡量方式融入测试实践中的方法。

衡量界面性能

为了改善性能,您首先必须能够衡量系统性能,然后诊断并识别可能源自管道各个部分的问题。

dumpsys 是一种在设备上运行并转储需要关注的系统服务状态信息的 Android 工具。通过向 dumpsys 传递 gfxinfo 命令,可以提供 logcat 格式的输出,其中包含与录制阶段发生的动画帧相关的性能信息。

    > adb shell dumpsys gfxinfo <PACKAGE_NAME>
    

此命令可以生成多个不同表达形式的帧时间数据。

聚合帧统计信息

借助 Android 6.0(API 级别 23),该命令可将在整个进程生命周期中收集的帧数据的聚合分析输出到 logcat。例如:

    Stats since: 752958278148ns
    Total frames rendered: 82189
    Janky frames: 35335 (42.99%)
    90th percentile: 34ms
    95th percentile: 42ms
    99th percentile: 69ms
    Number Missed Vsync: 4706
    Number High input latency: 142
    Number Slow UI thread: 17270
    Number Slow bitmap uploads: 1542
    Number Slow draw: 23342
    

这些总体统计信息可以较为全面地展示应用的渲染性能及其在多个帧之间的稳定性。

精确的帧时间信息

Android 6.0 附带提供了一个适用于 gfxinfo 的新命令,即:framestats,该命令会根据最近的帧提供非常详细的帧时间信息,让您能够更准确地查出并调试问题。

    >adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats
    

该命令会从应用生成的最近 120 个帧中输出带有纳秒时间戳的帧时间信息。以下是 adb dumpsys gfxinfo <PACKAGE_NAME> framestats 的原始输出示例:

    0,27965466202353,27965466202353,27965449758000,27965461202353,27965467153286,27965471442505,27965471925682,27965474025318,27965474588547,27965474860786,27965475078599,27965479796151,27965480589068,
    0,27965482993342,27965482993342,27965465835000,27965477993342,27965483807401,27965486875630,27965487288443,27965489520682,27965490184380,27965490568703,27965491408078,27965496119641,27965496619641,
    0,27965499784331,27965499784331,27965481404000,27965494784331,27965500785318,27965503736099,27965504201151,27965506776568,27965507298443,27965507515005,27965508405474,27965513495318,27965514061984,
    0,27965516575320,27965516575320,27965497155000,27965511575320,27965517697349,27965521276151,27965521734797,27965524350474,27965524884536,27965525160578,27965526020891,27965531371203,27965532114484,
    

每行输出均代表应用生成的一帧。每行都有固定的列数,描述帧生成管道的每个阶段所花的时间。下文将详细描述此格式,包括每列代表的具体内容。

Framestats 数据格式

由于数据块是 CSV 格式的输出,因此将其粘贴到所选的电子表格工具或使用脚本进行收集和解析非常简单。下表解释了输出数据列的格式。所有时间戳均以纳秒计。

  • FLAGS
    • FLAGS 列为“0”的行可以通过从 FRAME_COMPLETED 列中减去 INTENDED_VSYNC 列计算得出总帧时间。
    • 该列为非零值的行应该被忽略,因为其对应的帧已被确定为偏离正常性能,其布局和绘制时间预计超过 16 毫秒。出现这种情况可能有如下几个原因:
      • 窗口布局发生变化(例如,应用的第一帧或在旋转后)
      • 此外,如果帧的某些值包含无意义的时间戳,则也可能跳过该帧。例如,如果帧的运行速度超过 60fps,或者如果屏幕上的所有内容最终都准确无误,则可能跳过该帧,这不一定表示应用中存在问题。
  • INTENDED_VSYNC
    • 帧的预期起点。如果此值不同于 VSYNC,则表示界面线程中发生的工作使其无法及时响应 Vsync 信号。
  • VSYNC
    • 所有 Vsync 监听器中使用的时间值和帧绘图(Choreographer 帧回调、动画、View.getDrawingTime() 等等)
    • 如需进一步了解 VSYNC 及其对应用产生的影响,请观看了解 VSYNC 视频。
  • OLDEST_INPUT_EVENT
    • 输入队列中最早输入事件的时间戳或 Long.MAX_VALUE(如果帧没有输入事件)。
    • 此值主要用于平台工作,对应用开发者的作用有限。
  • NEWEST_INPUT_EVENT
    • 输入队列中最新输入事件的时间戳或 0(如果帧没有输入事件)。
    • 此值主要用于平台工作,对应用开发者的作用有限。
    • 但是,可以通过查看 (FRAME_COMPLETED - NEWEST_INPUT_EVENT) 大致了解应用增加的延迟时间。
  • HANDLE_INPUT_START
    • 将输入事件分派给应用的时间戳。
    • 通过观察此时间戳与 ANIMATION_START 之间的时间差,可以测量应用处理输入事件所花的时间。
    • 如果这个数字较高(> 2 毫秒),则表明应用处理 View.onTouchEvent() 等输入事件所花的时间太长,这意味着此工作需要进行优化或转交给其他线程。请注意,有些情况下(例如,启动新 Activity 或类似活动的点击事件),这个数字较大是预料之中并且可以接受的。
  • ANIMATION_START
    • 在 Choreographer 中注册的动画运行的时间戳。
    • 通过观察此时间戳与 PERFORM_TRANVERSALS_START 之间的时间差,可以确定评估正在运行的所有动画(常见动画有 ObjectAnimator、ViewPropertyAnimator 和 Transitions)所用的时间。
    • 如果这个数字较高(> 2 毫秒),请检查您的应用是否编写了任何自定义动画,或检查 ObjectAnimator 在对哪些字段设置动画并确保它们适用于动画。
    • 如需了解有关 Choreographer 的更多信息,请观看利弊视频。
  • PERFORM_TRAVERSALS_START
    • 如果您从此值中减去 DRAW_START,则可推断出完成布局和测量阶段所用的时间(请注意,在滚动或动画期间,您会希望此时间接近于零)。
    • 如需了解有关渲染管道的测量和布局阶段的更多信息,请观看失效、布局和性能视频。
  • DRAW_START
    • performTraversals 绘制阶段的开始时间。这是记录任何失效视图的显示列表的起点。
    • 此时间与 SYNC_START 之间的时间差就是对树中所有失效视图调用 View.draw() 所用的时间。
    • 如需了解有关绘图模型的详细信息,请参阅硬件加速失效、布局和性能视频。
  • SYNC_QUEUED
    • 将同步请求发送给 RenderThread 的时间。
    • 它标记的是将开始同步阶段的消息发送给 RenderThread 的时间点。如果该时间点与 SYNC_START 的时间差较大(约 > 0.1 毫秒),则意味着 RenderThread 正忙于处理另一帧。它在内部用于区分该帧是因作业负荷过大而超过了 16 毫秒的预算时间,还是该帧由于上一帧超过 16 毫秒的预算时间而停止。
  • SYNC_START
    • 绘制同步阶段的开始时间。
    • 如果此时间与 ISSUE_DRAW_COMMANDS_START 之间相差较大(约 > 0.4 毫秒),通常表示绘制了大量必须上传到 GPU 的新位图。
    • 如需进一步了解同步阶段,请观看 GPU 渲染分析视频。
  • ISSUE_DRAW_COMMANDS_START
    • 硬件渲染器开始向 GPU 发出绘图命令的时间。
    • 此时间与 FRAME_COMPLETED 之间的时间差让您可以大致了解应用生成的 GPU 工作量。绘制过度或渲染效果不佳等问题都会在此显示出来。
  • SWAP_BUFFERS
    • 调用 eglSwapBuffers 的时间,在平台工作范围之外,意义相对不大。
  • FRAME_COMPLETED
    • 大功告成!处理此帧所花的总时间可以通过执行 FRAME_COMPLETED - INTENDED_VSYNC 计算得出。

您可以通过不同的方法使用此数据。一种简单却有用的可视化方式就是在不同的延迟时段中显示帧时间 (FRAME_COMPLETED - INTENDED_VSYNC) 分布的直方图(参见下图)。此图直观地表明,大部分帧非常有效,截止时间远低于 16 毫秒(显示为红色),但是少数帧明显超出了截止时间。我们可以观察此直方图中的变化趋势,了解所产生的整体变化或新异常值。此外,您还可以根据数据中的多个时间戳绘制表示输入延迟、布局所用时间或其他类似关注指标的图表。

简单的帧时间转储

如果在“Developer Options”中将 Profile GPU rendering(或 Profile HWUI rendering)设置为 In adb shell dumpsys gfxinfo,则 adb shell dumpsys gfxinfo 命令会输出最近 120 帧的时间信息,分为几个不同的类别,并且值以制表符分隔。这些数据可以用于大致表明绘图管道的哪些部分可能速度较慢。

与上述 framestats 类似,将其粘贴到所选的电子表格工具或使用脚本进行收集和解析同样非常简单。下图详细显示了应用生成的许多帧的具体时间分布。

运行 gfxinfo、复制输出、将其粘贴到电子表格应用并将数据绘制成堆积条形图的结果。

每个竖条均代表一个动画帧;其高度代表计算该动画帧所用的毫秒数。竖条的每个彩色分段均代表渲染管道的一个不同阶段,因此您可以看到应用的哪些部分可能会造成瓶颈。如需了解有关渲染管道的详细信息,请参阅失效、布局和性能视频。

控制统计信息收集的时段

Framestats 和简单的帧时间均可在极短的时间内(相当于约 2 秒渲染)收集数据。要精确控制此时间范围(例如,将数据限制于特定动画),您可以重置所有计数器并汇总收集的统计信息。

    >adb shell dumpsys gfxinfo <PACKAGE_NAME> reset
    

这也可以与转储命令结合使用来定期进行收集和重置,从而持续捕获时间范围不到 2 秒的帧。

诊断性能回归

为了查出问题并使应用保持良好运行状况,第一步最好是识别回归。但是,dumpsys 仅确定是否存在问题及其相对严重性。您仍需诊断性能问题的具体原因并找到适当的解决方法。为此,我们强烈建议您使用 systrace 工具。

其他资源

如需了解有关 Android 渲染管道的工作原理、可能存在的常见问题以及如何解决这些问题的详细信息,以下资源可能对您有所帮助:

自动执行界面性能测试

界面性能测试方法之一是让测试人员对目标应用执行一系列用户操作,并目视检查是否存在卡顿现象,或花费大量时间使用工具驱动型方法来查明是否存在卡顿现象。但是,这种人工方法充满风险:人为感知帧率变化的能力参差不齐,并且此过程还很费时、繁琐且易于出错。

更为有效的方法是记录并分析自动化界面测试中的关键性能指标。Android 6.0 包括新的日志记录功能。利用这些功能,您可以轻松确定应用动画中的卡顿数量和严重性,您还可以使用这些功能构建严格的流程,用于确定当前性能并跟踪未来的性能目标。

其他资源

要进一步了解本主题,请参阅以下资源。

Codelab