Android M doze特性预研

Android M doze特性预研

2015年5月29日GoogleI/O大会发布新一代Android系统 - Android M preview 版本(API-"MNC")。该版本在电量续航能力方面针对整个系统和单个应用分别增加了特性doze和App standby。除范围不同外两者差别不大,这里仅讨论doze(休眠模式),代码分析主要基于Release 1版本,同时注明Release 2版本改动之处。相关文档可见https://developer.android.com/preview/behavior-changes.html#behavior-power

doze概念

在Android4.4的Wear系统(API 20)第一次引入doze概念,当时用在android.view.Display的state成员取值中,并在android5.0推广到大部分Android设备,当其时旨在描述在屏幕开启状态只临时显示静态(无交互)内容的低功耗状态。在Android M中,doze模式的含义略有修改,其含义为只允许少量后台进程活动的“IDEL”状态,这可以看做是android为了解决其饱受诟病的续航能力问题而进一步“伪后台”化,即在某种状态中限制大部分app的运行。

进入doze的条件

  1. 屏幕关闭
  2. 没有插USB(充电中)
  3. 手机处于静止状态一段时间

doze模式下应用受限功能

  1. 网络访问被禁用,但是一些高优先级的GCM推送消息将会被放行
  2. Wake locks被忽略
  3. Alarms被屏蔽,除非调用setAlarmClock()和AlarmManager.setAndAllowWhileIdle()
  4. WiFi热点扫描停止
  5. 同步和JobScheduler调度任务被挂起

白名单

类似的权限管理通常都会有白名单,doze也不例外,名单中的应用不受上述doze限制,例如系统自带的下载服务,Google Play及GMS服务都默认加入白名单。用户可以通过系统设置->应用->高级->忽略优化界面添加或移除白名单,如下图所示。

调试

使用adb命令可以手动将手机切入doze模式,即IDLE状态进行调试。

带USB调试的时候要先将充电模式禁止掉,使用battery服务的unplug命令。

顾名思义,dumpsys是用来查看系统service,而doze相关的系统服务是"deviceidle",如下命令即可查看。

新增加的“deviceidle”服务是通过IDeviceIdleController接口实现的,后面将会对其进行源码分析。 进入doze需要满足三方面的条件,这些条件控制着DeviceIdleController内部的状态机实现,分为5个状态:

  1. ACTIVE -手机亮屏使用或者充电中
  2. INACTIVE - 刚脱离ACTIVE状态(如手机关闭屏幕)
  3. IDEL_PENDING - 准备进入IDLE状态
  4. IDLE - 进入IDLE状态
  5. IDLEMANINTENANCE - IDLE状态保持一段时间后,短暂唤醒做一些事情

[注]Release 2在IDEL_PENDING和IDLE状态间增加了SENSING表示在进入IDLE侦测运动情况的状态。

我们可使用下面命令dump出手机当前的IDLE状态信息,包括白名单列表。

在禁用充电模式关闭屏幕后,手机会进入INACTIVE状态,此时通过step命令来手工控制状态切换。

也可以通过whitelist命令增加或删除白名单应用。

源码剖析

下面基于Android M Preview Release 1 版本对doze相关代码进行分析。系统在com/android/server路径下新增了一个继承自SystemService的DeviceIdleController服务类负责doze的控制逻辑。其内部字符串常量SERVICE_NAME明确定义了其服务的名称为“deviceidle”。

[注]Release 2该定义由Context.java新增常量DEVICE_IDLE_CONTROLLER取代。

状态机

系统定义了5个int常量表示5个状态。

在事件响应和状态切换方面,依靠其内部BroadcastReceiver类成员和两个listener实现驱动。

DeviceIdleController通过响应内外部事件完成状态驱动。实现逻辑如下:

内部定义的状态切换事件ACTION_STEP_IDLE_STATE由AlarmManager类成员根据预设时间触发,mReceiver接收到事件后调用stepIdleStateLocked()完成状态切换。

非ACTIVE状态之间的转换都是通过预设时间不同的alarm触发ACTION_STEP_IDLE_STATE事件完成跳转:

