Android App 优化之持久电量

2,132 阅读6分钟

引言

电量使用优化, 基本上是我们最不怎么关注的一项优化. 可能很多公司连QA/Tester也不会关注测试App电量的使用. 一般来说开发和测试的测试设备也一直是连着USB处于充电状态的, 感官上也体会不到电量的损耗.

然而, 对于用户来说, 实际上App的电量损耗也是用户体验的一个方面. 特别是当今人们对移动设备的依赖度越来越高, 电量也是用户特别关注的.

今天我们就来聊聊Android App的电量优化.

1, 分析电量的使用情况

老套路, 上来还是先介绍下我们使用什么工具来做电量分析.

1.1 Batterystats & bugreport

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方式导出数据分析, 可以看到更多信息.

1.2 Battery Historian

Google提供了一个开源的电池历史数据分析工具 -- Battery Historian.

1.2.1 安装

按照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 ]

程序运行在http://localhost:9999, 如下:


battery historian running web

1.2.2 界面

导入我们在第一步通过adb bugreport生成的bugreport.txt文件:


battery historian

2, 主要的耗电因素


battery usage

从手机的电池详情统计可以简单看出, 手机中最耗电的模块肯定是屏幕了, 接着就是网络相关, 另外可能的耗电大户还有GPS芯片, Camera等.

对于一个App, 对应因素主要有:

2.1 网络请求

我们可能会有发现:

  • 测试用的手机充满电放了一个十一假期还有电, 是因为测试手机没有上SIM卡.
  • 飞行模式下的手机灭屏下, 可能可以放一个月都还有电.

这是因为:

  • 手机的通过内置的射频模块和基站几乎, 从而链接上网的, 而这个射频模块(radio)是非常耗电的.
  • 为了控制这个射频模块的耗电, 硬件驱动及Android RIL层做了很多处理. 例如可以单独关闭radio(飞行模式), 间歇性假休眠radio(有数据发生时才上电, 保持一个频率的与基站交互)等等.

现如今App都是移动互联网App, 不可避免的会有大量的网络请求, 会导致radio一直处于活跃状态, 从而耗电量增加.

2.2 WakeLock

Android系统本身为了优化电量的使用, 会在没有操作时进入休眠状态, 来节省电量. 当然, 为了便于开发(很多应用不可避免的希望在灭屏后还能运行一些事儿, 或是要保持屏幕一直亮着--比如播放视频), Android提供了一个PowerManager.WakeLock的东西.

我们可以用WakeLock来保持CPU运行, 或是防止屏幕变暗/关闭, 让手机可以在用户不操作时依然可以做一些事儿. 然而, 获取WakeLock很容易, 释放不好就会成为难题, 消耗电量.

例如我们获取了一个WakeLock来保持CPU运转, 做一个复杂运算并将数据上传到后台服务器, 然后释放该WakeLock. 然而这个过程可能并不像我们想象的那么快, 可能因为比如服务器挂掉, 计算出了异常等等WakeLock没有释放. 问题就来了, CPU会一直得不到休眠, 而大大增加耗电.

另外, WakeLock还有android:keepScreenOn属性, 还可以让屏幕常量, 这可是耗电大户.

2.3 GPS

应用中经常会用到定位服务, Android提供了Network定位和GPS定位. 相对来说, GPS会精确得多, 对于一些诸如跑步, 导航类的应用基本会使用GPS定位. 然而, GPS定位也会消耗大量的电量.

3, 尽可能减少App的电量消耗的建议

了解了上述的主要的耗电因素, 还有一些程序的耗电问题, 我们通过Battery Historian也可以分析.

针对这些耗电情况, 给出如下优化建议:

3.1 优化网络请求

这个会在网络优化那篇中细聊, 在此略过.

3.2 谨慎使用WakeLock

  1. WakeLock获取释放成对出现.
  2. 使用超时WakeLock, 以防出异常导致没有释放.
// Acquires the wake lock with a timeout.
acquire(long timeout)

3.3 监听手机充电状态

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来监听:


  
    
    
  

监听电池状态, 可以让我们将一些操作放在充电或是电量足够的情况下进行, 以提升用户体验. 例如用户数据同步, Log上传等.

3.4 Doze and App Standby

Android 6.0提供了两个用来节省电量的技术Doze和App Standby.

  • Doze
    瞌睡. 如果设备闲置了一段较长时间, Doze技术将通过延迟后台网络活动, CPU运行等来减少电量损耗.

  • App Standy
    应用待机. 不是最近得到过用户"宠幸"的App, App Standy将延缓这个应用的后台网络活动.

因为所有Android 6.0及以上的设备上, Doze and App Standby都会运行. 可能会影响你的App的运行, 具体的适配请参考官方文档.

3.5 关于定位

// Remove the listener you previously added
locationManager.removeUpdates(locationListener);
  • 减少更新频率
  • 根据实际情况选择GPS或网络或两者. 只使用一个会降低电量损耗.

转载请注明出处, 欢迎大家分享到朋友圈, 微博~