android 10电源管理分析之Doze模式(三)

2,693 阅读11分钟

概述

从android 6.0开始,android加入了两种省电策略,通过管理设备未连接充电器时的应用程序的行为来延长电池使用时长。当设备长时间未使用时,Doze通过推迟应用程序后台CPU和网络的活动来减少电量的消耗。当设备处于Doze状态时,应用程序对某些电量密集型资源的访问将推迟到维护窗口期。

如果用户在未充电的情况下将屏幕关闭并静止一段时间,则设备将进入Doze模式。在Doze模式下,系统试图通过限制应用程序访问网络和CPU密集型服务来节省电量。它还阻止应用程序访问网络,并延迟其作业、同步和警报(Alarm)。 系统会定期短暂退出Doze状态,让应用程序完成推迟的活动。在此维护窗口期间,系统运行所有挂起的同步、作业和警报,并允许应用程序访问网络。

2508c65462885df81edb291a2fda12fd4250eb 在每个维护窗口结束时,系统再次进入Doze状态,暂停网络访问并延迟作业、同步和警报器。随着时间的推移,系统安排维护窗口期的频率越来越低,这有助于在设备未连接充电器时长期不活动的情况下减少电池消耗。

一旦用户通过移动设备、打开屏幕或连接充电器唤醒设备,系统将退出休眠,所有应用程序将恢复正常活动。

Doze 限制 在Doze状态下,你的app会受到以下限制:

  • 网络访问被挂起
  • 系统会忽略唤醒锁
  • AlarmManager警报(包括setExact()和setWindow())将延迟到下一个维护窗口
  • 系统不执行Wi-Fi扫描
  • 系统不允许 sync adapters 运行。
  • 系统不允许 JobScheduler 运行。

可以看到,在Doze模式下,App的推送等功能受到了极大的限制,google官方建议使用FCM解决问题。但由于国情的原因,国内这项服务是被阉割掉了的,这给广大的android开发人员带来了极其糟糕的编码体验。

Doze的两种模式

上一小节对Doze模式做了一个简单的介绍,从这一小节开始, 我们将从源码的层面来了解framework在哪些地方实现了概述里介绍的功能,让各位读者对Doze模式的整体设计有更为直观的认识。

先从Doze模式的入口DeviceIdleController类开始看起,构造方法的内容很简单,我们直接来看onStart方法:

DeviceIdleController.java
   public void onStart() {
        final PackageManager pm = getContext().getPackageManager();

        synchronized (this) {
	    //设置是否开启Doze模式。
            mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                    com.android.internal.R.bool.config_enableAutoPowerModes);
            SystemConfig sysConfig = SystemConfig.getInstance();
	    //初始化除doze模式之外,可以运行的系统应用白名单
            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) {
                }
            }
            //初始化在doze模式也可以运行的系统应用白名单,xml tag为allow-in-power-save
            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) {
                }
            }

            mConstants = mInjector.getConstants(this, mHandler, getContext().getContentResolver());
	    //解析/data/system/deviceidle.xml文件,这个文件存储的是用户设置的Doze模式白名单,可通过addPowerSaveWhitelistApp方法设置
            readConfigFileLocked();
	    //将上述不同渠道的白名单整合,并通知到AMS和PWMS中
            updateWhitelistAppIdsLocked();

            mNetworkConnected = true;
            mScreenOn = true;
            mScreenLocked = false;
            // 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;
            mActiveReason = ACTIVE_REASON_UNKNOWN;
            mState = STATE_ACTIVE;
            mLightState = LIGHT_STATE_ACTIVE;
            mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
	    //应用于INACTIVE_TIMEOUT和IDLE_AFTER_INACTIVE_TIMOUT的系数,以便更快或更慢地进入STATE_IDLE。
            mPreIdleFactor = 1.0f;
            mLastPreIdleFactor = 1.0f;
        }

        mBinderService = new BinderService();
        publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
        publishLocalService(LocalService.class, new LocalService());
    }

