Android App流畅度FPS测试方法总结

预备知识:

1、高速摄像机或Iphone录制视频测试FPS的方法(240的帧率)

步骤1:清除后台,录制联系人滑动过程中的视频(上下滑动屏幕(速度适中即可),默认持续20秒左右,视具体测试要求而定)

步骤2:使用QuickTime的帧回放,首先选取固定1个点清晰状态时作为起始帧,开始帧数记为F1

步骤3:使用QuickTime向后逐帧查看,界面开始刷新,当固定点清晰状态时记为F2

步骤4:为减少实验误差,循环步骤3,直到F10

步骤5:计算平均帧数F = (F10 – F1) / 10

步骤6:计算帧率 Fps = 240 /[ (F10 – F1) / 10]


2、SurfaceFlinger方法测试流畅度帧率

步骤1:获取当前界面的view

adb shell dumpsysSurfaceFlinger --list

备注:命令输出结果中越往下说明Activity越在最上面,注意一定要包含“#0”这个,这个只需要首次获取即可。如下图

步骤2:获取数据之前清除Buffer缓冲区

adb shell dumpsysSurfaceFlinger --latency-clear

步骤3:在测试机上做测试场景的滑动,产生数据

步骤4:获取滑动产生的数据,如下图:(图中数据未截完全)

adb shell dumpsysSurfaceFlinger --latency com.android.contacts/com.android.contacts.PeopleActivityAlias#0

命令解释:

1)数据的单位是纳秒,时间是以开机时间为起始点

2)每一次的命令都会得到128行的帧相关的数据

3)第一行数据,表示刷新的时间间隔refresh_period

4)第1列:这一部分的数据表示应用程序绘制图像的时间点

第2列:在SF(软件)将帧提交给H/W(硬件)绘制之前的垂直同步时间,也就是每帧绘制完提交到硬件的时间戳,该列就是垂直同步的时间戳

第3列:在SF将帧提交给H/W的时间点,算是H/W接受完SF发来数据的时间点,绘制完成的时间点。

5)第一部分和第三部分类似,差异在于帧有延迟时间,从准备好绘制完成绘制的时间间隔就是帧延迟

6)掉帧jank:每一行都可以通过下面的公式得到一个值,该值是一个标准,我们称为jankflag,如果当前行的jankflag与上一行的jankflag发生改变,那么就叫掉帧

ceil((C - A) / refresh-period)

# The first line is the refresh period (here 16.72 ms), it is followed
# by 128 lines w/ 3 timestamps in nanosecond each:
# A) when the app started to draw
# B) the vsync immediately preceding SF submitting the frame to the h/w
# C) timestamp immediately after SF submitted that frame to the h/w
 
# The difference between the 1st and 3rd timestamp is the frame-latency.
# An interesting data is when the frame latency crosses a refresh period
# boundary, this can be calculated this way:
#
# ceil((C - A) / refresh-period)
#
# (each time the number above changes, we have a "jank").
# If this happens a lot during an animation, the animation appears
# janky, even if it runs at 60 fps in average.
#
# We use the special "SurfaceView" window name because the statistics for
# the activity's main window are not updated when the main web content is
# composited into a SurfaceView.

步骤5:计算帧率(adb service call SurfaceFlinger 1013命令不能使用)

计算帧率的方法(使用第二列的数值):

sdk/sources/android-18/com/android/uiautomator/platform/SurfaceFlingerHelper.java(以android-18中的路径为例,其他版本中也有该文件,可能路径不同)函数:

/**
* Calculate frame rate
* @return
*/
public static double getFrameRate() {
    if (mRefreshPeriod < 0) {
        log("Run command \"" + FRAME_LATENCY_CMD + " \" before calcuating average fr        ame rate");
        return -1.0;
    }
    if (mFrameBufferData.get(0) == null) {
        log("Run command \"" + FRAME_LATENCY_CMD + " \" before retrieving frame buff        er data");
        return -1.0;
    }
    long startTime = Long.parseLong(mFrameBufferData.get(0).get(1));
    long endTime =  Long.parseLong(mFrameBufferData.get(mFrameLatencySampleSize - 1)    .get(1));
    long totalDuration = endTime - startTime;
    return (double)((mFrameLatencySampleSize - 1) * Math.pow(10, 9))/totalDuration;
}

1) 帧数量frameCount:即产生数据的行数(去掉第一行的周期)

2) 产生帧数量count需要的时间time:time = 第二列最后1个数据–第二列第1个数据

3) 帧率frameRate:frameRate = round((fameCount-1)/time)


步骤6:计算掉帧次数jack_count:

原始数据: B1、B2、B3、B4、……

1) 过滤掉2次时间间隔小于0.5阈值的数据:((行2 –行1) / refresh-period) >threshold(0.5),返回([B2-B1, B3-B2, …], [(B2-B1)/ refresh-period, (B3-B2)/ refresh-period,…])

-----过过滤条件为每项 (B2-B1)/ refresh-period > 0.5,返回的数据为C1、C2、C3、C4、……

2) 再次对1)中返回数据求差,然后求与refresh-period比值:(行2 –行1) / refresh-period

返回([C2-C1, C3-C2, …] , [(C2-C1)/ refresh-period, (C3-C2)/ refresh-period,…])

3)对2)中返回的值按小的四舍五入:jankiness =Max(0, round((行2 –行1) / refresh-period))

4)掉帧次数jank_count:过滤jankiness<0 and jankiness>=20的数量(如果超过20帧则认为很卡)

备注:如果要求出总的掉帧数,需要将jankiness中大于0的数据累加即可

举例说明:

1、联系人列表滑动后的数据如下:

