用户界面(UI)性能测试不仅可以确保app满足其功能要求,而且能够保证用户与app之间的交互是相当平滑的——以每秒60帧的速度运行(为什么是60fps?),没有任何丢弃或延迟的帧(或者我们喜欢称之为jank)。本文档介绍了可用于衡量UI性能的工具,并提供了将UI性能测量集成到测试实践中的方法。
为了提高性能,我们首先需要能够衡量系统的性能,然后诊断并识别可能从管道的各个部分到达的问题。
dumpsys是一款运行在设备上的Android工具,可以输出有关系统服务状态的信息。将 gfxinfo命令传递给dumpsys可在logcat中提供输出,其中记录了各阶段期间发生的动画以及帧相关的性能信息。
1.输入命令
示例:adb shell dumpsys gfxinfo < PACKAGE_NAME >
比如:adb shell dumpsys gfxinfo com.hashkey.hub
会出现如下结果
2.聚合帧统计信息
Graphics info for pid 2040 [com.hashkey.hub] -表明当前dump的为hashkey hub应用界面的帧信息,pid为2040
Total frames rendered: 2240817 -本次dump搜集了2240817帧的信息
Janky frames: 2163335 (96.54%) -2240817帧中有2163335 帧的耗时超过了16ms,卡顿概率为96.54%
50th percentile: 32ms
90th percentile: 44ms
95th percentile: 48ms
99th percentile: 61ms
Number Missed Vsync: 276985 -垂直同步失败的帧
Number High input latency: 7 -处理input时间超时的帧数
Number Slow UI thread: 1290744 -因UI线程上的工作导致超时的帧数
Number Slow bitmap uploads: 150321 -因bitmap的加载耗时的帧数
Number Slow issue draw commands: 2040376 -因绘制导致耗时的帧数
HISTOGRAM: 5ms=8610 6ms=1886 7ms......直方图数据,表面耗时为0-5ms的帧数为8610,耗时为5-6ms的帧数为1886,同理类推。这些高级统计数据在很高的层次上向我们传达了app的渲染性能,以及它在许多帧中的稳定性。
3.精确帧耗时信息
Android 6.0版本为gfxinfo提供了一个新的命令——framestats,其作用是可以从最近的帧中获取非常详细的帧耗时信息,因此我们可以更准确地跟踪和调试问题。
adb shell dumpsys gfxinfo com.hashkey.hub framestats >D:\zy.csv
此命令会打印出来app最后生成的120帧的帧耗时信息(使用纳秒时间戳)。运行该命令后,可将结果写进D盘的zy.csv文件里面
csv中各个字段的解释如下:
Framestats数据格式
由于数据块以CSV格式输出,因此将其粘贴到我们选择的电子表格工具,或者使用脚本进行收集和解析非常简单。下面说明了输出数据列的格式。所有时间戳都以纳秒为单位(1纳秒=1e-6毫秒)。
1.FLAGS
如果flags为0,则此帧的总耗时时间 = FRAME_COMPLETED(第14列,帧的结束时间) - INTENDED_VSYNC(第2列,帧的预期开始时间)。
如果flags不为0,则忽略该行,因为该帧的布局和绘制时间超过16ms,为异常帧。以下是可能发生的一些原因:
(1)窗口布局发生变化(例如app的第一帧或旋转后);
(2)帧被跳过也是有可能的,在这种情况下,某些值将具有垃圾时间戳。例如,如果帧超出60fps,或者屏幕上没有任何内容变脏,则可能跳过一个帧,这不一定是app中出现问题的迹象。
2.INTENDED_VSYNC
帧的预期开始时间。如果此值与VSYNC不同,则UI线程上发生了阻止其及时响应vsync信号的工作。
3.VSYNC
花费在vsync监听器和帧绘制的时间(Choreographer frame回调,动画,View.getDrawingTime()等)。
要了解VSYNC的更多信息及其对app的影响,请查看Understanding VSYNC视频。
4.OLDEST_INPUT_EVENT
输入队列中最早输入事件的时间戳。如果此帧没有输入事件,则为Long.MAX_VALUE。
此值主要用于平台工作,对app开发人员的用处不大。
5.NEWEST_INPUT_EVENT
输入队列中最后输入事件的时间戳,如果此帧没有输入事件,则为0。
此值主要用于平台工作,对app开发人员的用处不大。
但是,通过计算FRAME_COMPLETED - NEWEST_INPUT_EVENT的值,可以大致了解app添加的延迟时间。
6.HANDLE_INPUT_START
将输入事件分派给app的时间戳。
通过计算ANIMATION_START - HANDLE_INPUT_START的值,可以测量app处理输入事件所花费的时间。
如果它们的时间差很高(> 2ms),则表示app花费了非常长的时间处理输入事件,例如View.onTouchEvent(),这可能表示此工作需要优化,或者分发到其他线程。但是请注意,在某些情况下,例如发起新活动或类似活动的点击事件时,预计可接受的时间差是很大的。
7.ANIMATION_START
运行Choreographer注册动画的时间戳。
通过计算PERFORM_TRANVERSALS_START - ANIMATION_START的值,可以得到评估正在运行的所有动画器(ObjectAnimator,ViewPropertyAnimator和常用转换器)所花费的时间。
如果它们的时间差很高(> 2ms),请检查您的app是否已编写了自定义动画或者设置了ObjectAnimators动画的字段,并确保它们适用于动画。
要了解Choreographer的更多信息,请查看For Butter or Worse视频。
8.PERFORM_TRAVERSALS_START
计算DRAW_START - PERFORM_TRAVERSALS_START的值,可以得到完成布局和度量阶段所需的时间。(注意,在滚动或动画期间,你会希望它应该接近于零)
要了解有关渲染管道的度量和布局阶段的更多信息,请查看Invalidations, Layouts and Performance视频。
9.DRAW_START
performTraversals的绘制阶段开始的时间戳。这是录制任何无效视图的显示列表的起点。
此时间与SYNC_START之间的时间是在树中所有无效视图上调用View.draw()所需的时间。
有关绘图模型的更多信息,请查看Hardware Acceleration或者Invalidations, Layouts and Performance视频。
10.SYNC_QUEUED
将同步请求发送到RenderThread的时间。
这标志着开始同步阶段的消息被发送到RenderThread的时刻。如果此时间与SYNC_START之间的时间差很长(> 0.1ms左右),则表示RenderThread正忙于处理不同的帧。在内部,这用于区分执行太多工作以至于超过16ms预算的帧和由于前一帧超过16ms预算而导致被停止的帧。
11.SYNC_START
绘图同步阶段开始的时间。
如果此时间与ISSUE_DRAW_COMMANDS_START之间的时间很长(> 0.4ms左右),则通常表示已绘制了许多必须上传到GPU的新位图。
要了解有关同步阶段的更多信息,请查看Profile GPU Rendering视频。
12.ISSUE_DRAW_COMMANDS_START
硬件渲染器开始向GPU发出绘图命令的时间。
计算FRAME_COMPLETED - ISSUE_DRAW_COMMANDS_START的值,可以大致了解app生成多少GPU工作。这里会出现很多过度绘制或低效的渲染效果等问题。
13.SWAP_BUFFERS
调用eglSwapBuffers的时间,在平台工作之外相对无用。
14.FRAME_COMPLETED
帧的结束时间戳。可以通过执行FRAME_COMPLETED - INTENDED_VSYNC来计算在此帧上工作的总时间。
15.DequeueBufferDuration和QueueBufferDuration
我测试的是Android 8.0版本,除了以上14列数据外,还有2列数据——DequeueBufferDuration和QueueBufferDuration,这2列数据在官方文档中没有提及,估计是后来新加的。
网友评论