电量使用优化, 基本上是我们最不怎么关注的一项优化. 可能很多公司连QA/Tester也不会关注测试App电量的使用. 一般来说开发和测试的测试设备也一直是连着USB处于充电状态的, 感官上也体会不到电量的损耗.
然而, 对于用户来说, 实际上App的电量损耗也是用户体验的一个方面. 特别是当今人们对移动设备的依赖度越来越高, 电量也是用户特别关注的.
今天我们就来聊聊Android App的电量优化.
老套路, 上来还是先介绍下我们使用什么工具来做电量分析.
Android 5.0及以上的设备, 允许我们通过adb命令dump出电量使用统计信息.
1, 因为电量统计数据是持续的, 会非常大, 统计我们的待测试App之前先reset下, 连上设备, 命令行执行:
$ adb shell dumpsys batterystats --reset
Battery stats reset.
2, 断开测试设备, 操作我们的待测试App.
3, 重新连接设备, 使用adb命令导出相关统计数据:
// 此命令持续记录输出, 想要停止记录时按Ctrl+C退出.
$ adb bugreport > bugreport.txt
导出的统计数据存储到bugreport.txt, 此时我们可以借助如下工具来图形化展示电池的消耗情况.
注意, 官方SDK文档导出文件方式为:
adb shell dumpsys batterystats > batterystats.txt
使用python historian.py batterystats.txt > batterystats.html查看数据
是battery-historian老版本的使用方式. 目前Battery Historian已更新2.0版本, 推荐使用bugreport方式导出数据分析, 可以看到更多信息.
Google提供了一个开源的电池历史数据分析工具 – Battery Historian.
按照Battery Historian在github上的readme, 一步步安装即可.
需要注意的是, Battery Historian是Go语言的, 安装Go的时候需要配置其bin的环境变量.
Python环境需要是2.7的(3.x不行), 建议使用pyenv管理本地的python环境.
另外, 因为Battery Historian是一个网页版工具, 涉及一些JS引用, 有时需要.
安装完成后, 执行:
cd $GOPATH/src/github.com/google/battery-historian
go run cmd/battery-historian/battery-historian.go [--port <default:9999>]
程序运行在http://localhost:9999, 如下:
导入我们在第一步通过adb bugreport生成的bugreport.txt文件:
从手机的电池详情统计可以简单看出, 手机中最耗电的模块肯定是屏幕了, 接着就是网络相关, 另外可能的耗电大户还有GPS芯片, Camera等.
对于一个App, 对应因素主要有:
我们可能会有发现:
这是因为:
现如今App都是移动互联网App, 不可避免的会有大量的网络请求, 会导致radio一直处于活跃状态, 从而耗电量增加.
Android系统本身为了优化电量的使用, 会在没有操作时进入休眠状态, 来节省电量. 当然, 为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿, 或是要保持屏幕一直亮着–比如播放视频), Android提供了一个PowerManager.WakeLock的东西.
我们可以用WakeLock来保持CPU运行, 或是防止屏幕变暗/关闭, 让手机可以在用户不操作时依然可以做一些事儿. 然而, 获取WakeLock很容易, 释放不好就会成为难题, 消耗电量.
例如我们获取了一个WakeLock来保持CPU运转, 做一个复杂运算并将数据上传到后台服务器, 然后释放该WakeLock. 然而这个过程可能并不像我们想象的那么快, 可能因为比如服务器挂掉, 计算出了异常等等WakeLock没有释放. 问题就来了, CPU会一直得不到休眠, 而大大增加耗电.
另外, WakeLock还有android:keepScreenOn属性, 还可以让屏幕常量, 这可是耗电大户.
应用中经常会用到定位服务, Android提供了Network定位和GPS定位. 相对来说, GPS会精确得多, 对于一些诸如跑步, 导航类的应用基本会使用GPS定位. 然而, GPS定位也会消耗大量的电量.
了解了上述的主要的耗电因素, 还有一些程序的耗电问题, 我们通过Battery Historian也可以分析.
针对这些耗电情况, 给出如下优化建议:
这个会在网络优化那篇中细聊, 在此略过.
// Acquires the wake lock with a timeout.
acquire(long timeout)
BatteryManager会发送一个包含充电状态的持续广播, 我们可以通过此广播获取充电状态和电量详情:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
注意: 因为这是一个持续广播, 我们无需写receiver, 可以直接通过intent获取相关数据.
例如, 如果设备正在充电:
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
status == BatteryManager.BATTERY_STATUS_FULL;
// How are we charging?
int chargePlug = battery.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BATTERY_PLUGGED_AC;
另外我们也可以监听充电状态的变化, 只要设备连接或断开电源, BatteryManager就会广播相应的操作, 我们可以注册receiver来监听:
".PowerConnectionReceiver">
"android.intent.action.ACTION_POWER_CONNECTED"/>
"android.intent.action.ACTION_POWER_DISCONNECTED"/>
监听电池状态, 可以让我们将一些操作放在充电或是电量足够的情况下进行, 以提升用户体验. 例如用户数据同步, Log上传等.
Android 6.0提供了两个用来节省电量的技术Doze和App Standby.
Doze
瞌睡. 如果设备闲置了一段较长时间, Doze技术将通过延迟后台网络活动, CPU运行等来减少电量损耗.
App Standy
应用待机. 不是最近得到过用户”宠幸”的App, App Standy将延缓这个应用的后台网络活动.
因为所有Android 6.0及以上的设备上, Doze and App Standby都会运行. 可能会影响你的App的运行, 具体的适配请参考官方文档.
// Remove the listener you previously added
locationManager.removeUpdates(locationListener);