Android性能优化-电量

摘要:电池电量使用问题分析。

1.简介

  应用电量损耗主要在获取网络数据、休眠唤醒CPU、CPU高负荷执行代码、LCD亮度,这里总结了电量损耗的原因以及相关解决策略,其中大部分内容参照谷歌给出的教程。

2 网络访问策略

  在没有连接到网络的情况下访问网络不仅无法得到想要的结果,反而会导致不必要的电量损耗。另外通过移动网络(2G/3G/4G)相对WIFI来讲更加耗电,制定合理的网络访问策略能很大的节省电量。

2.1 无网络下访问策略

  在无网络的情况下,访问网络进行数据读取操作是没有必要。测试中发现很多应用都会尝试去获取数据,更有些极端的情况是应用线程中一种循环尝试,导致CPU占用率一直很高。
  因此可以首先通过ConnectivityManager先进行网络判断,如果网络无连接则不进行下载处理;接下来接收action为android.net.conn.CONNECTIVITY_CHANGE广播再判断网络是否连接再次启动任务进行下载。
  
  网络判断如下:

1
2
3
ConnectivityManager cm =(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null && activeNetwork.isConnectedOrConnecting();

  广播侦听AndroidManifest中自定义的BroadcastReceiver加入:

1
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>

2.2 移动网络与WIFI下的网络请求策略

  移动网络通常需要算流量计费,这意味着需要支付更多的费用,不单如此使用移动网络的功耗要远远大于WIFI。因此在移动网络情况下,多方面考虑都有必要减少网络操作。
  当然用户可能会长时间只使用移动网络,对于这种情况下可以适当降低网络请求频率,特别是对于定时更新检测操作。当用户切换到数据网络时,有必要主动挂起下载并等待连接WIFI后再进行恢复下载。

  判断是否为WIFI网络如下:

1
2
3
ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

2.3 电量与数据更新策略

  当电池低电时,可以将更新频率设为最低或者不做更新检测;当正在充电时,可以将更新频率设为最高。
  通过BatteryManager发送粘性广播Intent.ACTION_BATTERY_CHANGED可以直接获取电池充电状态。获取充电状态如下:

1
2
3
4
5
6
7
8
9
10
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 = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

当充电状态变化时BatteryManager会发送action为android.intent.action.ACTION_POWER_CONNECTED和android.intent.action.ACTION_POWER_DISCONNECTED广播。因此注册广播侦听:

1
2
3
4
5
6
<receiver android:name=".PowerConnectionReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED"/>
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/>
</intent-filter>
</receiver>

广播中获取充电状态:

1
2
3
4
5
6
7
8
9
10
public class PowerConnectionReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;
int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;
}
}

2.4 移动网络下网络请求策略

  通常设备在不工作状态下都会进入低功耗状态,如果被激活,那么恢复到低功耗状态需要等待一段时间,例如典型的 3G 无线电网络有三种能量状态:
   - Full power:当无线连接被激活的时候,允许设备以最大的传输速率进行操作。
   - Low power:一种中间状态,对电量的消耗差不多是 Full power 状态下的50%。
   - Standby:最小的能量状态,没有被激活或者需求的网络连接。
  在低功耗和空闲的状态下,电量消耗会显著减少。这里也会介绍重要的网络请求延迟。从 low power 能量状态返回到 full power 大概需要花费1.5秒,从空闲能量状态返回到 full power 状态需要花费2秒。
  为了最小化延迟,状态机使用了一种后滞过渡到更低能量状态的机制。下图是一个典型的 3G 无线电波状态机的图示(AT&T电信的一种制式)。

典型的 3G 无线电状态机

  由于存在状态切换延迟,因此最好将数据请求放在一段时间内处理,而不是频繁唤醒导致碎片化问题。具体查看谷歌教程:https://yazone.gitbooks.io/android-training-course-in-chinese/content/connectivity/efficient-downloads/efficient-network-access.html

3. 数据缓存策略

  能用缓存尽量用缓存,使用缓存的目的不仅仅是因为数据读取速度以及流量问题,缓存还可以减少因为网络访问带来的电量损耗。

4. Alarm定时任务

  Android提供了AlarmManager用来执行定时回调,用户可以设置ELAPSED_REALTIME、RTC、ELAPSED_REALTIME_WAKEUP以及RTC_WAKEUP,其中带_WAKEUP后缀类型将使设备从休眠状态中唤醒。我们知道当CPU处于休眠状态时功耗相当的低,基本不会怎么耗电,而被唤醒后功耗将大大增加。
  当应用频繁唤醒CPU时,会因为CPU长期处于高功耗的状态下,导致系统待机时间显著减少,虽然应用可能只是做了个状态检测。
  因此对于不需要及时通知用户的行为,例如应用检测更新,应用应该尽量使用不带后缀_WAKEUP的类型来执行定时任务。而对于多个需要唤醒CPU的操作,应该尽量在同一时刻触发。
  在Android 5.0后,系统提供了JobScheduler给应用来批量处理非紧急的定时任务。基于相同原理,小米提出了系统时钟对齐的概念,通过修改系统来减少CPU被唤醒的次数,不过这样有可能会导致应用响应有一定的延时,这个需要权衡。
  (JobSheduler用法参见博客http://blog.csdn.net/cuiran/article/details/42805057)

5. 界面刷新与算法优化

  当界面刷新时,电流会产生跳变,例如动画、滑屏,功耗也将显著增加,因此没有任何交互时最好不要做动态效果,例如跑马灯。
  界面刷新率越高,算法越复杂,CPU运算量就越大,从而导致的功耗就越大。运算量越小的话流畅度相对来讲也会越高,关于“卡”的问题后面再进行讨论。

6. 应用界面亮度

  除了系统屏幕亮度外,应用界面表现出越亮,功耗也会越大,例如黑色背景比白色背景要省电得多。在阅读应用中,对黑底白字和白底黑字的阅读界面两者来讲,前者比后者省电。对于这种需要长时间使用的应用来讲,设计时是有必要考虑暗色背景。
  调节屏幕亮度100%的情况下,测试下面两个背景图中前一个功耗要比后一个功耗大80mA以上。

壁纸背景1高功耗

壁纸背景2低功耗

7. BroadcastReceiver使用策略

  当应用接收到一个广播时,可能会唤醒进程进行操作,在无网络状态下进行需要网络的操作会带来不必要的功耗问题。
  考虑以下场景:当应用收到一个广播后,启动一个线程去下载图片。这里在下载图片前可以先判断网络是否连接,可以省却了请求操作;更进一步在启动线程前加入判断,可以省却线程创建带来的资源消耗。没网络的时候,这个广播可以不必侦听,通过PackageManager的setComponentEnabledSetting可以开启关闭应用组件。如下开启广播组件:

1
2
3
4
5
ComponentName receiver = new ComponentName(context, myReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP)

  我们只需要侦听网络连接状态一个广播,网络连接打开后开启广播接收者组件,关闭网络时关闭广播接收者组件。
  同样对于其他广播,动态更改接收广播策略(动态注册或设置组件是否可用)有助于降低应用的功耗,例如应用可见时才接收广播,如电量变换、下载进度侦听广播等。

8. 电量分析工具

  Google推出的Battery Historian能够分析adb bugreport导出信息并视图化显示。如下图:

电量可视化

  Battery Historian相关内容将在接下来介绍。

9. 总结

  电池电量消耗归根到底是硬件在工作,硬件是否需要工作以及工作时间长短由软件控制,例如优化算法可以减少CPU运行时长、修改网络访问策略降低网络模组工作时长等。优化的根本就在于让软件少做事,做真正需要做的事。