Android 'M' Doze

什么是Doze?

我第一次看到”doze”被用在Android上,其实是它作为一个display state在搭载了KitKat(Android 4.4, API 20)的穿戴设备上,之后我在搭载了Lollipop(Android 5.0)的设备上又看到了它。Doze是当设备暂时呈现出静态(不交互的)内容(想象一下Nexus 6,响应重要手势时出现的时钟,实际上并没有更新)时所处的一种全新的、低耗能的状态。在新的Android‘M’预览中,Doze Mode的含义发生了轻微的变化,现在是指“强制空闲”状态,这时只有很少的后台处理被允许。

评论:“Doze”对于这个新特性而言其实是一个糟糕的名字。因为这个名词在已经在framework层中存在了,是由DreamManager掌管,而与Doze Mode没有任何关系。

你好,DeviceIdleController

DeviceIdleController是Doze模式的主要驱动。接下来,我将使用device idle mode而不是doze mode来描述“Doze”,因为它更符合代码的实际情况。如果你已经阅读了官方文档,你可能已经注意到下面的命令,开发者可以通过这些命令得知当下设备的应用行为:

$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step

 

对于上面的命令你可能并不熟悉,dumpsys是用来与系统服务交互的(查看它们的状态),deviceidle是我们之前没有看到过的,它是一个新的系统服务。

$ adb shell service list | grep deviceidle
59  deviceidle: [android.os.IDeviceIdleController]

这个新的服务常驻以监听下面的系统事件,这些事件会触发系统是否进入idle mode:

 

  1. 亮屏/暗屏
  2. 充电状态
  3. 重要的手势检测

DeviceIdleController维持着设备包含的五种状态:

  1. ACTIVE – 设备在使用中,或者连接着电源。
  2. INACTIVE – 设备已经从ACTIVE状态中出来一段时间了(使用者关闭了屏幕或者拔掉了电源)
  3. IDLE_PENDING – 请留意,我们将进入idle mode.
  4. IDLE – 设备进入idle mode.
  5. IDLE_MAINTENANCE – 应用窗口已经打开去做处理.

当设备被唤醒和正在使用中,控制器就处于ACTIVE状态,外部的事件(不活跃时间超时,用户关闭屏幕,等等)将会使设备状态进入到INACTIVE. 那时,DeviceIdleController将会通过AlarmManager来设置他自己的alarm来驱动进程:

  1. 一个alarm会被设置在一个预设的时刻(这个时间在M的预览中是30分钟)。
  2. 当这个alarm生效后,DeviceIdleController 会进入到IDLE_PENDING然后再次设置同样的alarm。
  3. 当触发下一个alarm后,控制器会进入到IDLE 状态,进入到这个状态后,应用特性会被完全限制。
  4. 再向前推进这个服务会在IDLE 和IDLE_MAINTENANCE两个状态之间周期性的跳转,后者在服务完全被禁前,等待的应用事件被触发。

正如上面提到的,开发者能够使用下面的命令,手动地改变设备的这些状态:

$ adb shell dumpsys deviceidle step

 

我们可以使用‘-h’看到所有的deviceidle的所有选项:

$ adb shell dumpsys deviceidle -h
Device idle controller (deviceidle) dump options:
  [-h] [CMD]
  -h: print this help text.
Commands:
  step
    Immediately step to next state, without waiting for alarm.
  disable
    Completely disable device idle mode.
  enable
    Re-enable device idle mode after it had previously been disabled.
  whitelist
    Add (prefix with +) or remove (prefix with -) packages.

这些服务的公共API(由IDeviceIdleController 接口展现)持有全部方法访问白名单。应用(系统应用或其它第三方应用)任何情况下都不能驱动控制器状态。

 

你在这个名单中吗?

正如你在上面的帮助菜单中看到的一样,DeviceIdleController 维护着一个应用白名单,不需要额外的参数,通过dump服务的状态,我们能够看到现在的这个列表:

$ adb shell dumpsys deviceidle
  Whitelist system apps:
    com.android.providers.downloads
    com.android.vending
    com.google.android.gms
  Whitelist app uids:
    UID=10012: true
    UID=10016: true
    UID=10026: true
  …

这个名单分为两个部分:系统应用和第三方应用。

 

系统应用

