Doze模式是Android6.0上新出的一种模式,是一种全新的、低能耗的状态,在后台只有部分任务允许运行,其他都被强制停止。当用户一段时间内没有使用手机时,Doze模式通过延缓APP后台的CPU和网络活动来减少电量的消耗。
如果用户断开了充电连接,灭屏不动手机一段时间后会进入Doze模式。在Doze模式中系统尝试去通过减少应用的网络访问和CPU敏感的服务来保护电池。它也阻止应用访问网络、延迟应用的任务、同步和alarm。
系统定期退出Doze模式(maintenance window)去让app完成它们被延迟的动作,在maintenance window期间,系统运行所有挂起的任务、同步、alarm以及访问网络。如下图所示:
Doze模式的限制:
1.网络访问被暂停
2.忽略wakelock
3.alarm被延迟到下一个maintenance window执行
4.如果需要在Doze状态下启动设置的alarm,可以使用setAndAllowWhileIdle()或者setExactAndAllowWhileIdle()
5.当有setAlarmClock()的alarm启动时,系统会短暂退出Doze模式
6.系统不会扫描WiFi热点
7.系统不允许sync adapter运行
8.系统不允许JobScheduler运行
Doze模式在系统中主要是DeviceIdleController类来驱动,下面来分析下它:
DeviceIdleController的启动和初始化和之前分析的AlarmManagerService、JobSchedulerService等一样也是在SystemServer的startOtherServices()方法中启动的:
private void startOtherServices() {
. . .
mSystemServiceManager.startService(DeviceIdleController.class);
. . .
}
SystemServiceManager类的startService方法之前已经分析过,这里贴下代码:
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
/**
* 创建并启动一个继承自SystemService类的系统服务。
*
* @param 一个继承自SystemService类的服务类
* @return 服务类的实例
* @throws 如果服务启动失败则抛RuntimeException异常
*/
@SuppressWarnings("unchecked")
public <T extends SystemService> T startService(Class<T> serviceClass) {
// 获取服务类的类名
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
// 判断服务类是否是SystemService的子类
if (!SystemService.class.isAssignableFrom(serviceClass)) {
throw new RuntimeException("Failed to create " + name
+ ": service must extend " + SystemService.class.getName());
}
final T service;
try {
// 获取服务类包含一个Context参数的构造方法
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
// 创建这个服务类的实例
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service could not be instantiated", ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (NoSuchMethodException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service must have a public constructor with a Context argument", ex);
} catch (InvocationTargetException ex) {
throw new RuntimeException("Failed to create service " + name
+ ": service constructor threw an exception", ex);
}
// 把服务添加到mServices列表中,方便后续使用时取出
mServices.add(service);
try {
// 回调服务的onStart方法
service.onStart();
} catch (RuntimeException ex) {
throw new RuntimeException("Failed to start service " + name
+ ": onStart threw an exception", ex);
}
return service;
}
DeviceIdleController类的构造方法:
public final AtomicFile mConfigFile;
final MyHandler mHandler;
public DeviceIdleController(Context context) {
super(context);
// 创建原子操作文件deviceidle.xml
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
// 创建处理消息的handler,BackgroundThread是HandlerThread的子类
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
// 获取data/system目录
private static File getSystemDir() {
return new File(Environment.getDataDirectory(), "system");
}
mHandler处理消息的代码后面再分析,先看onStart()方法:
/**
* 省电模式下能在后台运行的白名单,idle模式除外
*/
private final ArrayMap<String, Integer> mPowerSaveWhitelistAppsExceptIdle = new ArrayMap<>();
/**
* 所有省电模式下的白名单
*/
private final ArrayMap<String, Integer> mPowerSaveWhitelistApps = new ArrayMap<>();
/**
* APPID是否在idle模式外的白名单中
*/
private final SparseBooleanArray mPowerSaveWhitelistSystemAppIdsExceptIdle
= new SparseBooleanArray();
/**
* APPID是否在全模式的白名单中
*/
private final SparseBooleanArray mPowerSaveWhitelistSystemAppIds = new SparseBooleanArray();
public void onStart() {
final PackageManager pm = getContext().getPackageManager();
synchronized (this) {
// 获取Doze模式是否开启的默认值,该参数在frameworks/base/core/res/res/values/config.xml文件中设置,默认值为false
mEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
/// M: Config Doze and App Standby { MTK做的修改,可以直接修改这里的默认值来打开或关闭Doze模式
if (SystemProperties.get(CONFIG_AUTO_POWER, "0").equals("-1")) {
mEnabled = false;
} else if (SystemProperties.get(CONFIG_AUTO_POWER, "0").equals("1")) {
mEnabled = true;
}
/// Config Doze and App Standby }
// 从SystemConfig中获取默认系统应用白名单,该类之前在分析apk的安装过程时分析过,
// 主要是解析system/etc/sysconfig、system/etc/permissions目录下的各xml文件
SystemConfig sysConfig = SystemConfig.getInstance();
// 从系统配置文件中读取省电模式下能在后台运行的白名单(除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, 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) {
}
}
// 从系统配置文件中读取省电模式下能在后台运行的白名单,包括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, 0);
if ((ai.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
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) {
}
}
// 初始化常量类,后面有详细分析
mConstants = new Constants(mHandler, getContext().getContentResolver());
// 读取deviceidle.xml文件,解析该文件并将用户应用的白名单保存在map中
readConfigFileLocked();
// 将系统应用白名单和用户应用白名单合并
updateWhitelistAppIdsLocked();
// 初始化变量:屏幕为开启、充电状态为true、Doze模式为ACTIVE状态等
mScreenOn = true;
mCharging = true;
mState = STATE_ACTIVE;
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
// 发布服务到ServiceManager和LocalService中
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, new BinderService());
// LocalService后续用到时再分析
publishLocalService(LocalService.class, new LocalService());
}
onStart()方法中的初始化常量类操作:
/**
* 该类中所有的时间单位都是ms,这些常量跟settings数据库中的值保持一致。所有访问该常量类的类或方法都
* 必须持有DeviceIdleController对象锁
*/
private final class Constants extends ContentObserver {
// Key names stored in the settings value.
private static final String KEY_INACTIVE_TIMEOUT = "inactive_to";
private static final String KEY_SENSING_TIMEOUT = "sensing_to";
private static final String KEY_LOCATING_TIMEOUT = "locating_to";
private static final String KEY_LOCATION_ACCURACY = "location_accuracy";
private static final String KEY_MOTION_INACTIVE_TIMEOUT = "motion_inactive_to";
private static final String KEY_IDLE_AFTER_INACTIVE_TIMEOUT = "idle_after_inactive_to";
private static final String KEY_IDLE_PENDING_TIMEOUT = "idle_pending_to";
private static final String KEY_MAX_IDLE_PENDING_TIMEOUT = "max_idle_pending_to";
private static final String KEY_IDLE_PENDING_FACTOR = "idle_pending_factor";
private static final String KEY_IDLE_TIMEOUT = "idle_to";
private static final String KEY_MAX_IDLE_TIMEOUT = "max_idle_to";
private static final String KEY_IDLE_FACTOR = "idle_factor";
private static final String KEY_MIN_TIME_TO_ALARM = "min_time_to_alarm";
private static final String KEY_MAX_TEMP_APP_WHITELIST_DURATION =
"max_temp_app_whitelist_duration";
private static final String KEY_MMS_TEMP_APP_WHITELIST_DURATION =
"mms_temp_app_whitelist_duration";
private static final String KEY_SMS_TEMP_APP_WHITELIST_DURATION =
"sms_temp_app_whitelist_duration";
/**
* 切换到inactive状态后,INACTIVE_TIMEOUT时间后开始检测运动传感器以确定设备是否是静止状态。
* 因为在屏幕关闭时切换到inactive状态后我们不想连续运行重要的运动传感器
*/
public long INACTIVE_TIMEOUT;
/**
* Sensing状态的超时时间
*/
public long SENSING_TIMEOUT;
/**
* 进入空闲模式之前等待尝试修复位置信息的时间
*/
public long LOCATING_TIMEOUT;
/**
* 满足位置信息最大精度(单位:米)的要求以确保能够进入Idle模式。
* 会尝试修复位置信息或直到LOCATING_TIMEOUT时间到期
*/
public float LOCATION_ACCURACY;
/**
* 操作手机后,从切换到inactive状态后,下次检测操作手机需要等待的时间
*/
public long MOTION_INACTIVE_TIMEOUT;
/**
* 在inactive超时之后,等待监测重大操作的时间,以便让手机进入Idle状态
*/
public long IDLE_AFTER_INACTIVE_TIMEOUT;
/**
* 切换到Idle状态时的初始化时间,此时允许回到IDLE_PENDING(空闲延迟)状态允许系统正常运行直到回到Idle状态
*/
public long IDLE_PENDING_TIMEOUT;
/**
*进入到Idle模式后,系统能正常运行的最大时间段
*/
public long MAX_IDLE_PENDING_TIMEOUT;
/**
* 空闲延迟缩放因子
*/
public float IDLE_PENDING_FACTOR;
/**
* 在再次唤醒手机以便回到待机空闲并允许正常工作运行之前,想要处于空闲状态的时间
* 即:空闲持续时间
*/
public long IDLE_TIMEOUT;
/**
* 最大空闲持续时间
*/
public long MAX_IDLE_TIMEOUT;
/**
* 空闲持续缩放因子
*/
public float IDLE_FACTOR;
/**
* 进入空闲模式后,到下一次执行alarm的最小时间
*/
public long MIN_TIME_TO_ALARM;
/**
* 当一个应用接收到高优先级事件时,将其加入到临时白名单的最长时间
*/
public long MAX_TEMP_APP_WHITELIST_DURATION;
/**
* 将正在接收彩信的应用加入临时白名单的时间
*/
public long MMS_TEMP_APP_WHITELIST_DURATION;
/**
* 将正在接收短信的应用加入临时白名单的时间
*/
public long SMS_TEMP_APP_WHITELIST_DURATION;
private final ContentResolver mRe