电量消耗的计算与统计是一件麻烦而且矛盾的事情,记录电量消耗本身也是一个费电量的事情(所以很多设备都把这个监测电量的功能河蟹掉了)。唯一可行的方案是使用第三方监测电量的设备,这样才能够获取到真实的电量消耗。
设备的各项活动在不同状态下,消耗的电量是不同的。当设备处于待机状态时消耗的电量是极少的,打开飞行模式,可以待机长达十多天乃至一个月。可是点亮屏幕,硬件各个模块就需要开始工作(比如使用蜂窝式无线数据交换(3G4G)、屏幕保持唤醒状态等),这会需要消耗很多电量。
唤醒屏幕(当用户点亮屏幕的时候,意味着系统的各组件要开始进行工作,界面也需要开始执行渲染。)
CPU唤醒使用(当工作完成后,设备会主动进行休眠,这非常重要,在不使用或者很少使用的情况下,长时间保持屏幕唤醒会迅速消耗电池的电量)
蜂窝式无线(当设备通过无线网发送数据的时候,为了使用硬件,这里会出现一个唤醒耗电高峰。接下来发送数据包以及接受数据包也会消耗大量电量)
Battery-Historian 电量分析工具的使用
简单总结汇总一下耗电的相关因素
1、监听手机充电状态
BatteryManager会发送一个包含充电状态的持续广播, 我们可以通过此广播获取充电状态和电量详情:
// 注意: 因为这是一个持续广播, 我们无需写receiver, 可以直接通过intent获取相关数据.
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
例如, 如果设备正在充电:
// 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上传等。比如:360 手机助手,当充上电的时候,才会自动清理手机垃圾,自动备份上传图片、联系人 等到云端,从而避免当用户手机低电量时,任然继续进行耗电操作。
2、屏幕唤醒
当Android设备空闲时,屏幕会变暗,然后关闭屏幕,最后会停止CPU的运行,这样可以防止电池电量掉的快。但有些时候我们需要改变Android系统默认的这种状态:比如玩游戏时我们需要保持屏幕常亮,比如一些下载操作不需要屏幕常亮但需要CPU一直运行直到任务完成。
保持屏幕常亮最好的方式是在Activity中使用FLAG_KEEP_SCREEN_ON的Flag。
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
这个方法的好处是不像唤醒锁(wake locks),需要一些特定的权限(permission)。并且能正确管理不同app之间的切换,不用担心无用资源的释放问题。
另一个方式是在布局文件中使用android:keepScreenOn属性:
...
两者作用一样。使用代码的好处是你允许你在需要的地方关闭屏幕。
注意:一般不需要人为的去掉FLAG_KEEP_SCREEN_ON的flag,windowManager会管理好程序进入后台回到前台的的操作。如果确实需要手动清掉常亮的flag,使用getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
3、WakeLock
wake_lock锁主要是相对系统的休眠而言的,程序给CPU加了这个锁那系统就不会休眠了,目的是为了全力配合我们程序的运行。
需要使用PowerManager这个系统服务的唤醒锁(wake locks)特征来保持CPU处于唤醒状态。唤醒锁允许程序控制宿主设备的电量状态。创建和持有唤醒锁对电池的续航有较大的影响,所以,除非是真的需要唤醒锁完成尽可能短的时间在后台完成的任务时才使用它。比如在Acitivity中就没必要用了。
只有一种合理的使用场景,是在使用后台服务在屏幕关闭情况下hold住CPU完成一些工作。 要使用唤醒锁,如果不使用唤醒锁来执行后台服务,不能保证因CPU休眠未来的某个时刻任务会停止,这不是我们想要的。
有的人认为写的后台服务一直运行得挺好的,1.可能是你的任务时间比较短;2.可能CPU被手机里面很多其他的软件一直在唤醒状态。
唤醒锁可划分为并识别四种用户唤醒锁:
标记值 | CPU | 屏幕 | 键盘 |
---|---|---|---|
PARTIAL_WAKE_LOCK | 开启 | 关闭 | 关闭 |
SCREEN_DIM_WAKE_LOCK | 开启 | 变暗 | 关闭 |
SCREEN_BRIGHT_WAKE_LOCK | 开启 | 变亮 | 关闭 |
FULL_WAKE_LOCK | 开启 | 变亮 | 变亮 |
请注意,自 API 等级 17 开始,FULL_WAKE_LOCK 将被弃用。 应用应使用 FLAG_KEEP_SCREEN_ON。
第一步就是添加唤醒锁权限:
直接使用唤醒锁:
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
WakeLock wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"MyWakelockTag");
wakeLock.acquire();
在使用该类的时候,必须保证acquire和release是成对出现的。不然当我们业务已经不需要时,当CPU处于唤醒状态,这个时候就会损耗多余的电量。
4、JobScheduler
自 Android 5.0 发布以来,JobScheduler已成为执行后台工作的很好的方式,其工作方式有利于用户在适当的时机执行正确的事情。应用可以在安排作业的同时允许系统基于内存、电源和连接情况进行优化。JobScheduler的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:
5、GPS
选择合适的 Location Provider
Android 系统支持多个 Location Provider:
如果App只是需要一个粗略的定位那么就不需要使用 GPS 进行定位,既耗费电量,定位的耗时也久。
及时注销定位监听
在获取到定位之后或者程序处于后台时,注销定位监听,此时监听GPS传感器相当于执行no-op(无操作指令),用户不会有感知但是却耗电。
多模块使用定位尽量复
多个模块使用定位,尽量复用上一次的结果,而不是都重新走定位的过程,节省电量损耗;例 如:在应用启动的时候获取一次定位,保存结果,之后再用到定位的地方都直接去取。
6、传感器
使用传感器,选择合适的采样率,越高的采样率类型则越费电。
在后台时注意及时注销传感器监听
7、Doze and App Standby
最后提这一点,理论上不是电量优化,而是做电量优化要注意的一个坑。Doze and App Standby 是 Android 6.0 以后,提供了两种省电延长电池寿命的功能。
具体可参考 google 官方介绍文档。
参考资料:https://github.com/google/battery-historian#wakelock-analysis