Doze和App Standby的优化(API23)

参考地址:http://developer.android.com/training/monitoring-device-state/doze-standby.html 
从Android 6.0 (API level 23)开始,Android提供了两个节电功能用来增加电池的续航时间。Doze 可以在设备长时间不使用时,通过延迟后台CPU和网络的活动来减少电池的消耗;App Standby将延迟没有交互的app网络活动。 
Doze和App Standby在Android6.0以上的版本上管理所有App的行为。为保证最好的用户体验,需要在Doze 和App Standby 模式下测试你的app并适当调整你的代码。

理解Doze

如果用户离开设备一段时间,没有插上电源,屏幕会关闭,设备会进入Doze模式。在这种模式下,系统会限制app的网络和CPU的服务来保存电量。系统也会组织app访问网络,延迟它要做的任务、同步工作以及标准闹钟。 
系统会定期的退出Doze模式一小会儿,让app完成他们的延迟活动。在这个窗口期(Maintenance Window),系统运行所有的同步工作、任务以及闹钟,允许app访问网络。 
Doze和App Standby的优化(API23)_第1张图片
在每个Maintenance Window结束后,系统会再次进入Doze模式,挂起网络操作等。随着时间的推移,这个窗口期会出现的越来越不频繁,这样帮助设备省电。

Doze的限制

  • 网络访问挂起
  • 系统忽略唤醒锁(wake locks.)
  • 标准的AlarmManager闹钟被推迟到下一次Maintenance Window运行
  • 系统不会进行WIFI扫描
  • 系统不允许sync adapters运行
  • 系统不允许JobScheduler工作

适配你的app到Doze模式

Doze特别会影响闹钟和定时器,因为在5.1(APi22)及以下版本中,闹钟在Doze模式下不会启动。 
在 Android 6.0 (API level 23) 中,提供了2个新的AlarmManager方法,setAndAllowWhileIdle()setExactAndAllowWhileIdle()。这样在Doze模式下,你也可以设置闹钟执行(每个App每次可以执行15分钟)。 
为确保你的应用在Doze模式下如何表现,你可以使用adb命令迫使系统进入和退出Doze模式,观察你的应用程序的行为。

理解App Standby

App Standby 允许系统决定app在用户没使用它时的空闲状态。当用户不触摸app一段时间且下面条件都没有时系统来做这个决定:

  • 用户显示的启动app
  • app在前台有一个任务(一个Activity或一个前台的service,或被调用的Activity或前台service)
  • app在锁屏界面或通知栏生成一个notification 
    当设备充电时,系统将app从Standby状态中释放掉,允许它自由访问网络,执行任意的任务和同步操作。如果设备长时间空闲,系统将允许空闲的app一天访问一次网络。

当app空闲时允许GCM来交互

Google Cloud Messaging (GCM),是一个推送服务。GCM通过high-priority GCM messages在Doze和App Standby模式下进行了优化。你的app可以使用它们高效的最低耗电的在系统和设备间进行交互。 
作为最佳实践,如果你需要使用即时消息,你应该使用GCM。如果你的服务器和客户端已经使用GCM,确保你的服务使用high-priority messages,因为这个可以可靠的唤醒app即使设备在Doze模式下。

对其它使用场景的支持

几乎所有的app都可以支持Doze,但也有一些用户场景还不能满足。在这些场景内,系统提供一个可配置的白名单来让某些app免除 Doze和App Standby优化。 
在白名单中的app可以在 Doze和App Standby模式下使用网络和唤醒锁。然而,白名单的app也有受限,和其它app一样。例如,白名单的app的job和同步任务被延迟了,和闹钟一样被限制了。app可以通过调用isIgnoringBatteryOptimizations()来检查自己是否在白名单中。 
用户可以在 Settings > Battery > Battery Optimization中手动配置白名单,系统也提供了方法让app去要求用户允许加入白名单。

  • 启动ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS Intent,进入Battery Optimization界面,那里可以添加白名单
  • 加了REQUEST_IGNORE_BATTERY_OPTIMIZATIONS权限的app可以直接弹出一个系统对话框让用户直接添加app到白名单中而不用进入设置界面。使用**ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS**Intent来触发这个Dialog。
  • 用户可以手动从白名单中移除app

注意:Google Play政策禁止app直接免除Android 6.0+(Doze和App Standby模式)电量管理特性的,除非app的核心功能受到不利影响。

测试Doze和App Standby模式

为确保好的用户体验,上线app前需进行Doze和App Standby模式的完整测试:

