深度解析Android性能测试

一直以来Android性能测试一直是Android测试中一个被一部分人遗忘,有被一部分人无可奈何的东西。在绝大部分的创业公司,性能测试基本上都是被 遗忘的,因为功能测试和稳定性测试才是重点,而在中等公司中一部分测试人员向对Android进行性能测试,却无从下手。Android性能测试一直存在测试维度少,测试数据难收集,已收集数据难量化的特点,这些特点又是因为Android手机版本碎片化、硬件多样化、App功能复杂造成的。

性能测试总的来说,可以分为卡顿ANR测试、流畅度测试、电量测试、流量测试。一个APP为什么需要性能测试,总的来说就是一些不严谨的代码,在低端机型造成卡顿,对手机上有限电量的浪费,昂贵流量的浪费,造成用户流失。


一、卡顿ANR测试

卡顿ANR与Android就是天生的朋友,从Android第一天诞生直到现在的8核CPU,Android还是未能摆脱页面不流畅,卡,死机的诟病,所以个人认为卡顿ANR测试是性能测试最主要的一块。

卡顿简单的来说,就是手机没有及时响应、页面延迟,出现丢帧的现象,或者点击无响应。绝大多数的卡顿,稍等片刻系统就会恢复正常,但假如超过5S,就可能会引发手机ANR,造成更高级别的警告。如图所示:

1.1 什么会引发ANR? 

在Android里,应用程序的响应性是由Activity Manager和WindowManager系统服务监视的 。当它监测到以下情况中的一个时,Android就会针对特定的应用程序显示ANR:

ANR一般有三种类型:

1)KeyDispatchTimeout(5 seconds) –主要类型按键或触摸事件在特定时间内无响应

2)BroadcastTimeout(10 seconds) –BroadcastReceiver在特定时间内无法处理完成

3)ServiceTimeout(20 seconds) –小概率类型 Service在特定的时间内无法处理完成

这三种原因都会造成ANR,但是第一种情况基本上占了所有ANR的百分之九十以上,第三种的情况微乎其微。这三种ANR不是孤立的,有可能会相互影响。例如一个应用程序进程中同时有一个正在显示的Activity和一个正在处理消息的BroadcastReceiver,它们都运行在这个进程的主线程中。如果BR的onReceive函数没有返回,此时用户点击屏幕,而onReceive超过5秒仍然没有返回,主线程无法处理用户输入事件,就会引起第1种ANR。如果继续超过10秒没有返回,又会引起第2种ANR。发生ANR的主要原因是潜在的耗时操作,例如网络或数据库操作,或者高耗时的计算如改变位图尺寸,应该在子线程里(或者以数据库操作为例,通过异步请求的方式)来完成。然而,不是说你的主线程阻塞在那里等待子线程的完成——也不是调用 Thread.wait()或是Thread.sleep()。替代的方法是,主线程应该为子线程提供一个Handler,以便完成时能够提交给主线程。以这种方式设计你的应用程序,将能保证你的主线程保持对输入的响应性并能避免由于5秒输入事件的超时引发的ANR对话框。

三种ANR发生时都会在log中输出错误信息,你会发现各个应用进程和系统进程的函数堆栈信息都输出到了一个/data/anr/traces.txt的文件中,ROOT手机导出该文件后,分析此日志可以得出一些结论,但traces文件信息比较抽象,难理解。总的来说,日志难收集,结果难分析。

1.2 如何避免ANR?

1) 运行在主线程里的任何方法都尽可能少做事情。特别是,Activity应该在它的关键生命周期方法(如onCreate()和onResume())里尽可能少的去做创建操作。(可以采用重新开启子线程的方式,然后使用Handler+Message的方式做一些操作,比如更新主线程中的ui等)

2) 应用程序应该避免在BroadcastReceiver里做耗时的操作或计算。但不再是在子线程里做这些任务(因为 BroadcastReceiver的生命周期短),替代的是,如果响应Intent广播需要执行一个耗时的动作的话,应用程序应该启动一个 Service。(此处需要注意的是可以在广播接受者中启动Service,但是却不可以在Service中启动broadcasereciver,关于原因后续会有介绍,此处不是本文重点)

3)避免在Intent Receiver里启动一个Activity,因为它会创建一个新的画面,并从当前用户正在运行的程序上抢夺焦点。如果你的应用程序在响应Intent广 播时需要向用户展示什么,你应该使用Notification Manager来实现。

