Android 6.0 Doze模式分析
Doze模式是Android6.0上新出的一种模式,是一种全新的、低能耗的状态,在后台只有部分任务允许运行,其他都被强制停止。当用户一段时间没有使用手机的时候,Doze模式通过延缓app后台的CPU和网络活动减少电量的消耗。PowerManagerService中也有Doze模式,和此处的Doze模式不一样,其实此处叫Device Idle模式更容易区分
如果一个用户断开了充电连接,关屏不动手机一段时间之后,设备进入Doze模式。在Doze模式中,系统尝试去通过减少应用的网络访问和CPU敏感的服务来保护电池。它也阻止应用通过访问网络,并且延缓应用的任务、同步和标准alarms。
系统定期退出Doze模式(maintenance window)去让app完成他们被延缓的动作。在maintenance window期间,系统运行所有挂起的同步、任务和alarms,同时也能访问网络
Doze模式的限制。
1. 网络接入被暂停
2. 系统忽略wake locks
3. 标准的AlarmManager alarms被延缓到下一个maintenance window
4. 如果你需要在Doze状态下启动设置的alarms,使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()。
5. 当有setAlarmClock()的alarms启动时,系统会短暂退出Doze模式
6. 系统不会扫描Wi-Fi
7. 系统不允许sync adapters运行
8. 系统不允许JobScheduler运行
Doze模式在系统中主要有DeviceIdleController来驱动。下面我们来分析下DeviceIdleController
DeviceIdleController 的启动和初始化
DeviceIdleController和PowerManagerService一样都继承自SystemService类,同样是在SystemServer服务中启动。
mSystemServiceManager.startService(DeviceIdleController.class);
同样,在SystemServiceManager中的startService方法中利用反射的方法构造DeviceIdleController对象,然后调用DeviceIdleController的onStart方法来初始化。
DeviceIdleController的构造方法
public DeviceIdleController(Context context) {
super(context);
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
构造方法很简单,只有两步
1. 创建一个deviceidle.xml文件,该文件位于data/system/目录下。
2. 创建了一个Handler用来处理消息
onStart方法
public void onStart() {
……
synchronized (this) {
//第1步,获取Doze模式是否默认开启
mEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
//第2步,从systemConfig中读取默认的系统应用的白名单
SystemConfig sysConfig = SystemConfig.getInstance();
ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();
for (int i=0; i<allowPowerExceptIdle.size(); i++) {
String pkg = allowPowerExceptIdle.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
}
} catch (PackageManager.NameNotFoundException e) {
}
}
//第3步
ArraySet<String> allowPower = sysConfig.getAllowInPowerSave();
for (int i=0; i<allowPower.size(); i++) {
String pkg = allowPower.valueAt(i);
try {
ApplicationInfo ai = pm.getApplicationInfo(pkg, 0);
if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
int appid = UserHandle.getAppId(ai.uid);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
mPowerSaveWhitelistApps.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIds.put(appid, true);
}
} catch (PackageManager.NameNotFoundException e) {
}
}
mConstants = new Constants(mHandler, getContext().getContentResolver());
//第4步
readConfigFileLocked();
//第5步
updateWhitelistAppIdsLocked();
//第6步
mScreenOn = true;
mCharging = true;
mState = STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
//第7步
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, new BinderService());
publishLocalService(LocalService.class, new LocalService());
}
这个方法中大致可以分为6部分
第1步:从配置文件中获取Doze模式的开关值,默认为false
第2步:从SystemConfig中读取Doze模式系统应用的白名单,这个白名单是已经在系统配置文件中配置好的,位于手机目录system/ect/sysconfig中。
收集了配置的除了Idle模式都可以运行的白名单
第3步:从SystemConfig中读取Doze模式的白名单
第2步和第3步主要用于读取Doze模式下系统应用的白名单。
第4步:读取deviceidle.xml文件,解析xml文件并将用户应用的白名单读入内存
第5步:设置Doze模式的白名单,通过updateWhitelistAppIdsLocked()方法将系统应用白名单和用户应用的白名单合并,然后将白名单设置到PowerManagerService中
mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
第6步:初始化一些变量,默认系统的屏幕为开启,Doze模式默认为ACTIV