参考:http://blog.csdn.net/gaugamela/article/details/52981984
在Android M中,Google就引入了Doze模式。它定义了一种全新的、低能耗的状态。
在该状态,后台只有部分任务被允许运行,其它任务都被强制停止。
在之前的博客中分析过Doze模式,就是device idle状态。可能有的地方分析的不是很详细,现在在android7.0上重新分析下。
一、基本原理
Doze模式可以简单概括为:
若判断用户在连续的一段时间内没有使用手机,就延缓终端中APP后台的CPU和网络活动,以达到减少电量消耗的目的。
上面这张图比较经典,基本上说明了Doze模式的含义。
图中的横轴表示时间,红色部分表示终端处于唤醒的运行状态,绿色部分就是Doze模式定义的休眠状态。
从图中的描述,我们可以看到:如果一个用户停止充电(on battery: 利用电池供电),关闭屏幕(screen off),手机处于静止状态(stationary: 位置没有发生相对移动),保持以上条件一段时间之后,终端就会进入Doze模式。一旦进入Doze模式,系统就减少(延缓)应用对网络的访问、以及对CPU的占用,来节省电池电量。
如图所示,Doze模式还定义了maintenance window。
在maintenance window中,系统允许应用完成它们被延缓的动作,即可以使用CPU资源及访问网络。
从图中我们可以看出,当进入Doze模式的条件一直满足时,Doze模式会定期的进入到maintenance window,但进入的间隔越来越长。
通过这种方式,Doze模式可以使终端处于较长时间的休眠状态。
需要注意的是:一旦Doze模式的条件不再满足,即用户充电、或打开屏幕、或终端的位置发生了移动,终端就恢复到正常模式。
因此,当用户频繁使用手机时,Doze模式几乎是没有什么实际用处的。
具体来讲,当终端处于Doze模式时,进行了以下操作:
1、暂停网络访问。
2、系统忽略所有的WakeLock。
3、标准的AlarmManager alarms被延缓到下一个maintenance window。
但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock时,alarms定义事件仍会启动。
在这些alarms启动前,系统会短暂地退出Doze模式。
4、系统不再进行WiFi扫描。
5、系统不允许sync adapters运行。
6、系统不允许JobScheduler运行。
另外我在另一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也详细介绍了Doze模式,可以参考下,上面有一些命令使用等。
二、DeviceIdleController
Android中的Doze模式主要由DeviceIdleController来控制。
public class DeviceIdleController extends SystemService
implements AnyMotionDetector.DeviceIdleCallback
可以看出DeviceIdleController继承自SystemService,是一个系统级的服务。
同时,继承了AnyMotionDetector定义的接口,便于检测到终端位置变化后进行回调。
2.1 DeviceIdleController的初始化
接下来我们看看它的初始化过程。
private void startOtherServices() {
.........
mSystemServiceManager.startService(DeviceIdleController.class);
.........
}
如上代码所示,SystemServer在startOtherServices中启动了DeviceIdleController,将先后调用DeviceIdleController的构造函数和onStart函数。
构造函数
public DeviceIdleController(Context context) {
super(context);
//deviceidle.xml用于定义idle模式也能正常工作的非系统应用
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
DeviceIdleController的构造函数比较简单,就是在创建data/system/deviceidle.xml对应的file文件,同时创建一个对应于后台线程的handler。这里的deviceidle.xml可以在设置中的电池选项那里。有电池优化,可以将一些应用放到白名单中,调用DeviceIdleController的addPowerSaveWhitelistApp方法,最后会写入deviceidle.xml文件,然后在下次开机的时候DeviceIdleController会重新读取deviceidle.xml文件然后放入白名单mPowerSaveWhitelistUserApps中。
onStart函数
public void onStart() {
final PackageManager pm = getContext().getPackageManager();
synchronized (this) {
//读取配置文件,判断Doze模式是否允许被开启
mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
//分析PKMS时提到过,PKMS扫描系统目录的xml,将形成SystemConfig
SystemConfig sysConfig = SystemConfig.getInstance();
//获取除了device Idle模式外,都可以运行的系统应用白名单
ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//获取device Idle模式下,也可以运行的系统应用白名单
ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
for (int i=0; i<allowPower.size(); i++) {
String pkg = allowPower.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg,
PackageManager.MATCH_SYSTEM_ONLY);
int appid = UserHandle.getAppId(ai.uid);
// These apps are on both the whitelist-except-idle as well
// as the full whitelist, so they apply in all cases.
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
mPowerSaveWhitelistApps.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIds.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//Constants为deviceIdleController中的内部类,继承ContentObserver
//监控数据库变化,同时得到Doze模式定义的一些时间间隔
mConstants = new Constants(mHandler, getContext().getContentResolver());
//解析deviceidle.xml,并将其中定义的package对应的app,加入到mPowerSaveWhitelistUserApps中
readConfigFileLocked();
//将白名单的内容给AlarmManagerService和PowerMangerService
//例如:DeviceIdleController判断开启Doze模式时,会通知PMS
//此时除去白名单对应的应用外,PMS会将其它所有的WakeLock设置为Disable状态
updateWhitelistAppIdsLocked();
//以下的初始化,都是假设目前处在进入Doze模式相反的条件上
mNetworkConnected = true;
mScreenOn = true;
// Start out assuming we are charging. If we aren't, we will at least get
// a battery update the next time the level drops.
mCharging = true;
//Doze模式定义终端初始时为ACTIVE状态
mState = STATE_ACTIVE;
//屏幕状态初始时为ACTIVE状态
mLightState = LIGHT_STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
//发布服务
//BinderService和LocalService均为DeviceIdleController的内部类
mBinderService = new BinderService();
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
publishLocalService(LocalService.class, new LocalService());
}
除去发布服务外,DeviceIdleController在onStart函数中,主要是读取配置文件更新自己的变量,思路比较清晰。
在这里我们仅跟进一下updateWhitelistAppIdsLocked函数:
private void updateWhitelistAppIdsLocked() {
//构造出除去idle模式外,可运行的app id数组 (可认为是系统和普通应用的集合)
//mPowerSaveWhitelistAppsExceptIdle从系统目录下的xml得到
//mPowerSaveWhitelistUserApps从deviceidle.xml得到,或调用接口加入;
//mPowerSaveWhitelistExceptIdleAppIds并未使用
mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
//构造不受Doze限制的app id数组 (可认为是系统和普通应用的集合)
//mPowerSaveWhitelistApps从系统目录下的xml得到
//mPowerSaveWhitelistAllAppIds并未使用
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
//构造不受Doze限制的app id数组(仅普通应用的集合)、
//mPowerSaveWh