TraceView是android SDK中自带的一个性能测试工具,可以在Tools目录下找到。TraceView能很精确的查看到每一个类,每一个类方法的执行时间。APP卡顿我们只需要保留当时的traceview文件,通过查看该文件,就可以定位绝大部分问题。

通过Debug.startMethodTracing(String FileName)和Debug.stopMethodTracing()来记录一段时间内方法执行情况。

在主线程中不停的插入一个轻量级别的操作,如果该变量在指定的时间内,没有改变,则说明此刻APP卡顿。卡顿工具工具就是根据https://github.com/SalomonBrys/ANR-WatchDog项目改进而成的。把ANR这种警告变成错误让APP闪退,持久化当时信息。

原理图:

问题背景:

百度国际化浏览器初次安装App,点击icon后,明显卡顿或者ANR,QA手工测试无法定位,RD优化代码多次依旧找不到问题的节点。

测试方法:

百度国际化浏览器加入卡顿工具jar,代码中初始化。测试结果分析。

在打开traceview 文件后,通过 real Time/Call 从大到小排序,找到对应的与代码相关消耗时间最大的方法。

 

我们能够看到很明显的看出FrameWindow.initDataBase()方法占用CPU过长达到3S左右了,距离5S很接近,通过查看代码,结合业务逻辑,得知此处为数据库初始化,并且主要是标签数据库初始化。

从整个APP来看,启动页面初始化标签数据库并没有错,但是此刻本身逻辑就非常多,标签数据库初始化后并没有马上使用到,而是到二级页面才有查询动作,总的来说,就是增加资源紧张。

建议:标签数据库什么时候使用,什么时候初始化。建议放到二级页面初始化,减少页面App页面启动的负荷,减少冷启动时间,避免卡顿和ANR。并且标签数据库初始化放在线程中。

二、流畅度测试

流畅度测试简单的来说就是Android页面绘制。Android系统每秒60hz,也就是大约每16ms刷新一次界面。但是在我们使用APP过程中,经常会看到页面有卡顿,或者说丢帧的现象。也就是说可能此刻两个页面绘制的时间差超过0.1S(人眼视觉残留0.1S)。总的来说,就是页面

原理分析

在确定衡量指标之前,我们先来研究一下Android的UI更新机制。

2.1 Android如何绘制UI?=

关于Android是如何更新UI,相信已经有很多文章介绍其中的步骤以及过程,大体上可以用下图来展示:

从图中可以看到无论那条路走下去始终都由SurfaceFlinger来控制最后的更新。

在Android版本更新过程中,发现在Jelly Bean中Google加入了一个Project Butter,用来解决严重影响Android口碑的问题之一“UI流畅性差”的问题。而Project Butter中主要引入了三个核心元素:VSYNC(垂直同步)、Triple Buffer和Choreographer。

2.2 从VSYNC开始

VSync是Vertical Synchronization(垂直同步)的缩写,是一种在PC上很早就广泛使用的技术,可以简单的把它认为是一种定时中断。而在Android 4.1(JB)中已经开始引入VSync机制。

上图所示是VSync机制下的绘制过程。从上图可以看出,CPU和GPU的处理时间都少于一个VSync的间隔,即16.6ms。如果每个间隔都有绘制的情况下,当前的FPS即为60帧。

当CPU和GPU处理时间都很慢,或因为其他的原因,如在主线程中干活太多,那么就会出现如下图这样的状况。

从上图可以看到,CPU和GPU的处理时间因为各种原因都大于一个VSync的间隔(16.6ms),所以在第二个VSync还在处理1区域的绘制时,不可能实现理论上的FPS60,同时也出现了丢帧(SF: Skipped Frame)情况。

为了便于理解,上图用的是双Buffer机制的情况,实际上Android 4.1引入了Triple Buffer,所以当双Buffer不够用时,Triple Buffer丢帧的情况如下图所示。

VSync机制就像是播放动画片(60帧/s)。每次都会播放画面,有的时候有人偷懒了,机器坏了,就会出现播放速度降低的状况。我们把这个播放速度叫做流畅度。

2.3 从FPS&丢帧到流畅度(SM: SMoothness)