看一下这条语句: ArraySet<String> allowPowerExceptIdle = sysConfig.getAllowInPowerSaveExceptIdle();

这条语句的目的是获取除doze模式之外,可以运行的系统应用白名单。SystemConfig构造方法中会从system/etc、vendor/etc、odm/etc、product/etc等几个目录的sysconfig和permission文件夹下的xml文件中解析allow-in-power-save-except-idle的标签所标识的包名,并缓存在SystemConfig的mAllowInPowerSaveExceptIdle变量中。

上面代码片段中还出现了两个重要的知识点,具体来看一下:

1. 深度休眠模式

mState变量保存的是深度休眠模式下的状态,共有以下7种状态。

StateDesc
STATE_ACTIVE设备当前属于活动状态
STATE_INACTIVE设备处于非活动状态(屏幕熄灭,没有运动),等待进入休眠状态
STATE_IDLE_PENDING设备已过初始非活动期,正在等待下一个休眠期
STATE_SENSING设备当前正在感应设备是否被移动
STATE_LOCATING设备当前正在定位
STATE_IDLE设备处于休眠状态,并尽量保持
STATE_IDLE_MAINTENANCE设备处于休眠状态,但暂时脱离休眠状态进行定期维护

mPreIdleFactor 概念解释中有两个时间 INACTIVE_TIMEOUT 和 IDLE_AFTER_INACTIVE_TIMOUT 需要澄清一下。这两个时间变量是什么含义呢? DeviceIdleController会监听ACTION_SCREEN_OFF的广播,当设备息屏时,Doze的状态会进入STATE_INACTIVE阶段,并且在 mPreIdleFactor * INACTIVE_TIMEOUT 秒后,进入下一个阶段—— STATE_IDLE_PENDING ;在这个阶段, 同样会等待 mPreIdleFactor * IDLE_AFTER_INACTIVE_TIMEOUT 秒后,进入STATE_SENSING状态。因此,这两个值其实是状态跃迁的等待时间。

下面用一张图来更直观的展示一下 深度休眠模式的 状态的变迁历程:

kq.png

上图中的时间有写15min or 30min,表示的是一般设备进入下一个状态所需的时间。实际上,具体的状态变迁的时间受三个方面的因素的影响。一是 时间系数;二是 电池容量,电池容量小的设备会更快地进入深度休眠状态; 三是 是否开启了时间压缩模式。

2. 轻度休眠模式

有深度休眠模式,自然对应的也有轻度休眠模式,代码中用mLightState变量来保存这个模式下的状态,具体的可选值如下:

StateDesc
LIGHT_STATE_ACTIVE设备当前属于活动状态
LIGHT_STATE_INACTIVE设备处于非活动状态(屏幕熄灭),等待进入第一次轻度休眠
LIGHT_STATE_PRE_IDLE设备即将休眠,正在等待当前工作完成
LIGHT_STATE_IDLE设备处于轻度休眠状态,并尽量保持
LIGHT_STATE_WAITING_FOR_NETWORK设备处于轻度休眠状态,我们希望进入休眠维护,但在进行此操作之前正在等待网络连接
LIGHT_STATE_IDLE_MAINTENANCE设备处于轻度休眠状态,但暂时脱离休眠状态进行定期维护
LIGHT_STATE_OVERRIDE设备轻度休眠状态被覆盖,现在进入深度休眠状态

轻度休眠模式的状态的变迁历程如下图所示:

kk_image.png

有没有发现,上面的这个状态转换图中并没有LIGHT_STATE_OVERRIDE这个状态,为什么?

原因在于 LIGHT_STATE_OVERRIDE的状态转换发生在 深度休眠模式的 STATE_IDLE阶段,当设备进入深度休眠模式时,就会将轻度休眠模式的mLightState置为LIGHT_STATE_OVERRIDE。


