Android性能专项FPS测试实践

前言

最近手上有个项目,需要进行流畅度的专项测试,目前已经进行了一段时间,因此想总结一些经验和教训跟大家分享。

测试需求

通过技术手段量化程序卡顿程度,过程数据可视化
多平台机型适配,方案不能依赖root
不能有Android的API版本限制(因为需要兼容多个系统版本)
监控流程可自动化执行
过程需要连续可靠
测试准备

理解FPS的概念

FPS即Frames per second,>>点击这篇文章 解释的非常清楚。当我们准备测试流畅度的时候,必须先理解两个关键指标60帧每秒以及16.67毫秒,这两个值代表什么意思?怎么得来的?
用过flash的人应该知道动画片其实是由一张张画出来的图片连贯执行产生的效果,当一张张独立的图片切换速度足够快的时候,会欺骗我们的眼睛,以为这是连续的动作。反之类推,当你的图片切换不够快的时候,就会被人眼看穿,反馈给用户的就是所谓的卡顿现象。
想要让大脑觉得动作是连续的,至少是每秒10-12帧的速度,而想达到流畅的效果,至少需要每秒24帧。这也是为什么电影片源通常都是24帧的原因,好奇的同学点击>>知乎高知看看大神的解答。不过60帧每秒的流畅度是最佳的,我们的目标就是让程序的流畅度能接近60帧每秒,当然超过60帧速的话大部分人还是会受不了的。

综上所述,APP需要尽可能的超过24帧/秒,接近60帧/秒的速度,并且在使用的过程中保持这个速率,试想一下你吃着火锅看着电影,突然图像发生了跳跃或者画面撕裂,那种感觉就像米饭里吃到了沙子一样极度不爽,因此这意味着我们的程序需要在16.67ms内处理一幅画面内的所有事,并保持住这个状态。
计算公式:1000ms / 60 frames ≈ 16.67 ms/frames

获取FPS的可行性方案

在查阅了很多的资料,也学习了同行前辈们的各种记录后,我根据自己需要将可行性方案的范围缩小到三种。

方案一
通过 [设置]->[开发者选项]->[GPU呈现模式分析] ->[在屏幕上显示为条形图] 进行直观的取样,截图如下:

设置完成后的效果是这样的:

屏幕下方的柱形图会持续刷新,最上方会有一根绿色的线,代表的是16ms的阈值,超过这个界限表示当前帧绘制的时间出现了延迟,及卡顿现象,后面会详细介绍原因。横坐标表示时间的持续,每一根柱形图表示当前帧的绘制时间。因此我们在使用的过程中,下面的柱形图会一直的刷新,单位是ms。各位看官是否有注意到每一帧的柱形图颜色不一样呢(注:不同手机的颜色不一样,仅限安卓4.0以上版本参考)?下图是官网提供的比较典型的GPU渲染卡顿的例子:

绘制过程中的不同颜色具有不同的含义,详细解释请移步>> 官网 查看更多。

那么是不是说我只需要打开界面去数一下超过绿色阈值的柱状图有多少就可以观察我们应用的流畅度了?然而并没有,因为这个方式获取到的渲染时间只是UI主线程上的绘制行为,目前我所接手的项目,采用的方式是捕捉相机的数据然后放到GPU中去进行绘制,有单独的绘制线程,单独的视图,所以这个方案并不适合我手上的项目。

方案二
adb shell dumpsys gfxinfo yourpackagename
1
使用这个命令可以得到如下的返回值,节选关键部分:

** Graphics info for pid 3125 [com.qtest.demo] **

Stats since: 37470720668ns
Total frames rendered: 95
Janky frames: 0 (0.00%)
90th percentile: 7ms
95th percentile: 7ms
99th percentile: 11ms
Number Missed Vsync: 0
Number High input latency: 0
Number Slow UI thread: 0
Number Slow bitmap uploads: 0
Number Slow issue draw commands: 0

大家可以看到,使用起来非常方便,但是遗憾的是必须是Android M 版本以上才支持,而且需要拖动屏幕产生的数据才比较准确,对于我这种需要适配多种机型和版本的情况就不太符合了,关于这个命令的详细解释,各位可以看一下谷歌官方的>>Document。

方案三
adb shell dumpsys SurfaceFlinger --latency com.qtest.demo/com.qtest.DemoActivity
1
执行这条命令以后,你会得到这样的信息:

16666667
575271438588 575276081296 575275172129
575305169681 575309795514 575309142441
。。。此处省略很多行。。。
580245208898 580250445565 580249372231
580279290043 580284176346 580284812908
580330468482 580334851815 580333739054

