现在安卓设备,很多都会有电量使用监控软件,可以查看设备上应用的耗电量情况。虽然有很多手机号称超长待机,但是他们也只是在手机静默状态下的测试数据,应用启动后的一些操作几乎都会不同程度的有一个电量消耗小高峰。比如唤醒屏幕、频繁的网络请求、复杂的cpu运算、传感器的监听、后台的长时间运行等等。如果你的应用一直保持着这种亢奋状态,那么它对电量的消耗是非常可怕的。作为用户,肯定不希望刚充满的电,一会儿就告罄了。
所以,电量优化是应用开发者不得不面对的问题。下面,我从几个方面来给出电量优化的建议。
判断电池状态
在应用运行过程中,我们通过注册电池状态改变的广播,是可以判断当前设备的电池状态的,比如:是否在充电、当前电量级别等信息。。
我们可以通过以下代码来注册广播
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
context.registerReceiver(new BatteryReceiver(), intentFilter);
然后通过对接收到的数据进行处理
public static BatteryInfo collectInfo(Intent intent) {
BatteryInfo info = new BatteryInfo();
//获取当前状态,默认未知状态
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, 1);
info.setBatteryStatus(String.valueOf(status));
//获取当前电量级别
int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0);
info.setBatteryLevel(String.valueOf(level));
//温度,默认0
int temperature = intent.getIntExtra(BatteryManager.EXTRA_TEMPERATURE, 0);
info.setBatteryTemp(String.valueOf(temperature));
return info;
}
在这里,我们可以获取到电池的很多信息,基于此,我们就可以控制一些应用的逻辑了。举几个简单的例子:
- 我们必须意识到,长时间复杂的cpu运算和io操作,会增加cpu和磁盘的负担,提高功耗,加快电量流失。因此在设备处于充电状态时,我们可以进行一些数据备份、文件扫描、大文件下载等比较耗时的操作
- 当设备处于电量比较低的状态时,我们应该尽可能的减少电量的消耗。因此,在判断设备电量比较低并且不是充电状态时,我们应该避免一些非必须操作的执行,比如:数据备份、应用更新等
屏幕常亮策略
设备屏幕在从睡眠到唤醒这个过程中,系统需要短时间内点亮屏幕上所有的像素点,这个过程会对电池产生比较大的压力,造成瞬间的耗电量提升,当屏幕点亮以后,耗电量会趋于平稳。也就是说,在需要屏幕保持唤醒状态时(游戏、支付、视频),我们应该尽量减少唤醒的操作,可以适当保持屏幕常亮。
保持屏幕常亮的方式有好几种:
- 可以通过设置widow的flag来添加当前窗口保持屏幕常亮的属性
- 可以通过在布局文件中添加keeepScreenOn的属性
- 也可以直接在Manifext文件中设置Activity的属性
cpu唤醒保持策略
cpu的唤醒会从硬件层到应用层进行一次完整的交互,在这个过程中会对电池造成一定的压力,因此,我们应该尽量避免cpu的频繁唤醒。然而,安卓系统在很多地方为了节省电量、提高安全性作了很多优化。其中的设备休眠状态、打盹模式等都可能会中断正在运行中的任务,针对这种场景,我们需要进行cpu唤醒策略的优化。
前台唤醒锁
设备在休眠时,cpu也会处于休眠状态,导致很多计算任务无法执行。有时候,为了避免cpu休眠,我们要通过一些强制手段重新唤醒cpu,cpu在被唤醒的瞬间也会产生一个电量消耗的小高峰。类比屏幕唤醒策略,我们在进行某些重要的或者需要保持cpu在工作状态的时候,可以对cpu进行唤醒保持。
这些是可以通过PowerManager.WakeLock
来实现的。。一般的使用方法是:
PowerManager pw = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wakeLock =
pw.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "lotty:MainActivity");
// 获取cpu唤醒锁,最多保持事件
wakeLock.acquire(3000);
// TODO: 2020/3/29 someThing todo
// 释放cpu唤醒锁
wakeLock.release();
在PowerManager.newWakeLock
方法中的第一个参数是标记唤醒锁的类型。他们主要包括:
标记 | 意义 |
---|---|
PARTIAL_WAKE_LOCK | 只锁定cpu,屏幕和键盘保持关闭 |
SCREEN_DIM_WAKE_LOCK | cpu开启,屏幕变暗,键盘关闭 |
SCREEN_BRIGHT_WAKE_LOCK | cpu开启,屏幕点亮,键盘关闭 |
FULL_WAKE_LOCK | 均开启或者点亮 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK | cpu开启,当距离传感器开启时,屏幕关闭 |
以上是一种通用方式,在实际使用过程中,我们应该避免频繁的加锁和释放,因为这也违背了我们使用cpu唤醒锁的初衷。
后台唤醒锁服务
WakefulBroadcastReceiver
其实,安卓系统已经帮助开发者设计了很多使用工具。这里说一下获取cpu唤醒锁的广播WakefulBroadcastReceiver
。
该广播是在接收到时,会获取一个cpu唤醒锁。类的描述如下:
* This helper is for an old pattern of implementing a {@link BroadcastReceiver}
* that receives a device wakeup event and then passes the work off
* to a {@link android.app.Service}, while ensuring that the
* device does not go back to sleep during the transition.
但是因为安卓8.0的安全机制,这个类已经被标记为Deprecated
了,官方给出的原因是,系统无法保证该接收器可以接收到对应的广播。。
* @deprecated As of {@link android.os.Build.VERSION_CODES#O Android O}, background check
* restrictions make this class no longer generally useful. (It is generally not safe to
* start a service from the receipt of a broadcast, because you don't have any guarantees
* that your app is in the foreground at this point and thus allowed to do so.)
因此,这种方式建议在安卓8.0以下使用。WakefulBroadcastReceiver
一般会与服务一起使用,特别是IntentService
。这里提供以下使用的方式步骤:
- 自定义服务,继承
ItentService
- 自定义广播接收器,继承
WakefulBroadcastReceiver
- 发送广播,在接收到广播时,调用
WakefulBroadcastReceiver.startWakefulService()
开启ItentService
并获取唤醒锁 - 任务自行完毕后,需要调用
WakefulBroadcastReceiver.completeWakefulIntent()
释放唤醒锁
JobScheduler
在Android 5.0以后,JobScheduler
是执行后台工作的首选方式。JobSchedule
允许系统基于内存、电源和连接情况进行优化。它会把一些不是特别紧急的任务放到更合适的时机批量处理。这样做可以避免频繁的唤醒硬件模块,还可以避免在不合适的时间执行过多的任务消耗电量。JobSchedule
在8.0以上的实现机制有所改变,在实际使用过程中需要注意。JobScheduler
在它的类描述信息中有这么一段说明:
* While a job is running, the system holds a wakelock on behalf of your app. For this reason,
* you do not need to take any action to guarantee that the device stays awake for the
* duration of the job.
意思是,在任务执行过程中,系统会为应用程序保持一个唤醒锁。JobScheduler
需要配合JobService
一起使用。我们来看下使用方式:
首先创建自定义JobService
public class IJobService extends JobService {
private JobParameters jp ;
public IJobService() {
}
@Override public boolean onStartJob(JobParameters params) {
jp = params;
Log.e("wh", "onStartJob: " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 释放锁
jobFinished(jp,false);
return false;
}
@Override public boolean onStopJob(JobParameters params) {
return false;
}
}
在Menifest
中注册服务以及给服务赋予权限
<service
android:name="com.github.frameworkaly.job.IJobService"
android:permission="android.permission.BIND_JOB_SERVICE" />
然后利用JobScheduler
分发任务
JobScheduler js =
(JobScheduler) this.getBaseContext().getSystemService(Context.JOB_SCHEDULER_SERVICE);
JobInfo.Builder builder = new JobInfo.Builder(333, new ComponentName(this.getApplication(),
IJobService.class));
JobInfo build = builder.setMinimumLatency(1000).build();
js.schedule(build);
以上是一种通用的使用格式,在具体使用过程中,可能会涉及系统版本判断、权限定义,具体JobInfo
的构建等。这些留给读者自行研究。
定位优化
在安卓设备获取地理位置信息时,有多种方式,主要包括:
- gps定位:利用gps进行卫星定位,精确度高,耗电量大,室内定位误差大
- 网络定位:利用基站和局域网节点地址来大致定位,耗电量小于gps
- 被动式定位:读取其他应用程序的缓存数据,没有明显的耗电节点
基于以上描述,我们可以给出电量优化的建议:
- 如果对位置精度要求不高,尽量选择网络定位
- 如果通过被动式可以获取到定位信息,尽量复用
- 多个模块之间的定位信息进行全局的共享,避免进行多次重复定位
传感器优化
传感器在安卓设备中直接跟硬件有关,对电池的消耗非常大,因此,对于传感器的处理,要格外谨慎。
首先是采样频率,采样频率越高,耗电越快;其次是监控时间。因此,我们可以进行一些处理:
- 选择合适的采样频率,在满足业务要求的前提下,采样频率尽量低
- 尽量少的注册传感器,如果可以通过一个传感器数据解决问题,就不需要注册两个传感器
- 在满足业务要求前提下,尽量短的采集时长,及时的解注册监听器
其他
其实电量优化只是应用性能优化的一部分,有些优化工作可以提升应用的各个指标,比如:内存优化、代码执行效率、网络优化、渲染等,这些技巧基本都可以单独写一篇文章来讨论了。应用性能优化是一件很精细的工作,需要开发者从应用的全局去分析和指定策略。