系统应用会被平台制作者通过配置定义列在白名单中。下面这个是从Nexus 6中得到的一个配置定义例子,它将GMS核心(在GCM中使用),应用商店,以及一个任意的用于电源监控的app白名单化:  /system/etc/sysconfig/google.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- These are configurations that must exist on all GMS devices. -->
<config>
    <allow-in-power-save package="com.google.android.gms" />

    <allow-in-power-save package="com.android.vending" />

    <allow-in-power-save package="com.google.android.volta" />
</config>

 

其它的系统服务可以通过SystemConfig的实例访问到这些值。DeviceIdleController使用SystemConfig.getAllowInPowerSave()将这些系统定义的元素放到白名单中。

注意:当设备处于“省电模式”时,同样也是这个配置文件决定哪个系统应用可以在后台开启服务。

第三方应用

白名单中剩下的部分是用户定义的,这些项可以通过两种方式被增加和删除。  第一种方式,开发者可以通过dumpsys接口使用白名单命令:

$ adb shell dumpsys deviceidle whitelist +com.example.myapplication
$ adb shell dumpsys deviceidle
  Whitelist system apps:
    com.android.providers.downloads
    com.android.vending
    com.google.android.gms
  Whitelist user apps:
    com.example.myapplication
  Whitelist app uids:
    UID=10012: true

 

第二种方式,用户可以通过设置(Settings -> Battery -> Ignore optimizations)来修改白名单。  BatteryIgnoreMenu BatteryIgnoreMenu  这个用户设置存在是因为白名单也被Android‘M’新的App Standby 特征使用。白名单只是当评估设备的空闲行为时被部分使用。

设备处于Idle Mode时的系统服务行为

既然我们已经知道了一些基础了,让我们来测试一下当设备空闲时剩下的系统服务的反应,并且看下白名单是否有实际的用处。下面是DeviceIdleController所能涉及到的主要服务的列表:

NetworkPolicyManagerService

Lollipop引入了“省电模式”,它只允许白名单应用才能在后台运行。Idle mode有着相同的逻辑,只对白名单中的应用授予访问网络的权限。这个服务对于“省电模式”和idle mode是等同的。

JobSchedulerService

所有应用(没有例外)中,现在正在执行的作业会被取消。Idle mode的时候没有等待中的作业被开启。

SyncManager

所有应用(没有例外)中的活跃的同步会被取消。

PowerManagerService

应用白名单意味着唤醒锁(wake locks)是有效的。没有在这个名单中的应用及时禁用的唤醒锁(wake locks)

AlarmManagerService

DeviceIdleController是使用一个特殊的私有方法(AlarmManager.setIdleUntil())来注册下一个唤醒alarm。当AlarmManagerService 看到它时,所有的标准应用alarm都强制进入到一个等待状态直到直到下一个DeviceIdleControlleralarm触发。以这种方式,应用alarm被批处理并且在IDLE_MAINTENANCE时期被控制器驱动。有三个标志允许alarm退出。

  • FLAG_ALLOW_WHILE_IDLE – 任何应用可以使用new setAndAllowWhileIdle() 对其进行设置。
  • FLAG_WAKE_FROM_IDLE – 任何应用可以使用setAlarmClock()来进行设置。
  • FLAG_IDLE_UNTIL – 为被DeviceIdleController使用的alarm保留,以改变机器的状态。  UID<10000的进程(例如系统服务)他们设置的每个alarm会自动被授予FLAG_ALLOW_WHILE_IDLE。

最后总结

许多开发者询问,文档中提到应用在空闲时期,如果接收到更高优先级的使用GCM推送的消息,应用会被授予“短暂的网络访问”。在我看来,在framework层的实现中没有任何与GCM的关联(谢天谢地,如果是那样就太愚蠢了,不是吗?)因为GMS是闭源的,我们只能推测在谷歌设备上会不会发生。

事实是这样,任何系统应用能够暂时修改app白名单或者特殊应用的网络策略。这会在不修改放下设备的空闲状态的情况下,为每个应用基础的网络访问打破限制。在当前的预览中,是否仍然存在漏洞于SyncManager,这点仍然不清楚。现存的同步被取消,但是我们不能确定新的会不会被允许。应用也许仍然能够在GCM的触发下执行新的同步(这是在这种状态下仅有的app允许的网络访问)  好吧,当然,这纯属推测。

这篇博文是Android M Development Tips 的部分。

posted on 2016-10-27 15:29  jamboo  阅读(351)  评论(0编辑  收藏  举报

导航