第一行是设备的刷新周期refresh-period,单位是纳秒,换算成毫秒就是16666667\1000\1000≈16.67ms;这条命令的含义是获取当前layer(窗口、图层)的最近128帧的信息(仅保存128帧),所以上面我省略的部分实际上总共有128行(刨去第一行刷新率,需要按127帧来换算),这三列数据的解释是(ps:翻译不正确的请指正):

第一列: when the app started to draw (开始绘制图像的瞬时时间)
第二列: the vsync immediately preceding SF submitting the frame to the h/w (VSYNC信令将软件SF帧传递给硬件HW之前的垂直同步时间)
第三列: timestamp immediately after SF submitted that frame to the h/w (SF将帧传递给HW的瞬时时间,及完成绘制的瞬时时间)
【垂直同步】
Vertical Synchronization,屏幕从图形芯片获取每帧的数据,然后逐行进行绘制。理想状况下,你期望显示屏在绘制完一帧之后,图形芯片整好能提供新帧的数据。图像撕裂的状况就发生在图形芯片在图像绘制到一半的时候,就载入了新一帧的数据,以致你最终得到的数据帧是半个帧的新数据和半个帧的老数据。而垂直同步,顾名思义就是用来同步的。它告知GPU在载入新帧之前,要等待屏幕绘制完成前一帧。
没有垂直同步的示意图

有垂直同步的示意图

【掉帧jank】
统计硬件掉帧数的计算方式是:在一个刷新周期内(16.67ms)会记录127行绘制时间,其中包括起始绘制(上面第一列的时间数据)和完成绘制两个纳秒时间(上面第三列的时间数据),每两行的绘制时间间隔应该相等,否则就发生了帧延迟,即掉帧(jank),每个周期内发生掉帧就计数1次,在整个绘制周期内的掉帧数即是渲染掉帧的数量。
测试指标&范围

搞清楚一些基础概念后,我们需要确定接下来测试需要获取的数据,本次测试除了常规的数据以外,业务方也通过程序埋点输出了一些关注的指标信息,概况起来包括但不限于以下几点:

  • 组件初始化时间(业务方埋点)
  • APP启动时间(冷启动、热启动)
  • CPU占用(活动、静默状态)
  • PSS内存占用(活动、静默状态)//因为不能root手机,所以没有取USS
  • 电池温度变化(活动、静默状态)
  • FPS
  • 硬件渲染掉帧数
  • 单帧渲染平均时间
  • 单帧检测处理时间(业务方埋点)

OS版本:4.4、5.0、5.1、4.2、4.3、6.0、4.0
品牌覆盖:华为、小米、Nexus、VIVO、奇酷、酷派、三星、锤子
分辨率覆盖:1920X1080、2560X1440、1280X720、854X480
业务场景:针对业务特点进行设置的测试场景,在此不表。

采样策略

在调研了网上各种方案后,个人觉得 @sandman 的方案是最符合我的预期,因此直接拿来使用。方案如下:

  1. 通过命令:dumpsys SurfaceFlinger | grep "|....|"获取当前置顶窗口名称
  2. 历史记录127行数据,按60帧算可记录2.12S数据,从而不用频繁获取。(最终考虑设定1.6S间隔刷新数据。)
  3. 定期清零重新记录,避免如何分清哪些数据是上次的。命令:dumpsys SurfaceFlinger --latency-clear
  4. 有刷新则计算帧率,无刷新则不输出数据,有时候取到的fps为1,就是这个原因
  5. 每次采样数据大于等于1帧则计算FPS,丢帧率,最大帧间隔
  6. 针对业务需求,增加了单帧平均渲染时间的统计

具体实现

网上的实现方式大多数都是取自于这里:
https://github.com/ChromiumWebApps/chromium/blob/master/build/android/pylib/perf/surface_stats_collector.py
掌握python开发的同学可以阅读和学习一下作者的采集思路。针对源代码,有篇博客做了详细的解读,感兴趣的同学可以看这里:
http://blog.csdn.net/itfootball/article/details/43084527
我采取的实现方式是@sandman的办法,利用shell文件在手机内部获取数据后生成csv文件,再通过adb命令pull到本地进行统计,针对我自己的需求,对shell代码略微做了调整,增加了单帧平均渲染时间的统计,运行效果像这样:

这种采样的方式最大的好处就是不用root手机,针对shell代码的解释,由于篇幅太长我就不在这里贴了,大家可以移步这里去看详细的解释:
https://testerhome.com/topics/4775
各位可以在这里下载完整版工具,工具包中包含了利用python生成HTML报表的代码:
https://yunpan.cn/ckcypyNLDAet2 (提取码:005f)

那么测试出来的报告大概可以是这样的:

你可能感兴趣的:(Android性能专项FPS测试实践)