本文由作者张迎贞授权网易云社区发布。
APP性能测试除了需要监控PCU、内存占用、流量等,还需要获取APP的电量数据,测试在可接受范围内,避免APP出现过度消耗电量的现象。
手机有很多硬件模块:CPU,蓝牙,GPS,显示屏,Wifi,射频(Cellular Radio)等,在手机使用过程中,这些硬件模块可能处于不同的状态,譬如WIFI打开或关闭,屏幕是亮还是暗,CPU运行或休眠。 硬件模块在不同的状态下的耗电量是不同的。Android在进行电量统计时,并不是采用直接记录电流消耗量的方式,而是跟踪硬件模块在不同状态下的使用时间,收集一些可用信息,用来近似的计算出电池消耗量。
从用户使用层面来看,Android需要统计出应用程序的耗电量。应用程序的耗电量由很多部分组成,可能使用了GPS,蓝牙等模块,可能应用程序要求长时间亮屏(譬如游戏、视频类应用)。 一个应用程序的电量统计,可以采用累计应用程序使用所有硬件模块时间这种方式近似计算出来。
举一个例子,假定某个APP使用了GPS,使用时间用 t 表示。GPS模块单位时间的耗电量用 w 表示,那么,这个APK使用GPS的耗电量就可以按照如下方式计算:
耗电量 = 单位时间耗电量(w) × 使用时间(t)
测试方法多种多样:
1、CPU空闲时,停留在主界面不退出,打开网络然后锁屏,12小时后查看电量、流量变化
2、APP在操作运行时(此过程可借助使用monkey进行压力测试),1小时后查看电量、流量变化
3、第三方APP进行步骤1、2相同的场景测试
4、对比竞品APP的耗电量。
期望结果:APP在静止状态时无没有明显的耗电情况,在运行状态时耗电量在可接受范围内。
方法1——手机自带
有的手机设置菜单里面会有流量和电量统计类,只需在测试前后记录下电量值,便可获取消耗电量数值。
方法2——使用第三方工具
已知公式 : 耗电量 = 电流 * 时间
例:平均电流为150mA,测试时间30min,则,30min内耗电量为:150 * 30 /60=75mAh(毫安小时)。
通过耗电量和使用手机电量的百分比,可得出手机电池容量(理论值)。
一个系统走一遍基础功能耗电不应超过20%。
1、Emmagee
https://github.com/NetEase/Emmagee
Emmagee是网易杭州研究院QA团队开发的一个简单易上手的Android性能监测小工具,主要用于监控单个App的CPU,内存,流量,启动耗时,电量,电流等性能状态的变化,且用户可自定义配置监控的频率以及性能的实时显示,并最终生成一份性能统计文件。
2、腾讯GT
http://gt.tencent.com/download.html
此apk是一款可以对APP进行测试的软件,可以在任何情况下快速测试手机app的CPU、内存、流量、电量、帧率/流畅度等性能测试。
使用方法:
打开GT,点击选择被测应用,选中自己要测的app,选择关注的测试点,cup、net(流量)、内存(pss)等,点击“启动”,在gt的插件标签里有耗电测试,导出文件查看电量使用。
3、其他各种常见耗电量监控工具
金山电池医生:能够记录CPU耗时、流量消耗随时间的变化曲线,这样可以查看app耗电在哪些时间点.
GSam Battery Pro:耗电量信息显示详细,包括了唤醒锁数,前后台占用cpu的时间,可以导出数据,支持多种监控起始时间点设置
PowerTutor:1.显示系统电量消耗水平,记录耗电量的焦耳值,不是百分比,包括LCD/OLED,CPU,WiFi,3G,GPS和Audio;2.查看某段时间内所有运行中的应用程序的耗电量
Battery Monitor Widget:高度可定制化的电量监控小工具,不仅可以显示当前电量,估算剩余电量支撑时间,还可以一目了然地检测出各个APP的耗电历史,从而方便进行比较。
Smart Battery Monitor:除了可以在状态栏显示电池消耗百分比外,还可以显示电池的温度,以及已充电时间。
Battery Indicator:亮点是轻便、小巧,甚至使用它时不会消耗电池。当然,它可以显示电池的电量百分比、电池的温度、以及电池的健康信息。
Battery Widget:这个工具不仅是一个电池Widget,还可以作为显示选项,GPS,WiFi,蓝牙选项等的快捷方式。
Battery Saver:被称为管理电池的最强大应用之一。除了显示电量信息,电池温度和健康信息外,它还可以快速管理一些耗电量大的应用如:GPS,WiFi,蓝牙等。另外:该工具可以查看一天之内的哪个时间点电池的耗电量最大。|
等等等等
方法3——Battery Historian
Android框架层通过一个名为batterystats的系统服务,电池的信息,电压,温度,充电状态等等,都是由BatteryService来提供的。电池的这些信息是BatteryService通过广播主动把数据传送给所关心的应用程序。实现了电量统计的功能,batterystats实现原理可以查阅电量统计服务
Android提供的dumpsys命令用于查看系统服务的信息(实现原理可以查阅dumpsys介绍), 将batterystats作为参数,就能输出完整的电量统计信息。
执行:
手机连接usb执行:
adb shell dumpsys batterystats --enable full-wake-history adb shell dumpsys batterystats –reset //清空电池的历史状态
断开USB,打开目标应用,执行monkey,正常使用5分钟左右。
连接USB,执行:
adb bugreport > bugreport.txt 也可以用adb shell dumpsys batterystats > com.nt.topline > toplinepower1.txt //得到指定app相关的电量消耗信息。 python historian.py -a bugreport.txt > batterystats.html
上面的historian.py脚本是python写的,所以需要python环境,从github上下载这个脚本。Chrome浏览器打开生成的battery.html文件,打开查看。
html中信息都能从bugreport.txt中找到相应的信息。
分析各个指标代表的意义:
上面的10,20代表的就是秒的意思,它是以一分钟为周期,到第60秒的时候变为0。横坐标就是一个时间范围,咱们的例子中统计的数据是以重置为起点,获取bugreport内容时刻为终点。我们一共采集了多长时间的数据,图表下也有信息说明。(经其他人的反馈,这个坐标间隔是会随着时间的长度发生改变,所以要以你的实际情况为准)
battery_level:电量,可以看出电量的变化。比如上图中的数据显示刚开始电量是100%,然后在第11秒-12秒中间的某个时刻降到了99%。
plugged: 充电状态,这一栏显示是否进行了充电,以及充电的时间范围。例如上图反映了我们在第22s插入了数据线,然后一直持续了数据采集结束。
screen: 屏幕是否点亮,这一点可以考虑到睡眠状态和点亮状态下电量的使用信息。
top:该栏显示当前时刻哪个app处于最上层,就是当前手机运行的app,用来判断某个app对手机电量的影响,这样也能判断出该app的耗电量信息。该栏记录了应用在某一个时刻启动,以及运行的时间,这对我们比对不同应用对性能的影响有很大的帮助。
wake_lock*: wake_lock 该属性是记录wake_lock模块的工作时间。是否有停止的时候等。Android的休眠唤醒主要基于wake_lock机制,只要系统中存在任一有效的wake_lock,系统就不能进入深度休眠,但可以进行设备的浅度休眠操作。wake_lock一般在关闭lcd、tp但系统仍然需要正常运行的情况下使用,比如听歌、传输很大的文件等。
running:界面的状态,主要判断是否处于idle的状态。用来判断无操作状态下电量的消耗。
wake_lock_in:wake_lock有不同的组件,这个地方记录在某一个时刻,有哪些部件开始工作,以及工作的时间。
data_conn:数据连接方式的改变,上面的edge是说明采用的gprs的方式连接网络的。此数据可以看出手机是使用2g,3g,4g还是wifi进行数据交换的。这一栏可以看出不同的连接方式对电量使用的影响。
status:电池状态信息,有充电,放电,未充电,已充满,未知等不同状态。 这一栏记录了电池状态的改变信息。
phone_signal_strength:手机信号状态的改变。 这一栏记录手机信号的强弱变化图,依次来判断手机信号对电量的影响。
health:电池健康状态的信息,这个信息一定程度上反映了这块电池使用了多长时间。 这一栏记录电池状态在何时发生改变,上面的图中电池状态一直处于good状态。
plug:充电方式,usb或者插座,以及显示连接的时间。 这一栏显示了不同的充电方式对电量使用的影响。
gps:gps是否开启
phone_in_call :是否进行通话
Sync :是否跟后台同步,可以把鼠标停在某一项上面。可以看到何时sync同步 启动的,持续时间Duration多久。电池容量不会显示单一行为消耗的具体电量,这里只能显示使用电池的频率和时长,你可以看分时段的剩余电量来了解具体消耗了多少电量。
Job :后台的工作,比如服务service的运行。从下面图中可以看到qihoo的AppStore和鲁大师都在运行后台服务。
data_conn:数据连接方式的改变,上面的edge是说明采用的gprs的方式连接网络的。此数据可以看出手机是使用2g,3g,4g还是wifi进行数据交换的。这一栏可以看出不同的连接方式对电量使用的影响。
status :电池状态信息,有充电,放电,未充电,已充满,未知等不同状态。 这一栏记录了电池状态的改变信息。
phone_signal_strength :手机信号状态的改变。 这一栏记录手机信号的强弱变化图,依次来判断手机信号对电量的影响。
结果分析:
通过前面学习到的Battery Historian我们可以得到设备的电量消耗数据.
例如拿移动网络举例,如果数据中的移动蜂窝网络(Mobile Radio)电量消耗呈现下面的情况,间隔很小,又频繁断断续续的出现,说明电量消耗性能很不好:
经过优化之后,如果呈现下面的图示,说明电量消耗的性能是良好的:
另外WiFi连接下,网络传输的电量消耗要比移动网络少很多,应该尽量减少移动网络下的数据传输,多在WiFi环境下传输数据。
耗电原因分析
主要造成耗电的几大原因:
屏幕唤醒
CPU唤醒
蜂窝数据
传感器
可能造成耗电的原因
网络请求耗电,而且手机数据网络进行http请求比无线网进行http请求更加耗电,因为数据网络调用到一些底层的硬件模块,就如GPS一样,当手机打开GPS功能后,也是启动了一些硬件模块就会明显增加耗电
高频的刷新UI界面,刷新UI界面其实就是进行layout的绘制,如果一个Activity的布局嵌套太多层,那么每一层layout都会刷新一次,例如动画等等这些都会造成耗电
数据库,SD卡文件操作,这些都是属于耗时操作,当操作次数很少的时候基本不会有耗电问题,但是当短时间内操作次数很多的话,也会明显的增加耗电,同时也有可能造成页面卡顿
AlarmManager,例如一些推送的心跳包实现,AlarmManager会定时唤醒CPU去执行一些任务,也是造成耗电的一大源头
手机网络环境不好的时候会频繁的切换网络,因为网络数据交互的时候,系统也是会被唤醒的,所以APP如果在监听了网络切换广播后做了大量的操作,一样会增加耗电
针对一些任务队列的处理,如果队列堆积的任务太多,导致循环执行太久也会造成耗电,因为占用了CPU资源去执行代码,我们的log日志工具保存到文件就是用任务队列实现的,当压力测试SDK一次性接受1万条消息的时候,那内存就表上来了,跟了下发现日志保存队列里面积压了4千多个任务,这时候即使手机锁屏,也还会不断的把队列中的任务执行完然后CPU才会休眠下去的,同样会造成严重的耗电,耗内存,好在release版本的日志都是关闭的
执行一些高运算量的代码,例如json数据解析,一些二进制协议的数据编码和解码
接收系统的一分钟广播,然后做一些程序逻辑处理,其实接收一分钟广播不耗电,耗电的是一分钟执行一次程序处理
Wake Lock使用不当导致没有及时的释放,Wake Lock可以阻止cpu进入休眠的,如果没有及时的release会造成cpu无法休眠,程序耗电严重
如果程序中有定时任务,在cpu休眠之后,定时任务就会被挂起不执行,这时候并不会造成太大的耗电,但是如果这个定时任务的时间间隔很短,1秒执行一次,那么当手机app集成了推送,推送就会有心跳包通过AlarmManager来唤醒,每次唤醒的时候就会再去执行挂起的定时任务,虽然执行定时任务的耗电量可能比心跳包的耗电量少很多,不过还是需要注意的,积少成多
其实android中的Log日志的打印也会耗电的,在日常开发中,我们可能不仅会把log打印到AndroidStudio里面,有可能还会保存起来,而且可能在打印对象信息数据的时候会用到json格式转换,这些都会增加耗电,但是在正式发布的apk包中日志管理一般都是关闭的
在手机锁屏后,CPU会过一段时间才休眠,如果程序中有定时任务,在CPU休眠后会被挂起不执行,但是在CPU休眠之前,定时任务还是会一直的执行的,之前遇到过这么一个问题,我们采用Picasso库:Picasso.with(context)
优化建议:
针对主要耗电原因的优化:
o 传感器的使用,比如gps,如果业务上非频繁使用并且要求精度不高,那么就在业务上优化定位时机和精度.
o 如果业务上需要有长时间的service,那么要考虑到CPU weak lock,防止后台任务没有完成,但是cpu睡眠,或者任务已经完成,仍然持有weak lock
o 我们也许会在手机充电时,做一些日志上传操作的定时任务,那么注意重试次数和成功后处理,防止用户一夜充电不到80%,然后手机厂商为我们背锅的情况
o 已知屏幕唤醒是耗电大户,原因是屏幕渲染,绘制消耗资源,所以我们在自定义视图,布局的时候,要尽量做到Android性能优化里要求
o 同理,各种优化,平时编码的习惯,都会影响我们脆弱的电量
o 为了减少电量的消耗,在蜂窝移动网络下,最好做到批量执行网络请求,尽量避免频繁的间隔网络请求。
针对Http请求优化:
o 对http请求数据做GZIP压缩,当前流行的http第三方看默认都支持GZIP压缩
o Http缓存,Http协议有一个Cache机制,当发出http请求的时候会先到指定目录下检查是否已经存在这个请求的数据,如果存在并且还没过时,那么就会直接返回;而一些第三方例如OkHttp也有有自己的缓存 机制OKHTTP缓存
o 合并Http请求来减少Http请求次数,因为Http底层也是TCP连接,对于每个Http请求,发出请求的时候都会创建TCP连接,请求结束后会断开TCP连接,那么当Http请求次数很多的时候就会频繁的创建和断开TCP连接,如果把当中一些请求进行合理的合并,那么就会减少Http请求次数
o 制定合理的Http请求数据格式和返回数据格式,做到请求数据中没有冗余字
o 可以在Http请求数据格式里面加一个字段dataVersion代表本地已有数据的版本号,然后传到服务器,服务器的数据表中也有一个字段是dataVersion,当服务器数据被修改的时候,dataVersion就加一,当检测到客户端传上来的dataVersion小于服务器数据表中的dataVersion的时候就返回最新数据,否则可以直接返回空数据代表当前本地数据已经是最新数据,这样就不会每次请求http的时候都会返回大量数据,当数据没有被改变的时候直接返回空,减少了http请求过程中的数据交互(但是要考虑一点,只有一些请求数据量比较大的才适合,因为增加了dataVersion字段后无论给客户端还是服务端也都相应的增加了维护的成本)
针对数据库,SD卡文件操作:
o APP在对数据库或者SD卡文件操作的时候无非也是涉及到一些数据的转换,json转换,可以采用json解析效率高的第三方库,例如fast json,Jackson,gson
o 可以把一些需要持久化到数据库或者文件中的数据先缓存在内存中,然后在一个时间点一起触发一同更新到数据库或者文件中;例如,在进入Activity的时候会首先从数据库中搜索出帐户信息并且展示在界面,然后会再去发Http请求服务器的帐户信息数据,再把服务器最新的帐户信息数据刷新到界面中,同时也存在一个内存对象中,这时候先不更新到数据库,当退出这个Activity界面的时候可以去检测帐户数据是否发生改变,如果改变了就更新到数据中;这么做的好处是:如果在Activity界面多次修改数据,那最新的数据都是只更新到内存中的,当Activity退出后才把最新的帐户信息数据更新到数据库中,没必要更改一次就同时把数据更新一次到数据库
o SD卡的文件读写操作比数据库要快,数据库也是属于文件,但是在写数据的时候还要经过sqlite的一些列数据库操作,但是SD卡在写数据的时候是直接写到文件中的,所以针对与简单数据,标志变量或者数据条数很少的数据,而且安全性要求也不高的数据可以直接存在文件中,例如SharedPreferences中,只有关系型的数据,数据安全性较高的,数据记录条数比较多的可以选择数据库存储,而且如果数据库进行加密后,对于数据库的读写操作会更慢了
o 针对数据库操作尽量不要直接使用android里面的Sqlite来手写读写的那些sql语句,可以选择一些orm框架库,例如ormlite,GreenDao等等,我们选择的是ormlite,因为ormlite数据库加密已经有现成的库可以提供使用。
以上!感谢!分析不够全面的部分欢迎交流补充!
参考:
http://blog.csdn.net/itfootball/article/details/44084159
http://blog.csdn.net/itfootball/article/details/49004699
http://news.mydrivers.com/1/282/282933.htm
https://github.com/google/battery-historian
http://hukai.me/android-performance-battery/
https://juejin.im/entry/589d66c686b599006b2e07a8
http://xusx1024.com/2018/01/08/battery-historian-2/
http://gityuan.com/2016/01/10/power_rank/
http://hukai.me/android-performance-patterns/
http://duanqz.github.io/2015-07-21-batterystats-part1
http://duanqz.github.io/2015-07-19-Intro-to-dumpsys
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 canvas 动画库 CreateJs 之 EaselJS(上篇)