Doze测试

1、配置一个Android6.0+的硬件设备或虚拟机 
2、连接设备到开发机,安装app 
3、运行app并保持活跃 
4、关闭设备屏幕(app时活动的) 
5、强制使系统循环进入Doze模式:

$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
  • 1
  • 2

第二行命令可能不止执行一次,直至设备状态到空闲。 
6、观察你的app重新激活后的行为,确保app在退出Doze模式后优雅的激活。

测试App Standby模式

1、配置一个Android6.0+的硬件设备或虚拟机 
2、连接设备到开发机,安装app 
3、运行app并保持活跃 
4、强制使系统循环进入App Standby模式:

$ adb shell dumpsys battery unplug
$ adb shell am set-inactive  true
  • 1
  • 2

5、模拟唤醒app:

$ adb shell am set-inactive  false
$ adb shell am get-inactive 
  • 1
  • 2

6、唤醒app后观察app的行为。确保app从standby模式优雅的恢复过来。 
特别的,你需要检查你的app的通知和后台工作是否继续按预期运行。

可接受的白名单用户场景

下面列表中高亮的是可接受的白名单场景:

Type 用户场景 是否可以用GCM 白名单是否接受 备注
即时消息、聊天或调用应用程序 在Doze或App Standby模式下,需要传递即时消息 是的,可用GCM 不接受 需要high-priority messages 唤醒app和访问网络
即时消息、聊天或调用应用程序企业VOIP apps 同上 否,不能用GCM 接受  
任务自动化的app app核心的功能就是即时消息、语音呼叫、照片管理或地图定位 如果适用的话 接受  

监测电池电量和充电状态

原文地址:http://developer.android.com/training/monitoring-device-state/battery-monitoring.html 
执行应用程序更新对电量的影响取决于电池和充电状态的电量值。设备充电时执行更新的影响可以忽略不计,所以在大多数情况下当设备被连接一个充电器时你可以最大化你的刷新频率。相反,如果设备没有充电,减少你的更新频率有助于延长电池续航时间。 
同样,你可以检测电池的电量,在电量几乎用光时减少甚至停止程序的运行。

获取当前的充电状态

BatteryManager通过一个sticky的Intent广播了所有的有关电池和充电状态的信息。 
因为是sticky的Intent,所以不需要注册BroadcastReceiver,简单的传null到registerReceiver中即可,然后电池状态就返回了。这里也可以传一个实际的BroadcastReceiver对象,但这里不是必须的,后面会有讲解。

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
  • 1
  • 2

你可以提取当前的充电状态,如果设备在充电,还可以知道是通过USB或AC充电器来充电。

// 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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

通常,在交流电(AC)充电情况下,你应该马力全开的执行程序,而在USB充电时,减少频率,在不充电的 情况下降低更低的频率来执行程序更新。

监控充电状态的变化

插拔设备的数据线很容易的引起充电状态的变化,监视充电状态的变化时很重要的,它决定了程序的刷新频率。 
BatteryManager广播了一个action,告知我们设备何时连上或断开了电源。我们注册一个BroadcastReceiver,通过定义 ACTION_POWER_CONNECTED 和 ACTION_POWER_DISCONNECTED的IntentFilter来监听:

<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

在广播中提取充电状态:

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

获取当前电量

int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);

float batteryPct = level / (float)scale;
  • 1
  • 2
  • 3
  • 4

监测电量的显著变化

你不能很荣易地持续监控电池状态,而且你也不需要。 
一般来说,持续监控电量对电池的影响大于app本身行为对电量的影响,所以最好的实践是仅仅监控电量显著的变化-如设备进入或离开低电量时。 
下面这段manifest片段,当设备电量很低或离开低电量时广播被触发,它监听ACTION_BATTERY_LOWACTION_BATTERY_OKAY

<receiver android:name=".BatteryLevelReceiver">
<intent-filter>
  <action android:name="android.intent.action.ACTION_BATTERY_LOW"/>
  <action android:name="android.intent.action.ACTION_BATTERY_OKAY"/>
  intent-filter>
receiver>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

一般在电池电量非常低时最好禁用所有你的后台更新操作,因为不管你的数据多新,在你使用它之前手机就关闭屏幕了。 
在很多情况下,给设备充电和将它放到dock模式是一致的。

确定和监控Docking(底座)状态和类型

