1. 程式人生 > >Android 8.1 Doze模式分析

Android 8.1 Doze模式分析

 

概述

Doze模式可以簡單概括為:

若判斷使用者在連續的一段時間內沒有使用手機,就延緩終端中APP後臺的CPU和網路活動,以達到減少電量消耗的目的。


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圖示如下: 

上面這張圖比較經典,基本上說明了Doze模式的含義。 
 圖中的橫軸表示時間,紅色部分表示終端處於喚醒的執行狀態,綠色部分就是Doze模式定義的休眠狀態。

從圖中的描述,我們可以看到:如果一個使用者停止充電(on battery: 利用電池供電),關閉螢幕(screen off),手機處於靜止狀態(stationary: 位置沒有發生相對移動),保持以上條件一段時間之後,終端就會進入Doze模式。一旦進入Doze模式,系統就減少(延緩)應用對網路的訪問、以及對CPU的佔用,來節省電池電量。

如圖所示,Doze模式還定義了maintenance window。 
 在maintenance window中,系統允許應用完成它們被延緩的動作,即可以使用CPU資源及訪問網路。 
 從圖中我們可以看出,當進入Doze模式的條件一直滿足時,Doze模式會定期的進入到maintenance window,但進入的間隔越來越長。 
 通過這種方式,Doze模式可以使終端處於較長時間的休眠狀態。

需要注意的是:一旦Doze模式的條件不再滿足,即使用者充電、或開啟螢幕、或終端的位置發生了移動,終端就恢復到正常模式。 
 因此,當用戶頻繁使用手機時,Doze模式幾乎是沒有什麼實際用處的。

具體來講,當終端處於Doze模式時,進行了以下操作: 
1、暫停網路訪問。 
2、系統忽略所有的WakeLock。 
3、標準的AlarmManager alarms被延緩到下一個maintenance window。 
 但使用AlarmManager的 setAndAllowWhileIdle、setExactAndAllowWhileIdle和setAlarmClock時,alarms定義事件仍會啟動。
 在這些alarms啟動前,系統會短暫地退出Doze模式。 
4、系統不再進行WiFi掃描。 
5、系統不允許sync adapters執行。 
6、系統不允許JobScheduler執行。

Deep Doze 和Light Doze模式對比如下:

DeviceIdleController的啟動流程

開機時

SystemServer.java-->startOtherServices()

private void startOtherServices() {
    .............
    mSystemServiceManager.startService(DeviceIdleController.class);
    ............
}

構造方法:

    public DeviceIdleController(Context context) {
        super(context);
        mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
        mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());

        // NOTE: Bug #627645 low power Feature BEG-->
        mPowerControllerHelper = new PowerControllerHelper();
        // <--NOTE: Bug #627645 low power Feature END

        mAbsDeviceIdleController = (AbsDeviceIdleController)PMSFactory.getInstance().createExtraDeviceIdleController(context);
    }

構造方法中,首先建立在/data/sytem下建立了deviceidle.xml檔案,然後建立了一個Handler,並將BackgroundThread中Handler的Looper和該Handler進行繫結。BackgroundThread是一個單例模式實現的後臺執行緒,可以用於任何一個程序。


DeviceIdleController.java-->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模式下都允許後臺執行的應用白名單; 

    public ArraySet<String> getAllowInPowerSaveExceptIdle() {
        return mAllowInPowerSaveExceptIdle;
    }

    public ArraySet<String> getAllowInPowerSave() {
        return mAllowInPowerSave;
    }
             } else if ("allow-in-power-save-except-idle".equals(name) && allowAll) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
                                + permFile + " at " + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSaveExceptIdle.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("allow-in-power-save".equals(name) && allowAll) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSave.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

我們再看下platform.xml檔案

    <!-- These are the standard packages that are white-listed to always have internet
         access while in power save mode, even if they aren't in the foreground. -->
    <allow-in-power-save package="com.android.providers.downloads" />
    <allow-in-power-save package="com.sprd.ImsConnectionManager" />
    <allow-in-power-save package="com.android.phone" />
    <allow-in-power-save package="com.spreadtrum.vowifi" />

