App架构师实践指南四之性能优化一
1、性能维度
常见用来衡量App性能的维度如图9-1所示。其中,性能指标包括电池(电量/温度)、流量(上行流量/下行流量等)、CPU(平均/最大/最小)、内存(平均/最大/最小)、帧率(平均/最高/最低/页面切换)和crash率等。
2、App优化总结描述
---倾听用户意见。了解并听取用户的意见是成功最好的工具之一,用户的意见既包括发布前的意见,又包括发布后的意见。
---衡量、分析用户行为并作出响应。衡量用户行为是发现并解决问题的最佳方式之一,我们可以通过统计分析用户行为来定期衡量与用户相关指标(如下载源、留存率、应用内行为等),从而对流失点、低评分、卸载率高等问题进行处理。
---提高稳定性并消除错误。可以借助Monkey等工具对APP稳定性进行测试,来消除可能存在的错误。
---改善UI响应能力。UI的卡顿会直接导致用户的流失,这是我们需要关注和优化的。
---提升可用性。可用是APP功能最基本的要求,我们可以通过线上演练和用户反馈等方式进行测试验证。
---专业外观和美术设计。UI设计是APP必不可少的一环,设计师的存在是保证我们界面优雅美观的基础。
---合适的功能。功能并不是越多越好,适时做减法,抓住属于我们APP的核心功能才是最重要的。
---与系统和第三方应用集成。Home界面的小部件(如天气类应用等)、丰富的通知、全局搜索等第三方应用集成可以进一步提高用户满意度,可以使用户享受应用和设备之间的无缝使用体验,值得考虑和关注。
3、性能测试平台
如果对自家数据不敏感,可以尝试采用第三方性能测试平台进行性能测试,主流性能测试平台有百度MTC,腾讯GT、bita以及Bugly,阿里云效,科大讯飞iTest,网易Emmagee,华为DevEco、Testin等。
4、硬件性能优化
硬件性能指由硬件或软件引起的导致电池消耗的性能,具体包括屏幕、传感器、CPU、WakeLOCK、JobScheduler等耗电性能。
4.1电量信息获取
---通过手机系统文件。直接通过手机系统文件“/sys/class/power_supply/battery/uevent”来获取手机电量相关信息(包括手机的电流、电压、电量和温度信息),这是一种简单暴力的方式,虽然存在一定的适配问题,但有时候也是最有效的一种方式。
---CPU分析。对于CPU过高使用导致的耗电,最简单直观的方式是通过top命令实时查看各个线程的CPU占用情况,如果某个线程持续占用超过10%就要重点关注了。(top命令需要借助ADB Shell,如果无法直接使用top命令,可以通过ANR的traces.txt文件进行分析,文件中线程里的schedstat表示线程消耗CPU的情况)。
---Batterystats工具和Battery Historian脚本。
------概述。基于Batterystats工具,通过adb命令dump出电量使用的统计信息,再通过Battery Historian脚本分析呈现dump出的统计信息文件。
------使用条件
----------Android 5.0+(API 21+)
----------Phython/Go语言环境。Battery Historian是Google开源的历史数据获取工具,基于Go语言开发,基于Phython环境运行脚本historian.py或者基于battery-historian.go来分析。
---使用
----------下载安装Battery Historian并配置好环境。
----------adb重连手机设备(通过adb kill-server和adb devices)。
----------reset电池收集信息,(adb shell dumpsys battery stats - reset)
----------断开手机设备连接,操作我们待测的App。
----------重连手机设备,dump出电量使用统计信息,存储到batterystats.txt(adb shell dumpsys battery stats > batterystats.txt)
----------将数据转换成可查看到html形式(python historian.py batterystats.txt > batterystats.html)
----------Battery Historain新版本中建议通过bugreport方式导出数据,这样可以看到更多信息。这种方式需要使用Docker或者配置Go语言环境,然后运行Battery Historian,再导入bugreport文件呈现电量使用信息。其信息非常丰富,如图9-2所示。
(1)adb bugreport bugreport.zip(Android 7.0+)
(2)adb bugreport > bugreport.txt(Android 5.0~6.0)
说明:上面描述的是图形化展示方式,如果仅仅只需要获取电量信息,可以直接使用命令adb shell dumpsys batterystats,打印出来log中即有电量信息,Android 5.0中信息比较粗糙,Android 6.0+中有更细化的耗电量信息。
-------耗电量统计API.
Android系统耗电量统计API一直存在,只不过都是隐藏的。Android系统中的设置-->电池功能调用的就是这个API.
------ GSam battery Monitor。检测手机电池电量消耗去向,以折线图进行统计展示。手机需要root,应用需要获取root权限。
5、耗电分析
耗电量计算
---Android中手机自带的设置中有电量统计,其本质是通过Android Framwork层中专门负责电量统计服务的BatteryStatsService来实现的,其在ActivityManagerService中创建,代码如下:
mBateryStatsService = new BatteryStatsService(new File(systemDir,'batterystats.bin').toString)
其他的模块比如WakeLock等向BatteryStatsService喂数据,数据存放在系统的batterystas.bin文件中,再交于BatteryStatsImpl来进行电量数据的分析,然后可以通过processAppUsage和processMiscUsage方法计算具体耗电量,系统的设置据说这样得到电量统计信息的。
如何来衡量一款App是否耗电,其实并没有统一标准,我们进行电量测试也仅仅是对移动设备电量消耗快慢的一种直观感应。一般用平均电流来衡量电量的消耗速度,但具体多大的平均电流值可以被认为是耗电呢?可以参考腾讯Bugly团队的一种定义方法,如图9-1所示。
手机中的耗电大户/主要耗电场景
---手机屏幕。毋庸置疑,手机中最耗电的模块肯定是屏幕里。亮屏时间越长,电量消耗越快。
---CPU相关。复杂运算逻辑、无限循环等会直接导致CPU负载过高,耗电剧增。
---网络相关。一般情况下,网络相关(网络请求、数据传输、网络切换等)是仅次于屏幕等耗电大户。
---WakeLock(Android)。WakeLock是Android系统中用于优化电量使用情况等一种手段,通过在用户一段时间没有操作的情况下让屏幕和CPU进入休眠状态来减少电量消耗。一些应用中出于特定业务场景调用PowerManager.WakeLock来使CPU保持持续运转,而释放需要时间,甚至呢根本就忘记释放了,灭屏后CPU却还在一直运转着,从而大大增加了耗电量。
---GPS。GPS定位涉及GPS位置传感器,也是耗电大户。平时不使用时应该关闭。
---Camera。Camera涉及前后摄像头硬件,如果一直使用(录屏等),耗电也会非常可观。
6、电量优化
电量优化最佳实践
6.1 网络相关
6.1.1发起网络请求时机。业务区分当前网络请求时需要及时返回结果的(用户主动下拉刷新等),还是可以延迟执行的(异步上传数据等),可以延迟执行的有针对性地把网络请求行为绑定在一起发出。
6.1.2减少移动网络被激活的时间和次数。
---采用回退机制来避免固定频繁的同步请求,例如,在发现返回数据相同的情况下,推迟下次的请求时机。
---使用Batching(批处理)的方式来集中发出请求,避免频繁的间隔请求,例如同一业务尽量少使用多次请求,合并多次请求。
---使用Prefetching(预取)的技术提前把一些数据拿到,避免后面频繁再次发起网络请求。
6.1.3数据处理
---网络数据传输前进行压缩处理
---进行大数据量下载时,尽量使用GZIP方式下载。
---使用高效率的数据格式和解析方法,推荐使用JSON和Protobuf。
6.1.4慎用或禁用Polling(轮询)的方式去执行网络请求,Android可以采用Google Cloud Messafeing,ios可以采用APNs。
6.1.5减少推送消息次数和频率。App收到服务端大量或频繁的推送消息,对手机的耗电量一定会有影响。
6.1.6网络状态。处理具体业务前,养成判断当前网络状态的习惯和编程思维。例如,在移动网络下,减少数据传输或降低数据传输频率(Wi-Fi下网络传输耗电量远比移动网络少);在网络不可用状态下,尽早进入网络异常处理逻辑,避免不必要的运算逻辑等。
6.2 界面相关
---离开某个界面后停止对应的耗电活动。例如,用户离开了A界面,而对应的耗电活动并没有及时停止,就会造成资源浪费。
---应用进入后台禁止异常消耗电量。
6.3 定位相关
---使用GPS后记得及时关闭,减少更新频率,根据实际情况切换GPS和网络,不要任何时候都同时使用两者。
---对定位要求不高的业务场景,尽量用网络定位代替GPS.
---慎用持续定位,对于大多数场景,使用一次定位接口即可。
---慎用被动定位,防止被动定位唤醒。
6.4 电池状态
---在处理一个耗时耗电的任务时,如果该任务不是很紧急(例如下载我们应用的更新包),建议事先判断一下电池电量是否足够,如果当前电池电量紧张,可以延迟到一定时间再执行该任务。
---监听充电状态变化(监听设备连接或断开电源状态)来处理特定的业务,以提升用户体验,例如应用更新包策略,以及Log日志上传、用户数据同步等。
6.5 消息广播。程序中避免频繁地监听系统广播或业务消息造成严重耗电问题,灵活控制消息广播接受的有效与无效状态。
6.6 H5页面。关注并测试H5页面的耗电量。
6.7 Android专栏
6.7.1 慎用WakeLock
---使用WakeLock时一定记得成双成对,及时释放。特别是PARTIAL_WAKE_LOCK(PowerManager.new WakeLock()的第一个参数)类型,一定要及时释放。忘记释放或者延迟释放都会导致CPU保持运行,而使得设备处于高功耗状态。
---使用WakeLock时,建议通过代参数的aquire设置超时,以防止APP异常等不可抗拒因素导致没有释放。
---建议通过try-catch-finally的方式确保wakelock被及时释放
try{
wakeLock.setReferenceCounted(false);
wakeLock.acquire(60*1000);
}catch(SomeException e){
//do Exception
}finally{
if(wakeLock.isHeld()){
wakeLock.release();
}
}
---不建议使用的场景。如播放器播放时需要保持屏幕常亮,可以使用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON或者android:keepScreenOn="true"来代替Wakelock;再如后台服务端数据请求,没必要通过WakeLock来保持屏幕让用户感知等。
6.7.2 定时任务选择。
Android中可以通过Handler/AlarmManager以及JobSchedule(Android5.0+) 3种方式执行定时任务,前台任务建议使用Handler/Timer,简单直观;后台任务,对调度时机没有强烈要求等场景,建议使用JobSchedule来管理任务(Android 5.0+),对于触发事件准确性要求非常高的场景,如果没法通过算法降级处理,再考虑AlarmManager,对于WAKEUP类型且Exact调度模式的AlarmManager任务一定要慎用。
6.7.3 Doze和App Standby。Doze和App Standby是Android 6.0中提供的两个用来节省电量的技术。
---Doze俗称瞌睡,当设备闲置了一段较长时间,Doze技术将通过延迟后台网络活动、CPU运行等来减少电量损耗。
---App Standby,应用待机,可以识别当前App最近是否得到过用户使用,如果没有被使用,APP Standby将延缓这个应用等后台网络活动。
6.7.4 Google官方优化电池寿命建议。
---监控电池电量和充电状态。根据相应的状态来调整应用的更新频率,比如在充电中就可以无虑更新应用对电池的消耗,而如果设备在消耗电池电量,则降低更新频率。
---判断并监测设备的底座状态和类型。通过判断和监听当前底座类型及种类来改变应用程序行为。
---确定和监控网络连接状态。如果设备没有连接互联网,则没有必要唤醒设备类进行更新操作,连接移动互联网比连接Wi-Fi使用更低的更新频率等。
---按需操作BroadcastReceiver。可以在运行时切换自己在Mainfest中声明的BroadcastReceiver,以便根据当前设备状态禁用不需要开启的BroadcastReceiver,从而节省耗电,提高应用效率。