那么,轻度休眠模式和轻度休眠模式有什么区别呢?什么时候会进入轻度休眠模式,又是什么时候会进入深度休眠模式?

为了更好地回答上述问题,我们要来具体看一看,轻度休眠模式和深度休眠模式分别做了什么,它们内部的状态又是如何转换的。

轻度休眠模式和深度休眠模式的触发初始条件都是收到息屏广播,它最终会调用到以下这个方法:

 void becomeInactiveIfAppropriateLocked() {
        verifyAlarmStateLocked();

        final boolean isScreenBlockingInactive =
                mScreenOn && (!mConstants.WAIT_FOR_UNLOCK || !mScreenLocked);
        if (!mForceIdle && (mCharging || isScreenBlockingInactive)) {
            return;
        }
        // Become inactive and determine if we will ultimately go idle.
        if (mDeepEnabled) {
            if (mQuickDozeActivated) {
                if (mState == STATE_QUICK_DOZE_DELAY || mState == STATE_IDLE
                        || mState == STATE_IDLE_MAINTENANCE) {
                    // Already "idling". Don't want to restart the process.
                    // mLightState can't be LIGHT_STATE_ACTIVE if mState is any of these 3
                    // values, so returning here is safe.
                    return;
                }
                if (DEBUG) {
                    Slog.d(TAG, "Moved from "
                            + stateToString(mState) + " to STATE_QUICK_DOZE_DELAY");
                }
                mState = STATE_QUICK_DOZE_DELAY;
                // Make sure any motion sensing or locating is stopped.
                resetIdleManagementLocked();
                // Wait a small amount of time in case something (eg: background service from
                // recently closed app) needs to finish running.
                scheduleAlarmLocked(mConstants.QUICK_DOZE_DELAY_TIMEOUT, false);
                EventLogTags.writeDeviceIdle(mState, "no activity");
            } else if (mState == STATE_ACTIVE) {
                mState = STATE_INACTIVE;
                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                resetIdleManagementLocked();
                long delay = mInactiveTimeout;
                if (shouldUseIdleTimeoutFactorLocked()) {
                    delay = (long) (mPreIdleFactor * delay);
                }
                scheduleAlarmLocked(delay, false);
                EventLogTags.writeDeviceIdle(mState, "no activity");
            }
        }
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {
            mLightState = LIGHT_STATE_INACTIVE;
            if (DEBUG) Slog.d(TAG, "Moved from LIGHT_STATE_ACTIVE to LIGHT_STATE_INACTIVE");
	    //取消所有的状态切换alarm
            resetLightIdleManagementLocked();
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
            EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
        }
    }

从上述代码中可以看到,两者一开始就都会被触发,并不是从轻度休眠过渡到深度休眠的这种逻辑。一般情况下,轻度休眠从STATE_INACTIVE过渡到下一个状态只需要3分钟,而深度休眠则需要至少15分钟,我们先从轻度休眠的逻辑线继续追踪。

  void scheduleLightAlarmLocked(long delay) {
        if (DEBUG) Slog.d(TAG, "scheduleLightAlarmLocked(" + delay + ")");
        mNextLightAlarmTime = SystemClock.elapsedRealtime() + delay;
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                mNextLightAlarmTime, "DeviceIdleController.light", mLightAlarmListener, mHandler);
    }

状态切换的等待时间是通过Alarm机制来管控的,也只有通过Alarm,系统才能在休眠后被重新唤醒,进入维护窗口期。