实际上在很多Android的App中,很少有需要不断地去绘制的场景,很多时候页面都是静态的。也就是会出现这样的状况,虽然1s中VSync的60个Loop不是每个都在做绘制的工作,FPS会比较低,但并不代表这个时候程序不流畅(如我将App放着不动,实测FPS为1)。所以FPS较低并不能代表当前App在UI上界面不流畅,而1s内VSync这个Loop运行了多少次更加能说明当前App的流畅程度。所以,下面这2个指标比FPS更能代表当前的App是否处于流畅的状态。同样这2个指标更加能够量化App卡顿的程度:

1)丢帧(SF: Skipped Frame):如上图2所示情况应该在16.6ms完成工作却因各种原因没做完,占了后n个16.6ms的时间,相当于丢了n帧。

2)流畅度(SM: SMoothness):和丢帧相对,在VSync机制中1s内Loop运行的次数。

和丢帧相对1s内有60个Loop因为某几次工作时间超过了16.6ms(丢帧),这样Loop就无法运行60次(理论最大值)。

当流畅度越小的时候说明当前程序越卡顿。

2.4 数数:如何得到流畅度(SM: SMoothness)

接着上面的结论,如果在这样的机制下每次Loop运行之前进行通知,记个数就好了。

很幸运我们在新的Android的那一套机制中找到了一个画图的打杂工Choreographer这个对象。根据Google的官方API文档描述中,它是用来协调animations、input以及drawing时序的,并且每个Loop共用一个Choreographer对象。

下图为Choreographer的定义和结构。

2.5 结论

通过如上原理分析可以得出结论:

1) Android 4.1引入了VSync机制后,可以通过其Loop来了解当前App最高绘制能力。

固定每隔16.6ms执行一次(这个值是一个静态变量,会根据系统版本不同而采用不同的值,目前测试版本是16.6ms这样最高的刷新的帧率就控制在60FPS以内);

如果没有以上事件的时候同样也会运行这样一个Loop;

这个Loop在1s之内运行了多少次,即可以表示当前App绘制的最高的能力,也就是Android App卡顿的程度;

另外,在一次Loop时如果执行时间超过了16.6ms,那么用多于16.6ms的时间除以16.6ms,即是当前App的丢帧(SF: Skipped Frame)。

2) 可以在Choreographer的回调FrameCallback中,按秒计数表示当前App的流畅程度,即流畅度SM(SMoothness)。

采用这样方式就可以在App内部观测当前App的流畅度了。并且在丢帧的地方打印traceView,就可以知道丢帧的大概原因,大概位置。定位代码问题。

三、 SmartMonkey测试

Android自动化测试中,monkey测试是一种传统的稳定性测试工具。它可以随机产生事件,不带任何主观性,并且使用方便。但是,正是由于这种随机性,使得传统的monkey测试只能作为稳定性测试工具,在其上进行功能扩展较为不易。在monkey测试中,由于事件的随机性,使得monkey容易卡在某些简单页面,比如登陆页面这种可操作内容很少的页面。

3.1 针对这些问题,我们基于Robotium自动测试框架,开发了SmartMonkey工具。它具有以下这些特点:

准确识别页面上的操作,避免无效点击

支持关键路径配置,使测试范围可控

操作优先级动态变化,覆盖更多功能和页面

多进程基础性能信息自动采集

结合性能专项工具,进一步挖掘性能隐患

支持Checklist配置,提供简单的功能验证

 

3.2 如何使用

SmartMonkey工具本质上是一个大型的case,其使用方法也与普通的case执行方法相似。

建立一个Android Test Project工程

修改AndroidManifest.xml文件

修改instrumentation TAG中的name和targetPackage字段内容如下。

3.3 导入SmartMonkey所需的lib

3.4 在测试工程的src文件中,新建JUnit Test Case该类需继承com.baidu.lynq.lynq.LynQBaseCase类。

3.5  在新建的Case中添加以下code

其中LAUNCHER_ACTIVITY_FULL_CLASSNAME为被测APP的launcher activity,TARGET_PACKAGE为被测包的包名。

    private static final String LAUNCHER_ACTIVITY_FULL_CLASSNAME = “com.example.android.apis.ApiDemos”;

private static final String TARGET_PACKAGE = “com.example.android.apis”;

 

public Test() throws   ClassNotFoundException{

super(TARGET_PACKAGE, Class.forName(LAUNCHER_ACTIVITY_FULL_CLASSNAME));

}

