忙于发版,又有些天没有上社区贡献帖子了,表示很惭愧,但是突然加了个类似 git contribution 的 timeline,让我觉得很紧张。。。这么零星感觉不太能忍
之前写过两篇帖子,是 Android UI 呈现系列的,里面介绍了一些 UI 测试方法和 UI 组成的基本概念,比如 window,frame,垂直同步这些,都被加了精,还推了微信,这里再次感谢社区。
理解这些 UI 结构对测试,或者开发,甚至设计师都是非常有好处的,抛开所有业务,UI 是用户最大的痛点,每帧画面的显示,jank 都最直接的影响用户体验,UI 系列的帖子二最后我给出了一个 FPS 的报表,帖子一里最前面也有说参考了一个库:
https://github.com/ChromiumWebApps/chromium/blob/master/build/android/pylib/perf/surface_stats_collector.py
但并没有把具体实现给出来,怎么拿到数据,回复下面有挺多人关心,今天就给大家介绍一下我的摸索路径
知道要统计 FPS,于是上社区找找有没有前辈已经做过,google 一下市面上用的方法,发现社区提到了那个库,市面上的查询结果涉及到几个关键字:gfxinfo,systrace
这个过程中,得去了解 surface,surfaceFlinger 等等概念,不然进行不下去
首先不要问为什么,先去读那个库的代码,读懂,多读几遍
读完就会发现几个事情:
adb shell dumpsys SurfaceFlinger --latency + <window名>
,完整会输出 128 行信息,但第一行代表 refresh_period,所以它其实只表示 127 帧的渲染情况在把这个库通过改造,集成到自己的工具中时(经过一个 time_interval 就执行一次命令),因为有上面那些疑问,所以稍作了修改,但最核心的 zip 比较没变
得出来的一些序列如下(格式同 帧总数 - 掉帧数-FPS ):
start collecting...
---------------------
current window:com.miui.home/com.miui.home.launcher.Launcher
surface fps info:0-0-0
---------------------
current window:activity.SplashActivity
surface fps info:31-2-17
---------------------
current window:MainActivity
surface fps info:127-13-38
---------------------
current window:MainActivity
surface fps info:127-6-55
---------------------
current window:MainActivity
surface fps info:127-8-46
---------------------
current window:MainActivity
surface fps info:127-7-44
---------------------
current window:MainActivity
surface fps info:127-7-46
---------------------
current window:MainActivity
surface fps info:127-6-43
---------------------
current window:MainActivity
surface fps info:127-8-48
---------------------
current window:MainActivity
surface fps info:127-9-45
---------------------
current window:MainActivity
surface fps info:127-9-48
---------------------
current window:MainActivity
surface fps info:127-7-41
---------------------
current window:MainActivity
surface fps info:127-5-32
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-7-39
---------------------
current window:MainActivity
surface fps info:127-8-29
---------------------
current window:MainActivity
surface fps info:127-11-32
---------------------
current window:MainActivity
surface fps info:127-12-36
---------------------
current window:MainActivity
surface fps info:127-7-39
这是一段连贯的日志,大家可以看到 fps 几乎没有上 50 的,更别说 60 这个标准了。
但并不是这个 app 就做的不好,我在这次操作过程中没有觉得卡顿,listview 滑动顺畅,但是一个有图的地方,是之后再渲染出来的,我把开发者模式中的 GPU 渲染打开,在屏幕上以柱状图显示的时候,被测应用每帧渲染情况,只有两三根柱形图超过了 16ms,所以总体来说应该是达标的
所以我又发现了如下几个问题:
127-7-39
这一部分有问题就得找另外的解决方法,上面也说了,除了 SurfaceFlinger,还有 gfxinfo,期间还问过了@monkey大神,他潇洒的回了我:gfxinfo
而且,Android 开发者模式里,GPU 呈现模式分析里,柱状图就是根据 gfxinfo 里的数据来绘制的
执行 adb shell dumpsys gfxinfo + package名
,(可能为空,需滑动后才能看到渲染数据),得到的结果如下:
Draw Process Execute
7.29 31.04 0.88
2.99 1.19 0.46
0.47 0.71 0.87
0.47 22.04 0.54
0.54 2.50 0.64
0.70 2.24 0.92
0.56 2.04 0.78
0.67 3.09 0.48
0.73 2.40 2.45
0.57 4.57 0.44
2.07 1.87 0.47
0.80 13.79 0.53
0.79 2.18 3.00
0.98 2.31 2.26
0.86 2.19 2.43
0.90 2.11 4.25
0.86 1.92 4.45
0.92 3.29 6.17
0.91 5.53 0.71
注意:一帧的渲染不应该超过 16.67 这个标准,垂直同步机制的应用,
Draw
,Process
,Execute
下面分别对应的时间,具体代表的意义可参加官网
那我怎么算 FPS 呢?。。下面是我的代码和注释,贴给大家
results = run_unblock_command(fps_command)
frames = [x for x in results.split('\n') if validator(x)]
frame_count = len(frames)
jank_count = 0
vsync_overtime = 0
for frame in frames:
time_block = re.split(r'\s+',frame.strip())
if len(time_block) == 3:
try:
render_time = float(time_block[0]) + float(time_block[1]) + float(time_block[2])
except Exception, e:
render_time = 0
'''
当渲染时间大于16.67,按照垂直同步机制,该帧就已经渲染超时
那么,如果它正好是16.67的整数倍,比如66.68,则它花费了4个垂直同步脉冲,减去本身需要一个,则超时3个
如果它不是16.67的整数倍,比如67,那么它花费的垂直同步脉冲应向上取整,即5个,减去本身需要一个,即超时4个,可直接算向下取整
最后的计算方法思路:
执行一次命令,总共收集到了m帧(理想情况下m=128),但是这m帧里面有些帧渲染超过了16.67毫秒,算一次jank,一旦jank,
需要用掉额外的垂直同步脉冲。其他的就算没有超过16.67,也按一个脉冲时间来算(理想情况下,一个脉冲就可以渲染完一帧)
所以FPS的算法可以变为:
m / (m + 额外的垂直同步脉冲) * 60
'''
if render_time > 16.67:
jank_count += 1
if render_time % 16.67 == 0 :
vsync_overtime += int(render_time / 16.67) - 1
else:
vsync_overtime += int(render_time / 16.67)
fps = int(frame_count * 60 / (frame_count + vsync_overtime))
return (frame_count,jank_count,fps)
既然没有时间概念,我就用 实际的帧数/本来应该在这个时间内渲染完成的帧数 ,得到这个比例再乘以 60 这个标准
我把两种方法收集到的数据放在一起,如下:
start collecting...
---------------------
current window:com.miui.home/com.miui.home.launcher.Launcher
gfx fps info:0-0-0
surface fps info:0-0-0
---------------------
current window:activity.SplashActivity
gfx fps info:30-3-47
surface fps info:30-0-15
---------------------
current window:MainActivity
gfx fps info:128-5-57
surface fps info:127-13-46
---------------------
current window:MainActivity
gfx fps info:128-4-58
surface fps info:127-7-52
---------------------
current window:MainActivity
gfx fps info:128-5-57
surface fps info:127-8-52
---------------------
current window:MainActivity
gfx fps info:128-7-56
surface fps info:127-9-48
---------------------
current window:MainActivity
gfx fps info:128-1-59
surface fps info:127-7-54
---------------------
current window:MainActivity
gfx fps info:128-7-56
surface fps info:127-11-43
---------------------
current window:MainActivity
gfx fps info:128-6-56
surface fps info:127-10-48
---------------------
current window:MainActivity
gfx fps info:128-9-55
surface fps info:127-16-48
---------------------
current window:MainActivity
gfx fps info:128-3-58
surface fps info:127-4-57
---------------------
current window:MainActivity
gfx fps info:128-6-57
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
current window:MainActivity
gfx fps info:0-0-0
surface fps info:127-11-28
---------------------
从上面的日志又可以看出几个问题:
经过多次的试验,问题排查,决定用 gfxinfo 收集 FPS,当然这是为了数据存储需要,如果是开发工程师,最有效直观的测试一段代码有没有影响 FPS,打开 GPU 呈现模式,在屏幕上以柱状图显示就好了(btw,真机和模拟器上差别也非常大,为了还原用户体验最好用真机)