转载

Android性能测试

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

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

一、卡顿ANR测试

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

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

Android性能测试

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闪退,持久化当时信息。

原理图:

Android性能测试

问题背景:

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

测试方法:

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

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

Android性能测试

我们能够看到很明显的看出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,相信已经有很多文章介绍其中的步骤以及过程,大体上可以用下图来展示:

Android性能测试

从图中可以看到无论那条路走下去始终都由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机制。

Android性能测试

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

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

Android性能测试

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

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

Android性能测试

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的定义和结构。

Android性能测试

结论

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

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字段内容如下。

Android性能测试

3.3  导入 SmartMonkey 所需的 lib

Android性能测试

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

Android性能测试

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样式。

m SleepTime是两次事件的执行间隔,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的最大执行时间,用时分秒来表示。

Android性能测试

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执行的事件,可以用来辅助定位造成曲线波动的操作。

Android性能测试

Android性能测试

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

Android性能测试

原文  http://qa.baidu.com/academy/detail/article/62
正文到此结束
Loading...