onStart()呼叫updateWhitelistAppIdsLocked()更新白名單列表

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);
    }
}

執行完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) {
    }
}

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());
        }
    }
}

updateConnectivityState()用於更新網路狀態

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());
        }
    }
}

DeviceIdleController的狀態變化

對於充電狀態,在onBootPhase函式中已經提到,DeviceIdleController監聽了ACTION_BATTERY_CHANGED廣播:

............  
IntentFilter filter = new IntentFilter();  
filter.addAction(Intent.ACTION_BATTERY_CHANGED);  
getContext().registerReceiver(mReceiver, filter);  
...........  
                case Intent.ACTION_BATTERY_CHANGED: {
                    synchronized (DeviceIdleController.this) {
                        int plugged = intent.getIntExtra("plugged", 0);
                        updateChargingLocked(plugged != 0);
                    }
                } break;

DeviceIdleController.java-->updateChargingLocked()

void updateChargingLocked(boolean charging) {  
    .........  
    if (!charging && mCharging) {  
        //從充電狀態變為不充電狀態  
        mCharging = false;  
        //mForceIdle值一般為false,是通過dumpsys命令將mForceIdle改成true的  
        if (!mForceIdle) {  
            //判斷是否進入Doze模式  
            becomeInactiveIfAppropriateLocked();  
        }  
    } else if (charging) {  
        //進入充電狀態  
        mCharging = charging;  
        if (!mForceIdle) {  
            //手機退出Doze模式  
            becomeActiveLocked("charging", Process.myUid());  
        }  
    }  
}  

我們先看下becomeInactiveIfAppropriateLocked()開始進入Doze模式

void becomeInactiveIfAppropriateLocked() {  
    .................  
    //螢幕熄滅,未充電  
    if ((!mScreenOn && !mCharging) || mForceIdle) {  
        // Screen has turned off; we are now going to become inactive and start  
        // waiting to see if we will ultimately go idle.  
        if (mState == STATE_ACTIVE && mDeepEnabled) {  
            mState = STATE_INACTIVE;  
            ...............  
            //重置事件  
            resetIdleManagementLocked();  
  
            //開始檢測是否可以進入Doze模式的Idle狀態  
            //若終端沒有watch feature, mInactiveTimeout時間為30min  
            scheduleAlarmLocked(mInactiveTimeout, false);  
            ...............  
        }  
  
        if (mLightState == LIGHT_STATE_ACTIVE && mLightEnabled) {  
            mLightState = LIGHT_STATE_INACTIVE;  
            .............  
            resetLightIdleManagementLocked();//重置事件  
            scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);  
        }  
    }  
}  

要進入Doze流程,就是呼叫這個函式,首先要保證螢幕滅屏然後沒有充電。這裡還有mDeepEnable和mLightEnable前面說過是在配置中定義的,一般預設是關閉(也就是不開Doze模式)。這裡mLightEnabled是對應禁止wakelock持鎖的,禁止網路。而mDeepEnabled對應是檢測裝置是否靜止,除了禁止wakelock、禁止網路、還會機制alarm。

ight idle 和deep idle根據不同條件進入


void becomeInactiveIfAppropriateLocked() {
        if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");
        if ((!mScreenOn && !mCharging) || mForceIdle) {
            // Screen has turned off; we are now going to become inactive and start
            // waiting to see if we will ultimately go idle.
            if (mState == STATE_ACTIVE && mDeepEnabled) {
                mState = STATE_INACTIVE;
                if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");
                resetIdleManagementLocked();
                scheduleAlarmLocked(mInactiveTimeout, 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");
                resetLightIdleManagementLocked();
                scheduleLightAlarmLocked(mConstants.LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT);
                EventLogTags.writeDeviceIdleLight(mLightState, "no activity");
            }
        }

light idle模式

light idle模式下會禁止網路、wakelock,但是不會禁止alarm

我們先看scheduleLightAlarmLocked函式,這裡設定了一個alarm,delay是5分鐘。到時間後呼叫mLightAlarmListener回撥。

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);  
}  

