本文围绕以下性能指标介绍测试Android端的测试方法:
启动时间:启动APP所需时间
流畅度:也称为帧率FPS,指画面每秒传输帧数;帧率越大,页面越流畅。
过渡绘制:过度绘制描述的是屏幕上的某个像素在同一帧的时间内被绘制了多次。
内存:占用系统内存的大小
CPU:对系统CPU的占用率
流量:流量消耗
电量:电量消耗
性能标准及Android如何获取数据
1. CPU
CPU测试,主要关注的是cpu的占用率。很多时候,我们玩手机时,会出现发热发烫,那是因为CPU使用率过高,CPU过于繁忙,会使整个手机无法响应用户,整体性能降低,用户体验就会很差,也容易引起ANR(application not responding, 主线程(UI线程)如果在规定时内没有处理完相应工作,就会出现ANR)等等一系列问题。
- 静态不超过5%,后台静默不超过1%,一般运行状态不超过30%,高负荷运行不超过75%,峰值不超过90%;
1.1 数据获取
- 每隔1s获取对应app的cpu占用
adb shell "top –d 1 | grep <pakagename | pid>"
top cpu 参数:
-m 显示最大数
-s 按指定行排序
-t 显示进程名称
-n 在退出前刷新几次
-d 刷新间隔
adb shell "dumpsys cpuinfo | grep <pakagename | pid>"
美团app占用总cpu的51%,在用户(user)中花32%的时间,并在内核空间(kernel)花费18%的时间
注:两种方法直接区别在于,top是持续监控状态,而dumpsys cpuinfo获取的实时CPU占用率数据
1.2 cpu检测的几种情况
- 空闲状态下的应用CPU消耗情况(程序运行后按home键挂后台)
- 中等规格状态下的应用CPU消耗情况(程序执行普通操作时的cpu占用)
- 中等规格状态下长时间的应用CPU消耗情况
- 满规格状态下的应用CPU消耗情况
- 针对性的场景测试
例如对软件使用较多的场景进行测试
1.3 测试点:
1)在空闲时间(切换至后台)的消耗,基本没大应用使用cpu
2)在运行一些应用的情况下,cpu已占50%的情况下,观察应用程序占用cpu的情况
3)在高负荷的情况下看CPU的表现(cpu占用应是在80%以上)
1.4 具体场景:
1、应用空闲状态运行监测CPU占用率
空闲状态:应用按Home键退到后台,不再占用系统的状态(通常是灭屏半分钟后)
CPU占用率=0%
2、应用中等规格运行监测CPU占用率
中等规格:模拟用户最常见的使用场景
CPU占用率≤30%
3、应用满规格长时间正常运行监测CPU占用率
Monkey测试
CPU占用率≤30%
4、应用正常运行期间监测CPU占用率峰值
应用正常运行:打开应用进行基本操作
CPU占用率≤50%
2. 内存
在Android系统中,每个APP进程除了同其他进程共享内存(shared dirty)外,还独用私有内存(private dirty),通常我们使用PSS(私有内存+比例分配共享内存)来衡量一个APP的内存开销。由于一个移动设备的内存是固定的,如果内存消耗过大就会造成应用卡顿或者闪退,需要对内存进行测试。正常情况下,应用不应占用过多的内存资源,且能够及时释放内存,保证整个应用内的稳定性和流畅性。
- 操作单次申请pss内存超过50M需查看是否合理,java堆内存单次操作申请内存不超过20M;
- 不存在内存抖动问题,频繁GC;
- 不存在内存持续增长问题,内存泄漏;
- 不存在一段时间内存持续增长后无法释放问题,如位图未释放或重复缓存;
- 返回mainActivity后不存在其他Activity实例;
- hprof(内存快照),只可分析java层泄漏
内存说明
VSS - Virtual Set Size 虚拟耗用内存(包含共享库占用的内存)
RSS - Resident Set Size 实际使用物理内存(包含共享库占用的内存)
PSS - Proportional Set Size 实际使用的物理内存(比例分配共享库占用的内存)
USS - Unique Set Size 进程独自占用的物理内存(不包含共享库占用的内存)
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
adb 查看单个内存占用量
- 单个应用的最大内存限制
adb shell "getprop | grep heapgrowthlimit"
- 应用启动后分配的初始内存
adb shell "getprop | grep dalvik.vm.heapstartsize"
- 单个java虚拟机的最大内存限制
adb shell "getprop | grep dalvik.vm.heapsize"
2.1 数据获取
获取对应app的内存占用(packagename【包名】或pid【进程id】两个参数二选一)
adb shell "dumpsys meminfo <pakagename | pid>"
输出结果分以下4部分:
- process 以进程的PSS从大到小依次排序显示,每行显示一个进程;
- OOM adjustment 分别显示每类的进程情况
- category 以Dalvik/Native/.art mmap/.dex map等划分的各类进程的总PSS情况
- total 总内存、剩余内存、可用内存、其他内存
执行结果如下图,重点取该应用的 TOTAL 对应的内存占用大小(KB)
2.2 内存检测的几种情况
(1) 空闲状态下的应用内存消耗情况(程序运行后按home键挂后台)
(2) 中等规格状态下的应用内存消耗情况(注意查看是否申请的内存过大)
(3) 满规格状态下的应用内存消耗情况
(4) 针对性的场景测试
较容易出现内存泄漏的部分场景:
-
activity间的切换,只要非静态的匿名类对象没有被回收,MainActivity就不会被回收,MainActivity所关联的资源和视图都不会被回收,发生比较严重的内存泄漏。
-
连续查看和发送大图片,不断反复观看返回继续观看等操作,都有可能因为和之前的内存资源没释放而导致内存不断增长。
-
有执行异步线程的场景后如果未给线程进行结束,会引起内存泄漏,因为activity的结束销毁不会把正在运行的thraead也结束回收掉。比如后台下载或加载东西时关闭activity。
-
在activity关闭时Handler还没结束,会到导致内存泄漏。例如一些界面UI还在刷新时关闭activity。
-
从登录界面登录账号后,登录界面的activity只是退到后台或是被登录后的activity覆盖,像这种过渡界面的acticity容易出现未去摧毁而出现内存泄漏。
对内存泄漏测试后期可预研使用LeakCanary,结合jenkins,定时取版本源码接入LeakCanary后执行monkey测试,查看是否能检查到内存泄露信息(此方法需要提供源码)。
2.3 测试标准
测试场景中内存不会出现持续上升或短时间内出现内存抖动情况和无故申请过大内存的情况
2.4 测试点
1、空闲状态:切换至后台或者启动后不做任何操作,消耗内存最少。
2、中强度状态:时间偏长的操作应用。
3、高强度状态:高强度使用应用,可以跑monkey来测试(通常用来测试内存泄漏)。
2.5 测试关注点
1、Native heap alloc
2、Dalvik heap alloc
3、PSS
2.6 关注点
1、退出某个页面后,内存是否有回落。
如果没有及时回落,且程序自动GC或者手动GC,那便可确认有问题。
2、进行某个操作后,内存是否增长过快。
如果增长过快,也有可能存在风险,需重复操作确认。
3.启动时间(native)
软件的响应时间和响应速度直接影响到用户的体验度,如果一个软件,迟迟加载不出来,会直接影响到软件的日活、留存。因此对于一个软件,对响应速度测试是必不可少的。
- 应用冷启动<=600ms为优秀,<=800ms为良好,<=1000ms为可接受;
- 应用热启动<=200ms为优秀,<=400ms为良好,<=600ms为可接受;
3.1 数据获取
获取activity的方法:
1)日志打印:获取运行中的activity,同时存在耗时的+457ms,总计耗时896ms
adb logcat ActivityManager:I *:s
2)获取当前页面的activity
adb shell "dumpsys activity | grep mFocusedActivity"
- 通过adb shell am start -W –S –n
获取应用首次启动时间
- 通过adb shell am start –W –n
获取应用二次启动时间
① WaitTime 就是总的耗时,包括前一个应用 Activity pause 的时间和新应用启动的时间;TotalTime+系统资源启动时间;
② ThisTime 表示一连串启动 Activity 的最后一个 Activity 的启动耗时;该Activity的启动耗时;
③ TotalTime 表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用 Activity pause 的耗时。也就是说,开发者一般只要关心 TotalTime 即可,这个时间才是自己应用真正启动的耗时。
使用am命令获取的时间只是apk从onCreate到onResume的时间,但不包含UI界面呈现出来的时间,所以考虑计算从am命令执行到logcat中有displayed出现的时候,视为app已经启动并展现,作为结束时间。此方法并不能完全计算出启动时间,有一定的误差。
3.2 App启动时间检测的几种情况
1、冷启动:首次启动app的时间间隔(只是启动时间,不包括页面加载)
2、热启动:非首次启动app的时间间隔(只是启动时间,不包括页面加载)
3、完全启动:从启动到首页完全加载出来的时间间隔
4、有网启动:从发起跳转,到页面完全加载出来的时间间隔
5、无网启动:从发起跳转,到页面完全加载出来的时间间隔
(在项目中,主要测试关注点是冷启动,热启动)
3.3 测试标准
首次启动时间<=600ms为优秀,<=800ms为良好,<=1000ms为可接受
二次启动时间<=200ms为优秀,<=400ms为良好,<=600ms为可接受
横向对比(与竞品对比)和纵向对比(与以往版本对比)
测试标准:冷启动时间不超过1.5s,热启动不超过1s
4.响应时间(native)
Activity切换时间<=100ms为优秀,<=200ms为良好,<=500ms为可接受;
4.1 数据获取
硬件:通过高速相机
软件:通过logcat日志的Displayed关键字
adb logcat | findstr Displayed
每切换一个页面会自动计算出消耗时间
4.2 Activity切换时间检测的几种情况
- 各主要场景页面的切换
- 关注一些一进入就需要进行加载的activity
- 针对性的场景测试
例如其他一些需要关注的页面切换等
4.3 测试标准
Activity切换时间<=100ms为优秀,<=200ms为良好,<=500ms为可接受
横向对比(与竞品对比)和纵向对比(与以往版本对比)
5.流畅度(FPS)
- 帧率<=50需优化,>=55为良好,>=57为优秀;
- 不出现连续丢帧情况;
5.1 数据获取
1、方式一:adb命令
1)手机开启开发者模式,开启“GPU呈现模式分析(profile GPU rendering)”,选择“在adb shell dumpsys gfxinfo中”
2)获取数据计算滑动帧率和掉帧数
adb shell "dumpsys gfxinfo <pakagename>"
如上图信息表示了每一帧在安卓系统中的四个阶段:
Draw:表示在Java中创建显示列表部分中,OnDraw()方法占用的时间
Prepare:准备时间
Process:表示渲染引擎执行显示列表所花的时间,view越多,时间就越长
Execute:表示把一帧数据发送到屏幕上排版显示实际花费的时间,其实是实际显示帧数据的后台缓存区与前台缓冲区交换后并将前台缓冲区的内容显示到屏幕上的时间
将上面的四个时间加起来就是绘制一帧所需要的时间,如果超过了16.67就表示掉帧了
计算帧率:FPS=1000/(Draw+Prepare+Process+Execute)
说明
Android定义了流畅度的数据标准,以60FPS为标准(FPS为每秒绘制的帧数),帧数过小就会出现卡顿感;
每一帧在安卓系统中分4个阶段,4个阶段的总和超过16.67(1秒60帧,算下来平均1帧的间隔就约是16.67ms)就认为丢帧;
这个定义在Android6.0以前是一定的,但是现在已经没有固定的标准了,因为目前安卓系统有3层缓存机制,加上硬件上的进步,即使超过16.67,也不一定会出现卡顿感。所以这个数据在测试时作为一种对比和相对衡量标准,也可根据需求自定义标准。
2、方式二:开发者选项自带的图表
1)打开手机:开发者选项->GPU渲染模式分析(GPU呈现模式分析) ->在屏幕上显示为条形图
5.2 滑动帧率检测的几种情况
- ListView界面的帧率
- 可滑动界面帧率,如长的textview或可滑动的长图等
- 动画较多的页面操作帧率
- 针对性的场景测试
可能引起掉帧的原因:
1.手机性能差,CPU + GPU工作效率低下
2.横竖屏切换,需要用savedInstanceState保存的view信息进行重画
3.动画效果太多
4.GC太多
5.UI线程阻塞
6.界面试图结构过于复杂(可以通过Hierachy View查看)
7.过度绘制
注意:如果只掉了一帧,但是这帧所占时间较长,比如占了几个垂直同步信号的时间,虽然总体的FPS值还不错,但依然能肉眼感知到这帧的卡顿,所以这种情况也需注意。
5.3 测试标准
应结合手机硬件性能决定性能标准,但建议最低也要大于50
横向对比(与竞品对比)和纵向对比(与以往版本对比)
6.过渡绘制(卡顿、慢)
GPU渲染是指在一个像素点上绘制多次(超过一次):显示一个什么都没有做的activity界面算作画了1层,给activity加一个背景是第2层,在上面放了一个Text View(有背景的Text View)是第3层,Text View显示文本就是第4层仅仅只是为了显示一个文本,却在同一个像素点绘制了四次,这是一定要优化的。过度绘制对动画性能的影响是极其严重的,如果你想要流畅的动画效果,那么一定不能忽视过度绘制。
6.1 数据获取
进入开发者选项->调试GPU过度绘制->显示过度绘制区域
6.2 GPU过渡绘制检测的几种情况
各/主要页面
针对性的场景测试
6.3 GPU过渡渲染不同的颜色代表不同的绘制程度
1)原色:无过渡绘制
2)蓝色:绘制一次 (理想状态)
3)绿色:绘制二次
4)浅红:绘制三次 (可以优化)
5)深红:绘制四次 (必须优化)
6.4 测试标准
- 不允许出现黑色像素
- 不允许存在4X过渡绘制
- 不允许存在面积超过屏幕1/4区域的3X过渡绘制
- 动态页面、可滑动/滚动列表,还可参考CPU的数据
一些静态页面,一般是无法修改的
横向对比(与竞品对比)和纵向对比(与以往版本对比)
7.流量
7.1 数据获取
1、方式一
注:<内容>替换成对应的数据
1)获取应用的进程id(PID)
adb shell "ps | grep <包名>"
2)根据PID获取app的Uid(Uid是每个app安装时候分配的唯一编号用于识别该app)
adb shell "cat /proc/<pid>/status | grep Uid"
3)读取tcp_rcv获取应用接收流量信息(设备重启后清零)
adb shell cat /proc/uid_stat/<Uid>/tcp_rcv
4)读取tcp_snd获取应用发送流量信息(设备重启后清零)
adb shell cat /proc/uid_stat/<Uid>/tcp_snd
计算流量消耗步骤:
- 执行一次这两条命令,获取到应用的开始接收及发送的流量
- 然后我们再操作应用,执行一段时间测试
- 再次执行上述 2 条命令可以获取到应用结束时候的接收及发送的流量,通过相减及得到应用的整体流量消耗
2、方式二
1)获取应用的进程id(PID)
adb shell "ps | grep <包名>"
2)根据PID获取app的Uid(Uid是每个app安装时候分配的唯一编号用于识别该app)
adb shell "cat /proc/<pid>/status | grep Uid"
3)执行命令
adb shell cat /proc/net/xt_qtaguid/stats | grep <Uid>
其中第6列代表rx_bytes接收的字节数,第8列为tx_bytes发送的字节数
一个uid可能对应多个进程,把数据累加就行
两个时间片中间应用流量的消耗,就计算接收字节数的差值就行
说明
-
android4.0以上版本可以用/proc/uid_stat/Uid/tcp_rcv和/proc/uid_stat/Uid/tcp_snd来获取某个程序的上下行流量;
-
4.0以下版本要用cat/proc/Pid/net/dev来查看上下行流量。uid和pid的关系,可以从/data/system/packages.list这个文件中获取,但是/proc/uid_stat/Uid这种方式,在Android>/tcp_rcv和/proc/uid_stat/Uid/tcp_snd来获取某个程序的上下行流量;而4.0以下版本要用cat/proc/Pid/net/dev来查看上下行流量。
Uid和Pid的关系,可以从/data/system/packages.list这个文件中获取,但是/proc/uid_stat/uid这种方式,在Android 6.0系统中部分厂商可能是做了某些修改,在proc目录下没有显示uid_stat文件夹。
获取Android(Linux)流量信息:
在Linux系统有3个地方保存流量统计文件,对于Android系统同样也适用:
(1)在/proc/net/dev下可以查看各个网络接口的收发流量 (等同adb shell cat /proc/pid/net/dev,adb shell cat /sys/class/net/wlan0/statistics/rx_bytes);
(2)在/sys/class/net/下可以找到相关类别(如wlan0)的目录.在其子目录statistics下游rxbytes和txbytes记录收发流量;
(3)在/proc/uid_stat/Uid/tcp_rcv记录该uid应用下载流量字节,/proc/uid_stat/Uid/tcp_snd有该Uid应用上传流量字节;
7.2 流量检测的几种情况
(1) 应用首次启动流量
(2) 应用后台长时间运行后的流量值
(3) 针对性的场景测试
例如发送接收图片,发送接收视频,刷新列表等操作
7.3 测试标准
- 不存在重复请求;
- 页面静默时或处于后台不应有流量增加;
- 缓存机制的逻辑合理;
- 在移动网络下不应自动下载素材等数据;
- 小缩略图小于5KB
- 中缩略图小于25KB
- 大缩略图小于50KB
8.电量
要通过adb命令来监控手机的耗电量,所以建议通过wifi的方式来连接手机,而不是用usb线来连接手机设备。usb方式连接手机会自动给手机充电,这样会影响对耗电量的统计的准确性。
8.1 连接手机
# 将 Android 设备与要运行 adb 的电脑连接到同一个局域网,比如连到同一个 WiFi
# 先使用原装数据线把电脑与手机连接,手机在开发者选项中开启USB调试
# 使用adb devices命令查看是否与设备连接
$ adb devices
List of devices attached
10.1.217.238:5555 device
04c5a5af52197902 device
# 显示device表示连接成功
# 断开数据线
$ adb connect 10.1.217.238
unable to connect to 10.1.217.238:5555: Connection refused
# 输入 adb tcpip 5555命令
# 打印 “restarting in TCP mode port: 5555”表示启动成功
# 找到手机的 IP 地址
# 进入手机的WIFI连接,进入连接属性,查看手机的 IP
# 也可在「设置」-「关于手机」-「状态信息」-「IP地址」找到
# 输入adb connect '手机ip地址'
# 出现 connected to ip:5555 表示连接成功
8.2 获取APP的uid
使用adb shell COLUMNS=512 top
或者adb shell ps | findstr <包名>
8.3 重置电池数据收集数据
- 清除已有的耗电量数据
adb shell dumpsys batterystats --enable full-wake-history
- 重置设备耗电量数据
adb shell dumpsys batterystats --reset
- 然后执行业务场景,测试完成之后再执行以下命令取出APP电量消耗日志(把数据重定向到本地)
adb shell dumpsys batterystats > D://batterystats.txt
在文件中搜索 Estimated power use (mAh): 即可跳转到App电量消耗位置。
之前获取的Uid值 u0_a487 需要将下划线 _ 去掉变成 u0a487 ,然后通过该UID在日志文件中查找则可搜索到该APP对应的耗电量。
其中, Uid u0a487:121 中的 121 为该APP在测试期间消耗的总电量,单位为 mAh ,圆括号中详细说明了电量消耗的详细信息