到达预定时间后,Alarm会回调传入的mLightAlarmListener,继续做后续的处理。mLightAlarmListener里调用stepLightIdleStateLocked方法,开始切换轻度休眠模式的状态:

 void stepLightIdleStateLocked(String reason) {
        if (mLightState == LIGHT_STATE_OVERRIDE) {
            // 如果处于深度休眠模式,则不做任何操作了
            return;
        }

        switch (mLightState) {
            case LIGHT_STATE_INACTIVE:
                mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                // Reset the upcoming idle delays.
                mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                mMaintenanceStartTime = 0;
                if (!isOpsInactiveLocked()) {
                    //进入LIGHT_STATE_PRE_IDLE状态,等待LIGHT_PRE_IDLE_TIMEOUT后切换到下一个状态
                    mLightState = LIGHT_STATE_PRE_IDLE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    scheduleLightAlarmLocked(mConstants.LIGHT_PRE_IDLE_TIMEOUT);
                    break;
                }
                // Nothing active, fall through to immediately idle.
            case LIGHT_STATE_PRE_IDLE:
            case LIGHT_STATE_IDLE_MAINTENANCE:
                if (mMaintenanceStartTime != 0) {
		   //这里的逻辑是,将维护窗口的平均时间尽量平衡在LIGHT_IDLE_MAINTENANCE_MIN_BUDGET的范围
		   //如果本次低于LIGHT_IDLE_MAINTENANCE_MIN_BUDGET,则将剩余时间增加到下个维护周期,如果高于则反之
                    long duration = SystemClock.elapsedRealtime() - mMaintenanceStartTime;
                    if (duration < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                        // We didn't use up all of our minimum budget; add this to the reserve.
                        mCurIdleBudget += (mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET-duration);
                    } else {
                        // We used more than our minimum budget; this comes out of the reserve.
                        mCurIdleBudget -= (duration-mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET);
                    }
                }
                mMaintenanceStartTime = 0;
		//alarm定时切换到下个状态
                scheduleLightAlarmLocked(mNextLightIdleDelay);
                mNextLightIdleDelay = Math.min(mConstants.LIGHT_MAX_IDLE_TIMEOUT,
                        (long)(mNextLightIdleDelay * mConstants.LIGHT_IDLE_FACTOR));
                if (mNextLightIdleDelay < mConstants.LIGHT_IDLE_TIMEOUT) {
                    mNextLightIdleDelay = mConstants.LIGHT_IDLE_TIMEOUT;
                }
                if (DEBUG) Slog.d(TAG, "Moved to LIGHT_STATE_IDLE.");
		//进入休眠状态
                mLightState = LIGHT_STATE_IDLE;
                EventLogTags.writeDeviceIdleLight(mLightState, reason);
                addEvent(EVENT_LIGHT_IDLE, null);
                mGoingIdleWakeLock.acquire();
		//这个Msg比较重要,下面会详细说明
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);
                break;
            case LIGHT_STATE_IDLE:
            case LIGHT_STATE_WAITING_FOR_NETWORK:
                if (mNetworkConnected || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK) {
                    // We have been idling long enough, now it is time to do some work.
                    mActiveIdleOpCount = 1;
                    mActiveIdleWakeLock.acquire();
                    mMaintenanceStartTime = SystemClock.elapsedRealtime();
                    if (mCurIdleBudget < mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET) {
                        mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MIN_BUDGET;
                    } else if (mCurIdleBudget > mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET) {
                        mCurIdleBudget = mConstants.LIGHT_IDLE_MAINTENANCE_MAX_BUDGET;
                    }
                    scheduleLightAlarmLocked(mCurIdleBudget);
                    if (DEBUG) Slog.d(TAG,
                            "Moved from LIGHT_STATE_IDLE to LIGHT_STATE_IDLE_MAINTENANCE.");
                    mLightState = LIGHT_STATE_IDLE_MAINTENANCE;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                    addEvent(EVENT_LIGHT_MAINTENANCE, null);
					//这个Msg也比较重要,下面会详细说明
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                } else {
                    //我们想进行维护,但目前没有网络连接…让我们尝试等到网络恢复。
                    // 然而,我们只会再等待一个完整的休眠周期,如果还没有等到的话,就放弃等待。
                    scheduleLightAlarmLocked(mNextLightIdleDelay);
                    if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
                    mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                }
                break;
        }
    }

