Doze模式,官方翻译为低电耗模式,是Andoriod6.0增加的一项系统服务,主要目的是为了优化电池性能,增加电池续航时间,Doze模式又分两种模式:深度Doze模式(Deep Doze)和轻度Doze模式(Light Doze),如果用户长时间没有主动使用其设备,处于静止状态且屏幕已关闭,则系统会使设备进入Doze模式,也就是深度Doze模式。如果用户关闭设备屏幕但仍处于移动状态时,则设备进入轻度Doze模式,此外,轻度Doze模式只适合Android7.0及以上版本。
当用户长时间未使用设备时,设备进入Doze模式,Doze模式会延迟应用后台 CPU 和网络活动,从而延长电池续航时间。处于Doze模式的设备会定期进入维护时段,在此期间,应用可以完成待进行的活动。然后,Doze模式会使设备重新进入较长时间的休眠状态,接着进入下一个维护时段。在达到几个小时的休眠时间上限之前,平台会周而复始地重复Doze模式休眠/维护的序列,且每一次都会延长Doze模式时长。处于Doze模式的设备始终可以感知到动作,且会在检测到动作时立即退出Doze模式。整个Doze图示如下:
Deep Doze 和Light Doze模式对比如下:
操作 | 低电耗模式 | 轻度低电耗模式 |
---|---|---|
触发因素 | 屏幕关闭、电池供电、静止 | 屏幕关闭、电池供电(未插电) |
时间 | 随维护时段依次增加 | 随维护时段反复持续 N 分钟 |
限制 | 无法进行网络访问、唤醒锁忽略、 GPS/WLAN无法扫描、闹钟和SyncAdapter/JobScheduler被延迟。 | 无法进行网络访问、SyncAdapter/JobScheduler |
行为 | 仅接收优先级较高的推送通知消息。 | 接收所有实时消息(即时消息、来电等)。优先级较高的推送通知消息可以暂时访问网络。 |
退出 | 设备有移动、和用户有交互、屏幕开启、闹钟响铃 | 屏幕开启。 |
接下来就分析Doze的实现原理。Doze模式是通过DeviceIdleController来实现的。
DeviceIdleController(以下简称DIC)继承于SystemService,因此也是一个系统服务,在分析PMS的时候说过,继承于SytemService的服务启动有以下几个共同点:
Constructor()->onStart()->onBootPhase()
;下面就从SystemServer开始分析DIC的启动流程。
在SystemServer启动其他服务时启动DIC:
private void startOtherServices() {
.............
mSystemServiceManager.startService(DeviceIdleController.class);
............
}
在SystemServiceManager中,通过反射的方式获取了DIC对象,并且调用了onStart()方法:
public <T extends SystemService> T startService(Class<T> serviceClass) {
try {
....................
final String name = serviceClass.getName();
Slog.i(TAG, "Starting " + name);
final T service;
try {
//通过反射获取实例
Constructor<T> constructor = serviceClass.getConstructor(Context.class);
service = constructor.newInstance(mContext);
} catch (InstantiationException ex) {
}
//调用同名重载方法
startService(service);
return service;
}
....................
}
public void startService(@NonNull final SystemService service) {
// 加入到mServices列表中
mServices.add(service);
long time = SystemClock.elapsedRealtime();
try {
//调用onStart()开始服务
service.onStart();
} catch (RuntimeException ex) {
}
}
执行到这里,DIC的启动就开始了,再来看看最后一个生命周期方法onBootPhase()
的调用,这个方法表示启动服务的过程,在SystemServer中会调用多次,从而在不同的启动阶段完成不同的工作,代码如下:
private void startOtherServices() {
.............
mSystemServiceManager.startBootPhase(SystemService.
PHASE_LOCK_SETTINGS_READY);
.............
mSystemServiceManager.startBootPhase(SystemService.
PHASE_SYSTEM_SERVICES_READY);
.............
mSystemServiceManager.startBootPhase(
SystemService.PHASE_ACTIVITY_MANAGER_READY);
.............
mSystemServiceManager.startBootPhase(
SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
.............
}
在SystemServiceManager中的startBootPhase()
方法中,遍历已启动的服务列表,调用onBootPhase():
public void startBootPhase(final int phase) {
if (phase <= mCurrentPhase) {
throw new IllegalArgumentException("Next phase must be larger than previous");
}
mCurrentPhase = phase;
Slog.i(TAG, "Starting phase " + mCurrentPhase);
try {
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
long time = SystemClock.elapsedRealtime();
try {
//调用每个service的onBootPhase()
service.onBootPhase(mCurrentPhase);
} catch (Exception ex) {
}
}
}
}
当这个方法执行完毕后,DIC的启动就完成了,下面我们看看DIC的生命周期方法中做了什么。首先看其构造方法:
public DeviceIdleController(Context context) {
super(context);
//创建可以执行原子操作的文件,/data/system/deviceidle.xml
mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
//创建Handler,和BackgroundThread的Looper进行绑定
mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}
构造方法中,首先创建在/data/sytem下创建了deviceidle.xml文件,然后创建了一个Handler,并将BackgroundThread中Handler的Looper和该Handler进行绑定。BackgroundThread是一个单例模式实现的后台线程,可以用于任何一个进程。
再来看看DIC的onStart()方法:
@Override
public void onStart() {
final PackageManager pm = getContext().getPackageManager();
synchronized (this) {
//Light doze模式和Deep Doze是否可用,默认不可用
mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableAutoPowerModes);
//获取系统全局配置信息,如权限
SystemConfig sysConfig = SystemConfig.getInstance();
//从配置文件中读取省电模式下的白名单列表且不在在idle状态的白名单列表,即列表中的app能够在省电模式下在后台运行,
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);
//添加到Map中,表示这些应用在省电模式下可后台运行,但在Doze下后台不可运行
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
//添加到SpareArray中,表示这是处于powersave白名单但不处于doze白名单的系统应用
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//从配置文件中读取时能够在省电模式白名单列表,即包括不在doze白名单中的应用,也包括所有的白名单上的应用
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);
mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
//添加到DIC中的白名单列表中
mPowerSaveWhitelistApps.put(ai.packageName, appid);
//添加到DIC中的系统应用白名单列表中
mPowerSaveWhitelistSystemAppIds.put(appid, true);
} catch (PackageManager.NameNotFoundException e) {
}
}
//设置Contants内容观察者
mConstants = new Constants(mHandler, getContext().getContentResolver());
//读取/data/system/deviceidle.xml文件,将读取的app添加到表示用户设置的白名单中
readConfigFileLocked();
//更新白名单列表
updateWhitelistAppIdsLocked();
//网络是否连接
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;
mState = STATE_ACTIVE;//设备保持活动状态,深度Doze的初始值
mLightState = LIGHT_STATE_ACTIVE;//设备保持活动状态,轻度Doze的初始值
mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
}
mBinderService = new BinderService();
//发布远程服务
publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
//发布本地服务
publishLocalService(LocalService.class, new LocalService());
}
在onStart()方法中,首先通过SystemConfig读取了两类白名单列表:在低电量模式下后台允许运行的应用的白名单、在低电量模式和Doze模式下都允许后台运行的应用白名单;
然后读取配置文件/data/system/deviceidle.xml
,将读取的应用包名添加到mPowerSaveWhiteListUserApps这个Map中,表示用户添加到白名单中的应用。接下来更新白名单列表,然后给成员变量进行初始化。
最后,发布了远程服务和本地服务,从而可以和其他服务进行交互。发布后其他服务中就可以通过这种获取到DIC的BinderService从而进行交互:
IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
获取本地服务通过如下方式:
mLocalDeviceIdleController
= LocalServices.getService(DeviceIdleController.LocalService.class);
在onStart()方法中还调用了用来更新白名单列表的方法,来看下这个方法:
private void updateWhitelistAppIdsLocked() {
//处于省电模式白名单但不处于Idle状态白名单的app
mPowerSaveWhitelistExceptIdleAppIdArray =
buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);
//处于所有的白名单的app,包括用户添加的
mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);
//用户添加的白名单列表应用
mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);
if (mLocalActivityManager != null) {
//将适用于所有情况的白名单列表通知给AMS
mLocalActivityManager.setDeviceIdleWhitelist
(mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalPowerManager != null) {
//将适用于所有情况的白名单列表通知给PMS
mLocalPowerManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
}
if (mLocalAlarmManager != null) {
//将用户添加到白名单列表中的应用通知给AlarmManagerService
mLocalAlarmManager.setDeviceIdleUserWhitelist
(mPowerSaveWhitelistUserAppIdArray);
}
}
在这个方法中,会将三类白名单列表中的应用id添加到一个int数组中,该方法在添加应用到白名单列表、将应用移除白名单列表,都会调用该方法来更新白名单列表。
执行完onStart()方法后,就开始执行最后一个生命周期方法onBootPhase()
方法了,该方法如下:
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_SYSTEM_SERVICES_READY) {
synchronized (this) {
mAlarmManager = (AlarmManager) getContext().getSystemService
(Context.ALARM_SERVICE);
mBatteryStats = BatteryStatsService.getService();
mLocalActivityManager = getLocalService(ActivityManagerInternal.class);
mLocalPowerManager = getLocalService(PowerManagerInternal.class);
mPowerManager = getContext().getSystemService(PowerManager.class);
//申请一个wakelock锁保持CPU唤醒
mActiveIdleWakeLock = mPowerManager.newWakeLock
(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_maint");
//设置wakelock锁为非计数锁,只要执行一次release()就能释所有非计数锁
mActiveIdleWakeLock.setReferenceCounted(false);
mGoingIdleWakeLock = mPowerManager.newWakeLock
(PowerManager.PARTIAL_WAKE_LOCK,"deviceidle_going_idle");
//设置wakelock锁为计数锁,一次申请对应一次释放
mGoingIdleWakeLock.setReferenceCounted(true);
mConnectivityService = (ConnectivityService)ServiceManager.getService(
Context.CONNECTIVITY_SERVICE);
mLocalAlarmManager = getLocalService(AlarmManagerService.
LocalService.class);
mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
ServiceManager.getService
(Context.NETWORK_POLICY_SERVICE));
mSensorManager = (SensorManager)getContext().getSystemService
(Context.SENSOR_SERVICE);
//可用于自动省电模式时的传感器id,0表示没有可用传感器
int sigMotionSensorId = getContext().getResources().getInteger(
com.android.internal.R.integer.config_
autoPowerModeAnyMotionSensor);
if (sigMotionSensorId > 0) {
//根据传感器id获取传感器
mMotionSensor = mSensorManager.getDefaultSensor
(sigMotionSensorId, true);
}
//如果没有指定任一传感器&&在没有指定传感器情况下首选WristTilt
//传感器配置为true
if (mMotionSensor == null && getContext().getResources().getBoolean(
com.android.internal.R.bool.config_
autoPowerModePreferWristTilt)) {
//获取一个WristTilt传感器(手腕抖动)
mMotionSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_WRIST_TILT_GESTURE, true);
}
if (mMotionSensor == null) {
//如果以上条件都不满足,则获取一个SMD传感器
mMotionSensor = mSensorManager.getDefaultSensor(
Sensor.TYPE_SIGNIFICANT_MOTION, true);
}
//是否在进入Doze模式时预先获取位置
if (getContext().getResources().getBoolean(
com.android.internal.R.bool
.config_autoPowerModePrefetchLocation)) {
mLocationManager = (LocationManager) getContext().
getSystemService(Context.LOCATION_SERVICE);
mLocationRequest = new LocationRequest()
.setQuality(LocationRequest.ACCURACY_FINE)
.setInterval(0)
.setFastestInterval(0)
.setNumUpdates(1);
}
//自动省电模式下传感器检测的阈值度
float angleThreshold = getContext().getResources().getInteger(
com.android.internal.R.integer.config_
autoPowerModeThresholdAngle) / 100f;
//用于检测设备是否已静止
mAnyMotionDetector = new AnyMotionDetector(
(PowerManager) getContext().getSystemService
(Context.POWER_SERVICE),
mHandler, mSensorManager, this, angleThreshold);
//用于Doze状态发生改变时发送广播
mIdleIntent = new Intent(PowerManager.ACTION_DEVICE
_IDLE_MODE_CHANGED);
mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
//用于当轻度Doze状态发生改变时发送广播
mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_
DEVICE_IDLE_MODE_CHANGED);
mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
| Intent.FLAG_RECEIVER_FOREGROUND);
//注册监听电池状态改变的广播
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
//注册监听卸载应用的广播
filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addDataScheme("package");
getContext().registerReceiver(mReceiver, filter);
//注册监听网络连接改变的广播
filter = new IntentFilter();
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
getContext().registerReceiver(mReceiver, filter);
//注册监听亮灭屏的广播
filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
getContext().registerReceiver(mInteractivityReceiver, filter);
//将适用于所有情况的白名单列表通知给AMS、PMS、AlarmManagerService
mLocalActivityManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
mLocalPowerManager.setDeviceIdleWhitelist(
mPowerSaveWhitelistAllAppIdArray);
mLocalAlarmManager.setDeviceIdleUserWhitelist(
mPowerSaveWhitelistUserAppIdArray)
//更新交互状态
updateInteractivityLocked();
}
//更新网络连接状态
updateConnectivityState(null);
} else if (phase == PHASE_BOOT_COMPLETED) {
}
}
首先,只有在SystemServer中启动阶段为PHASE_SYSTEM_SERVICE_READY
时,才会进入到onBootPhase()
方法,也就是说,DIC的onBootPhase()方法在到达这一启动阶段时才执行。在onBootPhase()方法中,获取了一些系统服务管理类和用于System进程的本地服务,如AlarmManager、PowerManagerInternal等,用于和它们对应的系统服务进行交互;
其次获取一个Sensor,具体获取哪种类型的Sensor根据判断条件而定;接下来获取了一个AnyMotionDector对象,该对象用来检测设备是否处于禁止状态;然后注册了四种广播的监听,分别是:
Intent.ACTION_BATTERY_CHANGED
,由BatteryService中发送。Intent.ACTION_PACKAGE_REMOVED
,由PKMS发送;ConnectivityManager.CONNECTIVITY_ACTION
;Intent.SCREEN_ON/SCREEN_OFF
,由PMS相关的Notifier发送;最后,调用了一个用于更新交互状态的updateInteractivityLocked()
方法和用于更新网络状态的updateConnectivityState()
方法。先看看updateInteractivityLocked()
方法:
void updateInteractivityLocked() {
// The interactivity state from the power manager tells us whether the display is
// in a state that we need to keep things running so they will update at a normal
// frequency.
//获取设备是否处于交互状态
boolean screenOn = mPowerManager.isInteractive();
if (DEBUG) Slog.d(TAG, "updateInteractivityLocked: screenOn=" + screenOn);
//表示当前不处于交互状态且上次处于交互状态
if (!screenOn && mScreenOn) {
mScreenOn = false;
if (!mForceIdle) {//是否强制进入Idle
//进入Idle模式的入口方法
becomeInactiveIfAppropriateLocked();
}
} else if (screenOn) {
mScreenOn = true;
if (!mForceIdle) {
//退出Idle模式
becomeActiveLocked("screen", Process.myUid());
}
}
}
这个方法在Android 8.0及以前,是updateDisplayLocked(),在8.1中,修改了名字。这个方法中通过设备当前的交互状态,决定是否进入Doze模式或者退出Doze模式,如果当前处于不交互状态(PMS中分析过,wakefulness=asleep
或者wakefulness=doze
),且上次处于交互状态,则会调用becomeInteractiveIfAppropriateLocked()
方法,开始准备进入Doze的工作;如果条件相反,调用becomeActiveLocked()
,做退出Doze模式Idle状态的工作。这两个方法在下面的内容中进行分析。
分析到这里,DIC的启动流程就完毕了。启动流程图及主要工作如下所示:
下篇文章中将分析Light Doze
模式的实现流程。