安卓性能优化-电量优化建议

现在安卓设备,很多都会有电量使用监控软件,可以查看设备上应用的耗电量情况。虽然有很多手机号称超长待机,但是他们也只是在手机静默状态下的测试数据,应用启动后的一些操作几乎都会不同程度的有一个电量消耗小高峰。比如唤醒屏幕、频繁的网络请求、复杂的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;
    }

在这里,我们可以获取到电池的很多信息,基于此,我们就可以控制一些应用的逻辑了。举几个简单的例子:

  1. 我们必须意识到,长时间复杂的cpu运算和io操作,会增加cpu和磁盘的负担,提高功耗,加快电量流失。因此在设备处于充电状态时,我们可以进行一些数据备份、文件扫描、大文件下载等比较耗时的操作
  2. 当设备处于电量比较低的状态时,我们应该尽可能的减少电量的消耗。因此,在判断设备电量比较低并且不是充电状态时,我们应该避免一些非必须操作的执行,比如:数据备份、应用更新等

屏幕常亮策略

设备屏幕在从睡眠到唤醒这个过程中,系统需要短时间内点亮屏幕上所有的像素点,这个过程会对电池产生比较大的压力,造成瞬间的耗电量提升,当屏幕点亮以后,耗电量会趋于平稳。也就是说,在需要屏幕保持唤醒状态时(游戏、支付、视频),我们应该尽量减少唤醒的操作,可以适当保持屏幕常亮。
保持屏幕常亮的方式有好几种:

  1. 可以通过设置widow的flag来添加当前窗口保持屏幕常亮的属性
  2. 可以通过在布局文件中添加keeepScreenOn的属性
  3. 也可以直接在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。这里提供以下使用的方式步骤:

  1. 自定义服务,继承ItentService
  2. 自定义广播接收器,继承WakefulBroadcastReceiver
  3. 发送广播,在接收到广播时,调用WakefulBroadcastReceiver.startWakefulService()开启ItentService并获取唤醒锁
  4. 任务自行完毕后,需要调用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的构建等。这些留给读者自行研究。

定位优化

在安卓设备获取地理位置信息时,有多种方式,主要包括:

  1. gps定位:利用gps进行卫星定位,精确度高,耗电量大,室内定位误差大
  2. 网络定位:利用基站和局域网节点地址来大致定位,耗电量小于gps
  3. 被动式定位:读取其他应用程序的缓存数据,没有明显的耗电节点

基于以上描述,我们可以给出电量优化的建议:

  • 如果对位置精度要求不高,尽量选择网络定位
  • 如果通过被动式可以获取到定位信息,尽量复用
  • 多个模块之间的定位信息进行全局的共享,避免进行多次重复定位

传感器优化

传感器在安卓设备中直接跟硬件有关,对电池的消耗非常大,因此,对于传感器的处理,要格外谨慎。
首先是采样频率,采样频率越高,耗电越快;其次是监控时间。因此,我们可以进行一些处理:

  • 选择合适的采样频率,在满足业务要求的前提下,采样频率尽量低
  • 尽量少的注册传感器,如果可以通过一个传感器数据解决问题,就不需要注册两个传感器
  • 在满足业务要求前提下,尽量短的采集时长,及时的解注册监听器

其他

其实电量优化只是应用性能优化的一部分,有些优化工作可以提升应用的各个指标,比如:内存优化、代码执行效率、网络优化、渲染等,这些技巧基本都可以单独写一篇文章来讨论了。应用性能优化是一件很精细的工作,需要开发者从应用的全局去分析和指定策略。

发布了46 篇原创文章 · 获赞 21 · 访问量 7069

猜你喜欢

转载自blog.csdn.net/lotty_wh/article/details/105179394