上述过程中,最重要的部分就是MSG_REPORT_IDLE_ON_LIGHTMSG_REPORT_IDLE_OFF这两个Message,其余的逻辑都是在做定时状态切换,可以忽略。

DeviceIdleController.java#MyHandler
...
  case MSG_REPORT_IDLE_ON:
  case MSG_REPORT_IDLE_ON_LIGHT: {
       // mGoingIdleWakeLock is held at this point
       EventLogTags.writeDeviceIdleOnStart();
       final boolean deepChanged;
       final boolean lightChanged;
       if (msg.what == MSG_REPORT_IDLE_ON) {
           deepChanged = mLocalPowerManager.setDeviceIdleMode(true);
           lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);
       } else {
           deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
           lightChanged = mLocalPowerManager.setLightDeviceIdleMode(true);
       }
       try {
           mNetworkPolicyManager.setDeviceIdleMode(true);
           mBatteryStats.noteDeviceIdleMode(msg.what == MSG_REPORT_IDLE_ON
      		? BatteryStats.DEVICE_IDLE_MODE_DEEP: BatteryStats.DEVICE_IDLE_MODE_LIGHT, null, Process.myUid());
       } catch (RemoteException e) {
       }
       if (deepChanged) {
           getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
       }
       if (lightChanged) {
           getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
       }
       EventLogTags.writeDeviceIdleOnComplete();
       mGoingIdleWakeLock.release();
    } break;
...

可以看到,进入STATE_IDLE阶段后,handler做了以下三条关键操作:

  1. PWMS 关闭DeviceIdle模式,开启 LightDeviceIdle 模式;DeviceIdle模式是在深度休眠状态下才会开启,一旦开启后,系统将忽略所有未列入白名单的应用程序的WakeLock; 而切换到LightDeviceIdle模式并没有做额外的处理操作,只是起到一个状态标识的作用。

  2. NetworkPolicyManagerService开启DeviceIdle模式,一旦开启后,除白名单的应用以外,其他app均无法请求网络。

  3. 对外发送android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGEDandroid.os.action.DEVICE_IDLE_MODE_CHANGED的广播。两个广播共同的接收者有 DeviceIdleJobsControllerBatterySaverControllerDeviceIdleJobsController收到广播后,会禁用所有白名单以外的app的JobScheduler。

除此之外,深度休眠广播还有以下额外的接收者:RttServiceImplAppStandbyControllerGnssLocationProviderWifiAwareStateManagerWifiServiceImpl,它们接收到广播后做了如下操作:

  • 禁用Wifi Rtt功能(wifi室内定位)
  • 检查StandBy的假释(perole)状态(ps:standby 是android 6.0引入的除Doze以外的另一个省电策略)。
  • 禁用GpS
  • 禁用Wi-Fi感知功能
  • 禁止Wifi扫描

MSG_REPORT_IDLE_OFF这条消息中做的操作,基本上就是将IDLE状态禁用的功能都重新启用,在此就不再做分析了。