mLightAlarmListener就是進入lightidle,呼叫stepLightIdleStateLocked函式

    private final AlarmManager.OnAlarmListener mLightAlarmListener
            = new AlarmManager.OnAlarmListener() {
        @Override
        public void onAlarm() {
            synchronized (DeviceIdleController.this) {
                stepLightIdleStateLocked("s:alarm");
            }
        }
    };

我們來看stepLightIdleStateLocked函式,這個函式會處理mLightState不同狀態,會根據不同狀態,然後設定alarm,到時間後繼續處理下個狀態。到LIGHT_STATE_IDLE_MAINTENANCE狀態處理時,會發送MSG_REPORT_IDLE_ON_LIGHT。這個訊息的處理會禁止網路、禁止wakelock。然後到LIGHT_STATE_WAITING_FOR_NETWORK,會先退出Doze狀態(這個時候網路、wakelock恢復)。然後設定alarm,alarm時間到後,還是在LIGHT_STATE_IDLE_MAINTENANCE狀態。和之前一樣(禁止網路、wakelock)。只是設定的alarm間隔會越來越大,也就是隻要螢幕滅屏後,時間越長。裝置會隔越來越長的時間才會退出Doze狀態,這也符合一個實際情況,但是會有一個上限值。

 void stepLightIdleStateLocked(String reason) {
        if (mLightState == LIGHT_STATE_OVERRIDE) {
            // If we are already in deep device idle mode, then
            // there is nothing left to do for light mode.
            return;
        }

        if (DEBUG) Slog.d(TAG, "stepLightIdleStateLocked: mLightState=" + mLightState);
        EventLogTags.writeDeviceIdleLightStep();

        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()) {
                    // We have some active ops going on...  give them a chance to finish
                    // before going in to our first idle.
                    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) {
                    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;
                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);
                mGoingIdleWakeLock.acquire();
//傳送訊息,這個訊息處理就會關閉網路,禁止wakelock  
                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);
                    //醒一下(開啟網路、恢復wakelock)
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
                } else {
                    // We'd like to do maintenance, but currently don't have network
                    // connectivity...  let's try to wait until the network comes back.
                    // We'll only wait for another full idle period, however, and then give up.
                    scheduleLightAlarmLocked(mNextLightIdleDelay);
                    if (DEBUG) Slog.d(TAG, "Moved to LIGHT_WAITING_FOR_NETWORK.");
                    mLightState = LIGHT_STATE_WAITING_FOR_NETWORK;
                    EventLogTags.writeDeviceIdleLight(mLightState, reason);
                }
                break;
        }
    }

deep idle模式

下面我們再來看deep idle模式,這個模式除了禁止網路、wakelock還會禁止alarm。

我們再來看becomeInactiveIfAppropriateLocked函式中下面程式碼。是關於deep idle的設定 這裡的mInactiveTimeout是半小時