原文地址:http://developer.android.com/training/monitoring-device-state/docking-monitoring.html#CurrentDockState 
Android设备可以放到到几种不同类型的dock(底座)中。包括car dock、home dock以及Digital和Analog的dock。dock状态和充电状态紧密相连因为很多的底座为dock状态下的设备提供电量。 
手机的dock状态如何影响你的更新频率取决于你的app。你可以选择在desktop dock增加体育中心app的更新频率,或者如果设备在car dock模式下完全禁用你的更新。相反,在car dock模式下如果你的后台服务需要更新交通状况你可以选择来最大化你的更新频率。 
dock状态也是由一个sticky的intent来广播的,它允许你查询设备是否在dock模式,如果是,是哪一种dock。

确定当前的dock状态

dock状态在sticky广播的ACTION_DOCK_EVENT的action中。因为是sticky,不需要注册一个广播:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);
  • 1
  • 2

可以从EXTRA_DOCK_STATE 中提取当前的dock状态:

int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
  • 1
  • 2

确定当前Dock类型

有四种Dock类型:

  • Car:车载底座
  • Desk:桌面底座
  • Low-End (Analog) Desk:模拟底座
  • High-End (Digital) Desk:数字底座 
    后两个类型在Android in API level 11中才有。所以最好的实践就是检查是否在3种desk的类型之中,而不用关心具体是哪一种:
boolean isCar = dockState == EXTRA_DOCK_STATE_CAR;
boolean isDesk = dockState == EXTRA_DOCK_STATE_DESK ||
                 dockState == EXTRA_DOCK_STATE_LE_DESK ||
                 dockState == EXTRA_DOCK_STATE_HE_DESK;
  • 1
  • 2
  • 3
  • 4

监控Dock的状态和类型变化

设备不管何时dock或undock,都会触发ACTION_DOCK_EVENT的广播。需要监听这个广播只需要在manifest中注册,然后加对应的action即可:

<action android:name="android.intent.action.ACTION_DOCK_EVENT"/>
  • 1

确定和监控连接状态

原文地址:http://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html 
闹钟和后台服务最常见的用途是定期从服务器更新数据到App,并缓存数据,或执行长时间的下载。但是如果你没有连接到互联网,或你的下载连接太慢了,你都要唤醒设备来进行更新吗? 
使用ConnectivityManager来检测是否连上网络,如果连上可以知道连接的是哪种网络。

检测是否连上网络

如果没有连上网络,就不需要进行任何的更新操作了。下面的代码判断设备是否连上网络:

ConnectivityManager cm =
        (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
boolean isConnected = activeNetwork != null &&
                      activeNetwork.isConnectedOrConnecting();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

判断连接的网络类型

boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
  • 1

移动网络数据的开销比WI-FI要大,所以大多数情况下,你的设备需要在WI-FI情况下进行数据更新。 
没有网络时需要禁用网络更新,一旦建立网络连接,可以恢复更新操作。

监控网络连接的变化

当网络连接发生变化时ConnectivityManager广播了一个 CONNECTIVITY_ACTION (“android.net.conn.CONNECTIVITY_CHANGE”) 的action,你可以在manifest注册一个broadcast receiver来监听这个action。

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

设备的网络连接状态可能会变化的很频繁,当每次设备网络从移动蜂窝数据切换到WI-FI时就会被触发。因此,最好的实践就是仅当你要将之前暂停的更新或下载恢复时才监听这个广播。这个一般就够了,在进行一个更新之前,先检查是否有网络连接;然后没有网络连接,挂起更新操作直到网络恢复。

按需操作Broadcast Receivers

原文地址:http://developer.android.com/training/monitoring-device-state/manifest-receivers.html#ToggleReceivers 
监控设备状态变化最简单的方法是在Manifest创建一个BroadcastReceiver。这个方式有一个影响就是app每次都需要唤醒设备来触发这些广播,可能会很频繁。 
一个更好的方式是在运行时禁用和使用BroadcastReceiver,这样只在需要的时候被动接受系统触发的广播。

切换Receivers可用状态来提高效率

我们可以使用PackageManager来切换在Manifest注册的包括broadcast receiver的任意组件。

ComponentName receiver = new ComponentName(context, myReceiver.class);

PackageManager pm = context.getPackageManager();

pm.setComponentEnabledSetting(receiver,
        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
        PackageManager.DONT_KILL_APP)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用这个技术,如果你确定网络连接已经丢失,你可以禁用除连接状态改变receiver之外的所有的receiver。相反,一旦网络连接上,你可以停止监听连接状态的receiver,然后执行更新操作前简单检查下是否连上网络。

你可能感兴趣的:(android)