我们来看看深度休眠模式下的状态切换代码:

  void stepIdleStateLocked(String reason) {
       ...

        switch (mState) {
            case STATE_INACTIVE:
                ...
                break;
            case STATE_IDLE_PENDING:
      			   ...

               break;
            case STATE_SENSING:
                cancelSensingTimeoutAlarmLocked();
                moveToStateLocked(STATE_LOCATING, reason);
                scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);
                LocationManager locationManager = mInjector.getLocationManager();
                if (locationManager != null
                        && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
                    locationManager.requestLocationUpdates(mLocationRequest,
                            mGenericLocationListener, mHandler.getLooper());
                    mLocating = true;
                } else {
                    mHasNetworkLocation = false;
                }
                if (locationManager != null
                        && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
                    mHasGps = true;
                    locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
                            mGpsLocationListener, mHandler.getLooper());
                    mLocating = true;
                } else {
                    mHasGps = false;
                }
                // If we have a location provider, we're all set, the listeners will move state
                // forward.
                if (mLocating) {
                    break;
                }

                // Otherwise, we have to move from locating into idle maintenance.
            case STATE_LOCATING:
                cancelAlarmLocked();
                cancelLocatingLocked();
                mAnyMotionDetector.stop();

                // Intentional fallthrough -- time to go into IDLE state.
            case STATE_QUICK_DOZE_DELAY:
                // Reset the upcoming idle delays.
                mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;

                // Everything is in place to go into IDLE state.
            case STATE_IDLE_MAINTENANCE:
                scheduleAlarmLocked(mNextIdleDelay, true);
                if (DEBUG) Slog.d(TAG, "Moved to STATE_IDLE. Next alarm in " + mNextIdleDelay +
                        " ms.");
                mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);
                if (DEBUG) Slog.d(TAG, "Setting mNextIdleDelay = " + mNextIdleDelay);
                mIdleStartTime = SystemClock.elapsedRealtime();
                mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
                if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
                    mNextIdleDelay = mConstants.IDLE_TIMEOUT;
                }
                moveToStateLocked(STATE_IDLE, reason);
                if (mLightState != LIGHT_STATE_OVERRIDE) {
                    mLightState = LIGHT_STATE_OVERRIDE;
                    cancelLightAlarmLocked();
                }
                addEvent(EVENT_DEEP_IDLE, null);
                mGoingIdleWakeLock.acquire();
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);
                break;
            case STATE_IDLE:
                // We have been idling long enough, now it is time to do some work.
                mActiveIdleOpCount = 1;
                mActiveIdleWakeLock.acquire();
                scheduleAlarmLocked(mNextIdlePendingDelay, false);
                if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE to STATE_IDLE_MAINTENANCE. " +
                        "Next alarm in " + mNextIdlePendingDelay + " ms.");
                mMaintenanceStartTime = SystemClock.elapsedRealtime();
				//attention here
                mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                        (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));
                if (mNextIdlePendingDelay < mConstants.IDLE_PENDING_TIMEOUT) {
                    mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;
                }
                moveToStateLocked(STATE_IDLE_MAINTENANCE, reason);
                addEvent(EVENT_DEEP_MAINTENANCE, null);
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                break;
        }
    }

上面代码和轻度休眠模式比较类似,大部分代码都是在负责定时状态切换,有所不同的是,深度休眠模式增加了两个状态,在这两个阶段分别去检测设备是否被移动以及进入休眠前做一次定位操作。IDLE ON和OFF所做的事情在轻度休眠模式下就已经分析过了,在此就不再赘述。

STATE_IDLE中有段代码值得注意一下:

 mNextIdlePendingDelay = Math.min(mConstants.MAX_IDLE_PENDING_TIMEOUT,
                        (long)(mNextIdlePendingDelay * mConstants.IDLE_PENDING_FACTOR));

这段代码说明,随着时间的推移,维护窗口的周期会越来越长(默认是2倍的系数增长),直到达到最大限制时间(6小时)。

Doze的中断

上文分析了Doze的进入条件以及进入后两种模式下的不同行为,那么Doze在什么情况下会被中断或唤醒呢?

从源码里追踪,笔者总结出了以下中断条件,追踪方式也很简单,只需要全局搜索becomeActiveLocked这个方法的调用处,就可以看到具体哪些操作执行了唤醒动作,感兴趣的读者也可以自己去尝试一下。

Doze中断条件

  • Notification的点击、回复操作。
  • 启动语音助手。
  • 与其他设备建立了蓝牙连接。
  • 显示锁屏界面。
  • adb 命令禁用Doze模式。
  • 传感器检测到设备发生了移动。
  • 插入了充电设备。

关于android的电源管理策略的分析的系列到此就暂时告一段落了,这篇文章的主要目的还是想起到一个抛砖引玉的作用,电源管理中还有StandBy省电策略以及PowerSave省电模式未作分析,感兴趣的读者可以再自行挖掘一下。