(a)如果没有外界干扰,进入IDLE态之后,即doze模式,系统就会在IDLE和IDLE_MAINTENAEC之前切换,后者会允许应用程序做一些事情(时间在5~10min)

(b)接收到某些外部事件则有可能转换为ACTIVE状态,包括充电/亮屏/运动等,以mReceiver处理的USB充电事件为例,相应的处理方法updateChargingLocked()实现如下:

USB插入充电会将手机马上唤醒,切换到ACTIVE状态并且停止运动检测;如果是拔出则视屏幕关闭等条件决定是否将其切换到INACTIVE状态,若发生切换则同时设定一个alarm(默认30min)看是否需要进一步发送ACTION_STEP_IDLE_STATE事件触发后续的状态切换。

外部调用

先插一句,遗憾的是当前DeviceIdleController没有提供任何公开API给上层应用使用。先来看看系统服务是如何与其交互的。

状态切换方面

DeviceIdleController没有提供接口访问私有成员mState,而是通过其内部的Handler成员把IDLE开关两个状态传给系统服务PowerManager,NetworkPolicyManager,BatteryStats等。

接口方面

系统提供了接口IDeviceIdleController,DeviceIdleController内部类BinderSevice实现该接口,在启动时以“deviceidle”的名字将后者实例注册到系统中。

系统其他服务程序则通过AIDL方式调用访问,如NetworkPolicyManagerService新增加如下接口成员。

IDeviceIdleController一共定义了7个方法,用于增加/删除/判断白名单,以及调试用到的dump命令。

开发者很容易想到使用上面系统服务一样的方式利用白名单,很遗憾,最关键的add/remove接口需要DEVICE_POWER系统权限,如何获得该权限这里不详述,总之就是也要把自己变成系统级应用,和系统共享数据,这个代价比较大。不过在判断自身是否在白名单这一问题上通过hack接口isPowerSaveWhitelistApp()的方式还是还是可行。

[注]Release 2中已经将判断应用是否在白名单这一功能接口在PowerManager.java中公开,接口实现如下:

既然说到白名单,顺便看一下其保存位置,系统路径“/data/system/deviceidle.xml”,所以没有root权限就不用多想直接对其修改。

至此难道真的不能再取到更多的信息了吗?回头看其利用内部Handler成员将idle mode状态传给了LocalPowerManager,进而查看PowerManager代码,发现其新增如下一个事件定义和一个状态接口,两者均公开,故可以通过注册BroadcastReceiver的方式监听该事件。同步管理SyncManager正是采用这种方式获知系统进入和退出doze的时机。

网络连接

在明确如何手工进入doze和监听事件后,可以验证下doze模式下网络连接情况。在子线程中测试下面简单的连接请求,发现子线程在openConnection后一直被挂起。

换下面的网络连接检查代码:

在doze状态下isAvailable接口返回true,而isConnected返回是false,网络连接失败,查看系统日志发现这样一行输出:

也就是APP的网络连接被BLOCKED掉,翻看其对应的系统服务ConnectivityService源码找到如下方法:

[注]Release 2把上面的系统debug log关闭。

继续看isNetworkWithLinkPropertiesBlocked()。

也就是系统通过应用uid维护了一份网络连接策略规则列表,该列表通过AIDL从NetworkPolicyManagerService同步而来。

绕了一圈再来看NetworkPolicyManagerService。

上面的代码片段明确指出如果在doze模式下限制所有后台非白名单的网络访问,返回RULE_REJECT_METERED。

GCM

对于网络应用,特别是如微信等IM应用,doze模式下限制网络,消息收发功能必然受到影响,Android给出了解决方案-GCM:

微信本身已经具备注册接收GCM推送功能,在接收到GCM推送消息后,会取拉取消息内容,前一个步骤由系统GCM服务完成,GCM服务默认已在白名单中,而后面拉取的动作需要微信联网完成。

经过测试在doze模式下,即使接收到了GCM推送后,应用再发起网络连接的结果和上面的网络测试一样,仍旧是被禁止的!Google决心强推所有的消息接收都只能依靠GCM推送!?只能说持续跟进+拭目以待。