/*

*   Smart monkey 示例

*/

public void test_sample() throws   ClassNotFoundException {

mSolo.sleep(10000);

mSolo.analysis();

}

3.6 添加配置项

在项目中新建assets文件夹,添加AdvancedConfig.properties和config.properties文件

在config.properties文件中添加配置项:

# report path. MUST   BE absolute path.

mReportPath = /mnt/sdcard/lynq-report

# login switch.   value:{true, false}

# true ->   automatic login when ENTER the LOGIN PAGE.

# login page MUST be   baidu pass. (http://wappass.baidu.com/passport/login)

mLoginSwitch = true

# username.   using in login function

mUsername = USERNAME

# password. using in   login function

mPassword = PASSWORD

其中mReportPath是设置输出报告的位置, 默认为/mnt/sdcard/lynq-report/目录,mLoginSwitch是自动登录开关,当前仅限baidu passport SDK自动登录。mUsername和mPassword分别为自动登录时输入的用户名和密码。

在AdvancedConfig.properties文件中添加配置项:

# time slot of   performance collector. unit: ms. value: int

mTimeSlot = 10000

# This is   performance report type.value:int{0,1} defualt is 0,report is html   file. when value equels 1,the report is xml file.

mPerformanceReportType   = 0

# sleep time.   interval between two action. unit: ms. value: int

mSleepTime = 1000

# default text for editview.   value: string

# NEVER enter any:   commenting this config item OR make it empty

mDefaultText = default text IN smart monkey

# text for special   EditText. split by “|”, format: viewID1,content1|viewID2,content2

# viewID: EditText’s   id, content: text for this EditText

mSpInputText = 1234,special text for editText with id 1234|123,special text for editText with id 123

# view never click.   split by “|”

mNeverClick = \u9000\u51FA\u767B\u5F55|button_settings_logout

# view must click.   split by “|”

mMustClick = positiveButton|\u53D6\u6D88

# max operation   running time. working in mode1 && mode2.

# value: String

mMaxRunningTime = 08:00:00

其中,mTimeSlot是性能数据采集的间隔,mPerformanceReportType是设置性能报告输出样式,1为xml样式,0为html样式。

mSleepTime是两次事件的执行间隔,mDefaultText是默认的SmartMonkey在EditText中输入的内容。mSpInputText中可以配置在某些输入框中特殊输入的内容用竖线分割,比如,” 1234,special text for editText with id 1234”,表示在id为1234的view中,输入“special text for editText with id 1234”内容。

mNeverClick和mMustClick用来表示关键路径,分别为避免点击的view和必点的view。View可以用文字或十进制id或id string来表示。例如下图中右下角OK按钮,在R文件中是“public static final int btn_ok=0x7f09001d; ”,所以,这个view可以用其上文字“OK”来表示,也可以用id string “btn_ok”或十进制id“2131296285”来表示。

mMaxRunningTime是SmartMonkey的最大执行时间,用时分秒来表示。

3.7 执行

通过Run as Android Junit Test方式执行。

 

四、 查看输出报告

4.1 crash信息

SmartMonkey会自动记录被测APP的crash栈信息,以及native crash信息。

Crash信息会输出在你配置的目录中,以stack为开头的txt文件。每个crash单独输出一个文件。Native crash信息记录在以dmp开头的文件中,可以通过google-breakpad进行查看。

4.2 基础性能报告

根据配置项,SmartMonkey会输出性能报告到输出报告目录中。性能报告是以performance开头的html或xml文件。

Html格式的性能报告中,首先会列出被测app的相关信息,包括包名、uid和同uid下的每一个进程的pid和进程名等。随后列出CPU、内存、流量的图表。CPU图表中记录了每一个进程的CPU占用率,内存图表中记录了每个进程PSS和USS的占用情况,流量图表中记录了流量总使用情况和两个采集点之间的流量差值。

在每个图表上,用node记录了这个节点上SmartMonkey执行的事件,可以用来辅助定位造成曲线波动的操作。

Xml格式的性能报告中,每个operation为一个采集点,其中记录了时间戳、测试手机总CPU占用率、流量差值、流量总和、节点上的事件,以及每个进程的pid、CPU占用率、PSS、USS等。

共有 0 条评论评论