概述
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来实现的。
1.DeviceIdleController的启动流程
DeviceIdleController(以下简称DIC)继承于SystemService,因此也是一个系统服务,在分析PMS的时候说过,继承于SytemService的服务启动有以下几个共同点:
- 1.在SystemServer中实例化并启动,启动时会执行SytemService的生命周期方法:
Constructor()->onStart()->onBootPhase()
; - 2.内部维护一个Binder和其他服务进行IPC通讯;
- 3.内部维护一个Internal类用于和System进程进行交互;
下面就从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.xmlmConfigFile = 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状态白名单的appmPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);//处于所有的白名单的app,包括用户添加的mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);//用户添加的白名单列表应用mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);if (mLocalActivityManager != null) {//将适用于所有情况的白名单列表通知给AMSmLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);}if (mLocalPowerManager != null) {//将适用于所有情况的白名单列表通知给PMSmLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);}if (mLocalAlarmManager != null) {//将用户添加到白名单列表中的应用通知给AlarmManagerServicemLocalAlarmManager.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//传感器配置为trueif (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、AlarmManagerServicemLocalActivityManager.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对象,该对象用来检测设备是否处于禁止状态;然后注册了四种广播的监听,分别是:
- 1.电源状态发生改变时的广播:
Intent.ACTION_BATTERY_CHANGED
,由BatteryService中发送。 - 2.卸载应用时的广播:
Intent.ACTION_PACKAGE_REMOVED
,由PKMS发送; - 3.网络连接状态改变时的广播:
ConnectivityManager.CONNECTIVITY_ACTION
; - 4亮灭屏时的广播:
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
模式的实现流程。