void becomeInactiveIfAppropriateLocked() {  
    if (DEBUG) Slog.d(TAG, "becomeInactiveIfAppropriateLocked()");  
    if ((!mScreenOn && !mCharging) || mForceIdle) {  
        // Screen has turned off; we are now going to become inactive and start  
        // waiting to see if we will ultimately go idle.  
        if (mState == STATE_ACTIVE && mDeepEnabled) {  
            mState = STATE_INACTIVE;  
            if (DEBUG) Slog.d(TAG, "Moved from STATE_ACTIVE to STATE_INACTIVE");  
            resetIdleManagementLocked();  
            scheduleAlarmLocked(mInactiveTimeout, false);  
            EventLogTags.writeDeviceIdle(mState, "no activity");  
        }  

scheduleAlarmLocked(),注意如果這裡引數idleUntil是true會呼叫AlarmManager的setIdleUntil函式,呼叫這個函式後普通應用設定alarm將失效。

void scheduleAlarmLocked(long delay, boolean idleUntil) {  
    if (mMotionSensor == null) {  
        //在onBootPhase時,獲取過位置檢測感測器  
        //如果終端沒有配置位置檢測感測器,那麼終端永遠不會進入到真正的Doze ilde狀態  
        // If there is no motion sensor on this device, then we won't schedule  
        // alarms, because we can't determine if the device is not moving.  
        return;  
    }  
  
    mNextAlarmTime = SystemClock.elapsedRealtime() + delay;  
    if (idleUntil) {  
        //此時IdleUtil的值為false  
        mAlarmManager.setIdleUntil(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);  
    } else {  
        //30min後喚醒,呼叫mDeepAlarmListener的onAlarm函式  
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,  
                mNextAlarmTime, "DeviceIdleController.deep", mDeepAlarmListener, mHandler);  
    }  
}  

需要注意的是,DeviceIdleController一直在監控螢幕狀態和充電狀態,一但不滿足Doze模式的條件,前面提到的becomeActiveLocked函式就會被呼叫。mAlarmManager設定的定時喚醒事件將被取消掉,mDeepAlarmListener的onAlarm函式不會被呼叫。

因此,我們知道了終端必須保持Doze模式的入口條件長達30min,才會進入mDeepAlarmListener.onAlarm:

private final AlarmManager.OnAlarmListener mDeepAlarmListener  
        = new AlarmManager.OnAlarmListener() {  
    @Override  
    public void onAlarm() {  
        synchronized (DeviceIdleController.this) {  
            //進入到stepIdleStateLocked函式  
            stepIdleStateLocked("s:alarm");  
        }  
    }  
};  

下面我們就來看下stepIdleStateLocked方法:

void stepIdleStateLocked(String reason) {  
    ..........  
    final long now = SystemClock.elapsedRealtime();  
    //個人覺得,下面這段程式碼,是針對Idle狀態設計的  
    //如果在Idle狀態收到Alarm,那麼將先喚醒終端,然後重新判斷是否需要進入Idle態  
    //在介紹Doze模式原理時提到過,若應用呼叫AlarmManager的一些指定介面,仍然可以在Idle狀態進行工作  
    if ((now+mConstants.MIN_TIME_TO_ALARM) > mAlarmManager.getNextWakeFromIdleTime()) {  
        // Whoops, there is an upcoming alarm.  We don't actually want to go idle.  
        if (mState != STATE_ACTIVE) {  
            becomeActiveLocked("alarm", Process.myUid());  
            becomeInactiveIfAppropriateLocked();  
        }  
        return;  
    }  
  
    //以下是Doze模式的狀態轉變相關的程式碼  
    switch (mState) {  
        case STATE_INACTIVE:  
            // We have now been inactive long enough, it is time to start looking  
            // for motion and sleep some more while doing so.  
            //保持螢幕熄滅,同時未充電達到30min,進入此分支  
  
            //註冊一個mMotionListener,檢測是否移動  
            //如果檢測到移動,將重新進入到ACTIVE狀態  
            //相應程式碼比較直觀,此處不再深入分析  
            startMonitoringMotionLocked();  
  
            //再次呼叫scheduleAlarmLocked函式,此次的時間仍為30min  
            //也就說如果不發生退出Doze模式的事件,30min後將再次進入到stepIdleStateLocked函式  
            //不過屆時的mState已經變為STATE_IDLE_PENDING  
            scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false);  
  
            // Reset the upcoming idle delays.  
            //mNextIdlePendingDelay為5min  
            mNextIdlePendingDelay = mConstants.IDLE_PENDING_TIMEOUT;  
            //mNextIdleDelay為60min  
            mNextIdleDelay = mConstants.IDLE_TIMEOUT;  
  
            //狀態變為STATE_IDLE_PENDING   
            mState = STATE_IDLE_PENDING;  
            ............  
            break;  
        case STATE_IDLE_PENDING:  
            //保持息屏、未充電、靜止狀態,經過30min後,進入此分支  
            mState = STATE_SENSING;  
  
            //保持Doze模式條件,4min後再次進入stepIdleStateLocked  
            scheduleSensingTimeoutAlarmLocked(mConstants.SENSING_TIMEOUT);  
  
            //停止定位相關的工作  
            cancelLocatingLocked();  
            mNotMoving = false;  
            mLocated = false;  
            mLastGenericLocation = null;  
            mLastGpsLocation = null;  
  
            //開始檢測手機是否發生運動(這裡應該是更細緻的側重於角度的變化)  
            //若手機運動過,則重新變為active狀態  
            mAnyMotionDetector.checkForAnyMotion();  
            break;  
        case STATE_SENSING:  
            //上面的條件滿足後,進入此分支,開始獲取定位資訊  
            cancelSensingTimeoutAlarmLocked();  
            mState = STATE_LOCATING;  
            ............  
            //保持條件30s,再次呼叫stepIdleStateLocked  
            scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT, false);  
  
            //網路定位  
            if (mLocationManager != null  
                    && mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {  
                mLocationManager.requestLocationUpdates(mLocationRequest,  
                        mGenericLocationListener, mHandler.getLooper());  
                mLocating = true;  
            } else {  
                mHasNetworkLocation = false;  
            }  
  
            //GPS定位  
            if (mLocationManager != null  
                    && mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {  
                mHasGps = true;  
                mLocationManager.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) {  
                //無法定位則直接進入下一個case  
                break;  
            }  
        case STATE_LOCATING:  
            //停止定位和運動檢測,直接進入到STATE_IDLE_MAINTENANCE  
            cancelAlarmLocked();  
            cancelLocatingLocked();  
            mAnyMotionDetector.stop();  
  
        case STATE_IDLE_MAINTENANCE:  
            //進入到這個case後,終端開始進入Idle狀態,也就是真正的Doze模式  
  
            //定義退出Idle的時間此時為60min  
            scheduleAlarmLocked(mNextIdleDelay, true);  
            ............  
            //退出週期逐步遞增,每次乘2  
            mNextIdleDelay = (long)(mNextIdleDelay * mConstants.IDLE_FACTOR);  
            ...........  
            //週期有最大值6h  
            mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);  
            if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {  
                mNextIdleDelay = mConstants.IDLE_TIMEOUT;  
            }  
  
            mState = STATE_IDLE;  
            ...........  
            //通知PMS、NetworkPolicyManagerService等Doze模式開啟,即進入Idle狀態  
            //此時PMS disable一些非白名單WakeLock;NetworkPolicyManagerService開始限制一些應用的網路訪問  
            //訊息處理的具體流程比較直觀,此處不再深入分析  
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON);  
            break;  
  
        case STATE_IDLE:  
            //進入到這個case時,本次的Idle狀態暫時結束,開啟maintenance window  
  
            // We have been idling long enough, now it is time to do some work.  
            mActiveIdleOpCount = 1;  
            mActiveIdleWakeLock.acquire();  
  
            //定義重新進入Idle的時間為5min (也就是手機可處於Maintenance window的時間)  
            scheduleAlarmLocked(mNextIdlePendingDelay, false);  
  
            mMaintenanceStartTime = SystemClock.elapsedRealtime();  
            //調整mNextIdlePendingDelay,乘2(最大為10min)  
            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;  
            }  
  
            mState = STATE_IDLE_MAINTENANCE;  
            ...........  
            //通知PMS等暫時退出了Idle狀態,可以進行一些工作  
            //此時PMS enable一些非白名單WakeLock;NetworkPolicyManagerService開始允許應用的網路訪問  
            mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);  
            break;  
    }  
}  

上面的流程在註釋裡面已經很明白了,而我們在進入Deep idle時,傳送了一個MSG_REPORT_IDLE_ON訊息,我們看下面這個訊息的處理和之前的MSG_REPORT_IDLE_ON_LIGHT一樣的,關閉網路,禁止wakelock。

case MSG_REPORT_IDLE_ON:  
case MSG_REPORT_IDLE_ON_LIGHT: {  
    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();  
} break;  

而禁止alarm是通過呼叫如下函式,注意引數是true。引數是true會呼叫mAlarmManager.setIdleUntil函式。這樣其他的alarm會被滯後(除非在白名單中)

  1. scheduleAlarmLocked(mNextIdleDelay, true);  

而每隔一段時間會進入Maintenance window的時間,此時是通過傳送MSG_REPORT_IDLE_OFF訊息,來恢復網路和wakelock。而這個時候之前設定的mAlarmManager.setIdleUntil的alarm也到期了,因此其他alarm也恢復了。但是這個時間只有5分鐘,重新設定了alarm再次進入deep idle狀態。