1)帧率:(126 * 1e9) / ( 12326664478503 - 12324549807888) = 60

2)从上面的数据看,帧率是60,说明滑动很流畅

3)再次计算jank_count为0

2、日历人列表滑动后的数据如下:

1) 帧率:(126 * 1e9) / (4653898206507 - 12324549807888) = 49.02

2) 从上面的数据看,帧率是49,说明滑动不是很流畅,有优化的空间

3) 再次计算jank_count为3,见表中黄底的区域,其中有1处明显的卡顿


3、Gfxinfo方法测试流畅度帧率(以联系人应用列表滑动,导入5000联系人)

记录遇到的坑:

1)打开被测应用,然后执行adb shell dumpsys window | findstr “mCurrentFocus”,查看到的包名为:com.android.contacts

2)然后执行adb shell dumpsys gfxinfo com.android.contacts,显示“No process found for: com.android.contacts”

3)各种搜索都没没有介绍,最后才发现原来包名是不一样

步骤1、设置-开发者选项-GPU呈现模式分析-勾选“在adb shell dumpsys gfxinfo中”,然后退出应用再次进入

步骤2、获取正确测试应用的包名<package name>

打开被测应用,然后执行命令adb shell dumpsys gfxinfo,如下截图包名:android.process.contacts

步骤3、滑动被测应用列表,多滑动几次(2-3S为宜,120帧数据大概2S多)

步骤4、执行命令获取数据,adb shell dumpsys gfxinfo android.process.contacts,如下截图(一部分)

步骤5、计算帧率(fps = m/(m+额外的同步脉冲)*60)

执行一次命令,总共收集m帧(理想情况下m=128),但m帧里面有些帧渲染超过了16.67ms,算一次jank,一旦有出现jank,需要用掉额外的垂直同步脉冲。其他的没有超过16.67ms的按照一个脉冲时间来算(理想情况下一个脉冲可以渲染完一帧)

所以帧率计算公式为:fps = (m/(m+额外的同步脉冲) * 60)

渲染时间:render_time = time(Draw) + time(Prepare) + time(Process) + time(Execute)

if render_time > 16.67:

jank_count += 1

if render_time % 16.67 == 0:

extra_jank += round(render_time / 16.67) – 1

else:

extra_jank += round(render_time / 16.67)

fps = (m*60 / (m+extra_jank))

备注:

Draw: 表示在Java中创建显示列表部分中,OnDraw()方法占用的时间。

Prepare:准备时间

Process:表示渲染引擎执行显示列表所花的时间,view越多,时间就越长

Execute:表示把一帧数据发送到屏幕上排版显示实际花费的时间。其实是实际显示帧数据的后台缓存区与前台缓冲区交换后并将前台缓冲区的内容显示到屏幕上的时间。

Draw +Prepare + Process + Execute = 完整显示一帧,这个时间要小于16ms才能保证每秒60帧。

举例说明

1)日历列表滑动后的数据如下:

com.google.android.calendar/com.google.android.calendar.AllInOneCalendarActivity/android.view.ViewRootImpl@c11f595 (visibility=0)

2)从以上数据可知如下信息:

帧数m = 120

jank_count = 4

extra_jank = 4

fps = (120*60 / (120+4)) = 58.06


4、使用systrace查看帧率

1、使用 javaSDK 自带的Monitor或抓取Trace的脚本抓取滑动谷歌日历 trace.html文件(具体方法可以看文章Android App启动时间总结中的抓取获取方法),然后使用chrome导入—多滑动几次,后续求平均值

2、鼠标模式选timing模式,然后在测试应用的线程选择大概1秒的一段,如下图:

3、然后将鼠标改为selection模式,统计UI Thread线程的方法,如下图中Frames项:可以看出这一段非常的流畅,Frame数为60帧了

4、同理,从下图中Slices项中的Choreographer#doFrame发生的次数也可以看出当前的帧数,如下图:

6、 在测试前多滑动几次,时间可以选20S,同上步骤测试几个周期求平均值,可以得出帧率

说明:上图中绿色圆圈表示正常帧(每帧时间<16.6ms),黄色为稍微卡顿(16.6~33.3ms),红色为卡顿帧(每帧时间>33.3ms)


其他信息说明:

1、帧率与刷新率区别

帧数(帧率):就是画面改变的速度,只要显卡够强,帧数就能很高,只要帧数高画面就流畅。理论上,每一帧都是不同的画面。60fps就是每秒钟显卡生成60张画面图片。

刷新率:顾名思义,就是显卡将显示信号输出刷新的速度。60赫兹(hertz)就是每秒钟显卡向显示器输出60次信号。

假设帧数是刷新率的1/2,那么意思就是显卡每两次向显示器输出的画面是用一幅画面。相反,如果帧数是刷新率的2倍,那么画面每改变两次,其中只有1次是被显卡发送并在显示器上显示的。所以高于刷新率的帧数都是无效帧数,对画面效果没有任何提升,反而可能导致画面异常。

2、LCD控制与刷新率、帧率的关系

依照刷新率的速度,每刷一次,显示数据都需要从内存中读取数据输出给LCD显示。在这个情况之下,帧率跟刷新率就应该一样的了,即只要改变LCD控制器所指向内存范围内的内容,就可以以刷新率的速度(帧率)输出。当然这个内存关系非常密切,如果内存速度(总线频率)跟不上,而LCD配置的刷新率太快,也就是出现了内存带宽不够的现象,导致显示闪或者抖动的现象(特别是下半屏)。

另:内存带宽的计算公式:带宽=总线宽度×总线频率×一个时钟周期内交换的数据包个数

发布于 2019-05-26 18:50