参考地址: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模式。在这种模式下,系统会限制app的网络和CPU的服务来保存电量。系统也会组织app访问网络,延迟它要做的任务、同步工作以及标准闹钟。
系统会定期的退出Doze模式一小会儿,让app完成他们的延迟活动。在这个窗口期(Maintenance Window),系统运行所有的同步工作、任务以及闹钟,允许app访问网络。
在每个Maintenance Window结束后,系统会再次进入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在用户没使用它时的空闲状态。当用户不触摸app一段时间且下面条件都没有时系统来做这个决定:
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去要求用户允许加入白名单。
注意:Google Play政策禁止app直接免除Android 6.0+(Doze和App Standby模式)电量管理特性的,除非app的核心功能受到不利影响。
为确保好的用户体验,上线app前需进行Doze和App Standby模式的完整测试:
1、配置一个Android6.0+的硬件设备或虚拟机
2、连接设备到开发机,安装app
3、运行app并保持活跃
4、关闭设备屏幕(app时活动的)
5、强制使系统循环进入Doze模式:
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
第二行命令可能不止执行一次,直至设备状态到空闲。
6、观察你的app重新激活后的行为,确保app在退出Doze模式后优雅的激活。
1、配置一个Android6.0+的硬件设备或虚拟机
2、连接设备到开发机,安装app
3、运行app并保持活跃
4、强制使系统循环进入App Standby模式:
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive true
5、模拟唤醒app:
$ adb shell am set-inactive false
$ adb shell am get-inactive
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);
你可以提取当前的充电状态,如果设备在充电,还可以知道是通过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;
通常,在交流电(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>
在广播中提取充电状态:
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;
}
}
int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
float batteryPct = level / (float)scale;
你不能很荣易地持续监控电池状态,而且你也不需要。
一般来说,持续监控电量对电池的影响大于app本身行为对电量的影响,所以最好的实践是仅仅监控电量显著的变化-如设备进入或离开低电量时。
下面这段manifest片段,当设备电量很低或离开低电量时广播被触发,它监听ACTION_BATTERY_LOW和ACTION_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>
一般在电池电量非常低时最好禁用所有你的后台更新操作,因为不管你的数据多新,在你使用它之前手机就关闭屏幕了。
在很多情况下,给设备充电和将它放到dock模式是一致的。
原文地址: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状态在sticky广播的ACTION_DOCK_EVENT的action中。因为是sticky,不需要注册一个广播:
IntentFilter ifilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
Intent dockStatus = context.registerReceiver(null, ifilter);
可以从EXTRA_DOCK_STATE 中提取当前的dock状态:
int dockState = battery.getIntExtra(EXTRA_DOCK_STATE, -1);
boolean isDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
有四种Dock类型:
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;
设备不管何时dock或undock,都会触发ACTION_DOCK_EVENT的广播。需要监听这个广播只需要在manifest中注册,然后加对应的action即可:
<action android:name="android.intent.action.ACTION_DOCK_EVENT"/>
原文地址: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();
boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;
移动网络数据的开销比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"/>
设备的网络连接状态可能会变化的很频繁,当每次设备网络从移动蜂窝数据切换到WI-FI时就会被触发。因此,最好的实践就是仅当你要将之前暂停的更新或下载恢复时才监听这个广播。这个一般就够了,在进行一个更新之前,先检查是否有网络连接;然后没有网络连接,挂起更新操作直到网络恢复。
原文地址:http://developer.android.com/training/monitoring-device-state/manifest-receivers.html#ToggleReceivers
监控设备状态变化最简单的方法是在Manifest创建一个BroadcastReceiver。这个方式有一个影响就是app每次都需要唤醒设备来触发这些广播,可能会很频繁。
一个更好的方式是在运行时禁用和使用BroadcastReceiver,这样只在需要的时候被动接受系统触发的广播。
我们可以使用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)
使用这个技术,如果你确定网络连接已经丢失,你可以禁用除连接状态改变receiver之外的所有的receiver。相反,一旦网络连接上,你可以停止监听连接状态的receiver,然后执行更新操作前简单检查下是否连上网络。