[注]使用Release 2测试结果和1一致,Google方面确认此处存在bug导致应用无法加到临时白名单中,此问题已在修复中。

主动退出doze

最后探讨下应用如何“悄悄”地使系统退出doze模式。根据doze的条件,在没有充电的情况下,只能通过亮屏或震动等外部事件触发系统退出IDLE状态。

1.亮屏

APP拥有“android.permission.WAKE_LOCK”权限,执行下面代码即可点亮屏幕,实测可以让手机马上退出doze模式。

2.震动

DeviceIdleController使用的运动传感器是Significant motion,单触发低功耗的传感器,调用deviceidle dump命令即可查看实际传感器信息。

APP拥有“android.permission.VIBRATE”权限,执行下面代码即可能触发手机震动退出doze模式。

结语

综上,doze的限制很多,但是不可否认地是其对手机的续航能力,特别是夜间待机的提升是相当显著的。普通应用如何顺应其趋势要求又能保持原有功能及体验效果,这是一个值得深入考虑的问题。

原文发布于微信公众号 - WeMobileDev(WeMobileDev)

原文发表时间:2015-08-03

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏信安之路

论二级域名收集的各种姿势

1 查询whois http://whois.chinaz.com/baidu.com

96200
来自专栏王清培的专栏

.NET应用架构设计—面向查询服务的参数化查询设计(分解业务点,单独配置各自的数据查询契约)

阅读目录: 1.背景介绍 2.对业务功能点进行逻辑划分(如:A、B、C分别三个业务点) 2.1.配置映射关系,对业务点配置查询契约(构造VS插件方便生成查询契...

24380
来自专栏JackieZheng

十分钟带你了解服务化框架

在此之前 在此之前,你需要知道中间件的概念,可能在过往的从业生涯这个名词无数次的从你的眼前、耳畔都留下了足记,但是它的样子依然很模糊。 今天要说的服务化框架其...

20780
来自专栏CSDN技术头条

缓存那些事

导语:在网络分层应用服务中,缓存的使用已比较普及,本文将结合作者实际工作经验总结,讲述在不同的场景下如何选择和使用适用的缓存框架,以达到提升服务质量,优化系统...

28070
来自专栏CSDN技术头条

和各种诡异 Bug 打交道 13 年,我总结了 18 条经验

作者 | Henrik Warne 翻译 | 郑芸 在《程序员,你会从 Bug 中学习么?》一文中,我写了我是怎样追踪这些年遇到的最有趣 bug 的。最近我重新...

28380
来自专栏华章科技

Python 开发者的 6 个必备库

无论你是正在使用 Python 进行快速开发,还是在为 Python 桌面应用制作原生 UI ,或者是在优化现有的 Python 代码,以下这些 Python ...

8320
来自专栏java一日一条

我的编码习惯 - 参数校验和国际化规范

今天我们说说参数校验和国际化,这些代码没有什么技术含量,却大量充斥在业务代码上,很可能业务代码只有几行,参数校验代码却有十几行,非常影响代码阅读,所以很有必要把...

11210
来自专栏SeanCheney的专栏

Awesome Asyncio 《碉堡的Asyncio·中文版》Awesome-Asyncio-CN

Awesome-asyncio 是 Timo Furrer 发起并维护的 Python Asyncio 资源列表。本项目是其中文版,在这里,收集了大量的 Asy...

71040
来自专栏JackieZheng

十分钟带你了解服务化框架

在此之前 在此之前,你需要知道中间件的概念,可能在过往的从业生涯这个名词无数次的从你的眼前、耳畔都留下了足记,但是它的样子依然很模糊。 今天要说的服务化框架其...

198100
来自专栏IT技术精选文摘

Java文件映射(mmap)全接触

前言 我们在平时的工作中大多都会需要处理像下面这样基于Key-Value的数据: ? 其中UID是数据唯一标识,FIELD[1]是属性值。以QQ用户的Sess...

1.8K60

扫码关注云+社区

领取腾讯云代金券