Android7.0 Doze模式分析(一)Doze介紹 & DeviceIdleController




參考:http://blog.csdn.net/gaugamela/article/details/52981984

在Android M中,Google就引入了Doze模式。它定義了一種全新的、低能耗的狀態。


 在該狀態,后台僅僅有部分任務被同意執行。其他任務都被強制停止。

在之前的博客中分析過Doze模式。就是device idle狀態。可能有的地方分析的不是非常具體,如今在android7.0上又一次分析下。

一、基本原理

Doze模式能夠簡單概括為:
 若推斷用戶在連續的一段時間內沒有使用手機,就延緩終端中APP后台的CPU和網絡活動,以達到降低電量消耗的目的。


上面這張圖比較經典,基本上說明了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執行。

另外我在還有一篇博客中:http://blog.csdn.net/kc58236582/article/details/50554174也具體介紹了Doze模式。能夠參考下,上面有一些命令使用等。


二、DeviceIdleController

Android中的Doze模式主要由DeviceIdleController來控制。

public class DeviceIdleController extends SystemService
        implements AnyMotionDetector.DeviceIdleCallback 

能夠看出DeviceIdleController繼承自SystemService,是一個系統級的服務。
同一時候,繼承了AnyMotionDetector定義的接口,便於檢測到終端位置變化后進行回調。

2.1 DeviceIdleController的初始化

接下來我們看看它的初始化過程。


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

如上代碼所看到的,SystemServer在startOtherServices中啟動了DeviceIdleController,將先后調用DeviceIdleController的構造函數和onStart函數。

構造函數

public DeviceIdleController(Context context) {
    super(context);
    //deviceidle.xml用於定義idle模式也能正常工作的非系統應用
    mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
    mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
}

DeviceIdleController的構造函數比較簡單,就是在創建data/system/deviceidle.xml相應的file文件,同一時候創建一個相應於后台線程的handler。這里的deviceidle.xml能夠在設置中的電池選項那里。

有電池優化,能夠將一些應用放到白名單中,調用DeviceIdleController的addPowerSaveWhitelistApp方法。最后會寫入deviceidle.xml文件,然后在下次開機的時候DeviceIdleController會又一次讀取deviceidle.xml文件然后放入白名單mPowerSaveWhitelistUserApps中。

onStart函數

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

    synchronized (this) {
        //讀取配置文件,推斷Doze模式是否同意被開啟
        mLightEnabled = mDeepEnabled = getContext().getResources().getBoolean(
                com.android.internal.R.bool.config_enableAutoPowerModes);

        //分析PKMS時提到過,PKMS掃描系統文件夾的xml,將形成SystemConfig
        SystemConfig sysConfig = SystemConfig.getInstance();

        //獲取除了device 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,
                        PackageManager.MATCH_SYSTEM_ONLY);
                int appid = UserHandle.getAppId(ai.uid);
                mPowerSaveWhitelistAppsExceptIdle.put(ai.packageName, appid);
                mPowerSaveWhitelistSystemAppIdsExceptIdle.put(appid, true);
            } catch (PackageManager.NameNotFoundException e) {
            }
        }

        //獲取device 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,
                         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) {
            }
        }

        //Constants為deviceIdleController中的內部類,繼承ContentObserver
        //監控數據庫變化,同一時候得到Doze模式定義的一些時間間隔
        mConstants = new Constants(mHandler, getContext().getContentResolver());

        //解析deviceidle.xml,並將當中定義的package相應的app。增加到mPowerSaveWhitelistUserApps中
        readConfigFileLocked();

        //將白名單的內容給AlarmManagerService和PowerMangerService
        //比如:DeviceIdleController推斷開啟Doze模式時,會通知PMS
        //此時除去白名單相應的應用外,PMS會將其他全部的WakeLock設置為Disable狀態
        updateWhitelistAppIdsLocked();

        //下面的初始化,都是如果眼下處在進入Doze模式相反的條件上
        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;

        //Doze模式定義終端初始時為ACTIVE狀態
        mState = STATE_ACTIVE;
        //屏幕狀態初始時為ACTIVE狀態
        mLightState = LIGHT_STATE_ACTIVE;
        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
    }

    //公布服務
    //BinderService和LocalService均為DeviceIdleController的內部類
    mBinderService = new BinderService();
    publishBinderService(Context.DEVICE_IDLE_CONTROLLER, mBinderService);
    publishLocalService(LocalService.class, new LocalService());
}

除去公布服務外。DeviceIdleController在onStart函數中。主要是讀取配置文件更新自己的變量,思路比較清晰。

在這里我們僅跟進一下updateWhitelistAppIdsLocked函數:

private void updateWhitelistAppIdsLocked() {
    //構造出除去idle模式外。可執行的app id數組 (可覺得是系統和普通應用的集合)
    //mPowerSaveWhitelistAppsExceptIdle從系統文件夾下的xml得到
    //mPowerSaveWhitelistUserApps從deviceidle.xml得到。或調用接口增加;
    //mPowerSaveWhitelistExceptIdleAppIds並未使用
    mPowerSaveWhitelistExceptIdleAppIdArray = buildAppIdArray(mPowerSaveWhitelistAppsExceptIdle,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistExceptIdleAppIds);

    //構造不受Doze限制的app id數組 (可覺得是系統和普通應用的集合)
    //mPowerSaveWhitelistApps從系統文件夾下的xml得到
    //mPowerSaveWhitelistAllAppIds並未使用
    mPowerSaveWhitelistAllAppIdArray = buildAppIdArray(mPowerSaveWhitelistApps,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistAllAppIds);

    //構造不受Doze限制的app id數組(僅普通應用的集合)、
    //mPowerSaveWhitelistUserAppIds並未使用
    mPowerSaveWhitelistUserAppIdArray = buildAppIdArray(null,
            mPowerSaveWhitelistUserApps, mPowerSaveWhitelistUserAppIds);

    if (mLocalPowerManager != null) {
        ...........
        //PMS拿到的是:系統和普通應用組成的不受Doze限制的app id數組 
        mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
    }

    if (mLocalAlarmManager != null) {
        ..........
        //AlarmManagerService拿到的是:普通應用組成的不受Doze限制的app id數組 
        mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
    }
}

updateWhitelistAppIdsLocked主要是將白名單交給PMS和AlarmManagerService。
注意Android區分了系統應用白名單、普通應用白名單等,因此上面進行了一些合並操作。這里我們有沒有發現。systemConfig的app不會增加alarm的白名單,而在Settings中電池那邊設置的白名單,會增加Power wakelock的白名單。

onBootPhase函數

與PowerManagerService一樣,DeviceIdleController在初始化的最后一個階段須要調用onBootPhase函數:

public void onBootPhase(int phase) {
    //在系統PHASE_SYSTEM_SERVICES_READY階段,進一步完畢一些初始化
    if (phase == PHASE_SYSTEM_SERVICES_READY) {
        synchronized (this) {
            //初始化一些變量
            mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
            ..............

            mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
            //依據配置文件,利用SensorManager獲取相應的傳感器,保存到mMotionSensor中
            ..............

            //假設配置文件表明:終端須要預獲取位置信息
            //則構造LocationRequest
            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;
            //創建一個AnyMotionDetector,同一時候將DeviceIdleController注冊到當中
            //當AnyMotionDetector檢測到手機變化角度超過門限時。就會回調DeviceIdleController的接口
            mAnyMotionDetector = new AnyMotionDetector(
                    (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
                    mHandler, mSensorManager, this, angleThreshold);

            //創建兩個經常使用的Intent。用於通知Doze模式的變化
            mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
            mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            mLightIdleIntent = new Intent(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
            mLightIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);

            //監聽ACTION_BATTERY_CHANGED廣播(電池信息發生改變)
            IntentFilter filter = new IntentFilter();
            filter.addAction(Intent.ACTION_BATTERY_CHANGED);
            getContext().registerReceiver(mReceiver, filter);

            //監聽ACTION_PACKAGE_REMOVED廣播(包被移除)
            filter = new IntentFilter();
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addDataScheme("package");
            getContext().registerReceiver(mReceiver, filter);

            //監聽CONNECTIVITY_ACTION廣播(連接狀態發生改變)
            filter = new IntentFilter();
            filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
            getContext().registerReceiver(mReceiver, filter);

            //又一次將白名單信息交給PowerManagerService和AlarmManagerService
            //這個工作在onStart函數中,已經調用updateWhitelistAppIdsLocked進行過了
            //到onBootPhase時。又一次進行一次,可能:一是為了保險。二是,其他進程可能調用接口,更改了相應數據,於是進行更新
            mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);

            //監聽屏幕顯示相關的變化
            mDisplayManager.registerDisplayListener(mDisplayListener, null);

            //更新屏幕顯示相關的信息
            updateDisplayLocked();
        }
        //更新連接狀態相關的信息
        updateConnectivityState(null);
    }   
}

從代碼能夠看出。onBootPhase方法:
 主要創建一些本地變量。然后依據配置文件初始化一些傳感器,同一時候注冊了一些廣播接收器和回到接口。
 最后更新屏幕顯示和連接狀態相關的信息。


2.2 DeviceIdleController的狀態變化

充電狀態的處理

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

............
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
getContext().registerReceiver(mReceiver, filter);
...........

我們看看receiver中相應的處理:

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override public void onReceive(Context context, Intent intent) {
        switch (intent.getAction()) {
            .........
            case Intent.ACTION_BATTERY_CHANGED: {
                synchronized (DeviceIdleController.this) {
                    //從廣播中得到是否在充電的消息
                    int plugged = intent.getIntExtra("plugged", 0);
                    updateChargingLocked(plugged != 0);
                }
            } break;
        }
    }
}。
依據上面的代碼,能夠看出當收到電池信息改變的廣播后,DeviceIdleController將得到電源是否在充電的消息,然后調用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模式,而becomeActiveLocked是退出Doze模式。

顯示狀態處理

DeviceIdleController中注冊了顯示變化的回調

                mDisplayManager.registerDisplayListener(mDisplayListener, null);
回調會調用updateDisplayLocked函數

    private final DisplayManager.DisplayListener mDisplayListener
            = new DisplayManager.DisplayListener() {
        @Override public void onDisplayAdded(int displayId) {
        }

        @Override public void onDisplayRemoved(int displayId) {
        }

        @Override public void onDisplayChanged(int displayId) {
            if (displayId == Display.DEFAULT_DISPLAY) {
                synchronized (DeviceIdleController.this) {
                    updateDisplayLocked();
                }
            }
        }
    };

updateDisplayLocked函數和更新充電狀態的函數updateChargingLocked類似

    void updateDisplayLocked() {
        mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
        // We consider any situation where the display is showing something to be it on,
        // because if there is anything shown we are going to be updating it at some
        // frequency so can't be allowed to go into deep sleeps.
        boolean screenOn = mCurDisplay.getState() == Display.STATE_ON;
        if (DEBUG) Slog.d(TAG, "updateDisplayLocked: screenOn=" + screenOn);
        if (!screenOn && mScreenOn) {
            mScreenOn = false;
            if (!mForceIdle) {//開始進入Doze模式
                becomeInactiveIfAppropriateLocked();
            }
        } else if (screenOn) {//屏幕點亮。退出Doze模式
            mScreenOn = true;
            if (!mForceIdle) {
                becomeActiveLocked("screen", Process.myUid());
            }
        }
    }

becomeActiveLocked函數退出Doze模式

我們先來看看becomeActiveLocked函數

//activeReason記錄的終端變為active的原因
void becomeActiveLocked(String activeReason, int activeUid) {
    ...........
    if (mState != STATE_ACTIVE || mLightState != STATE_ACTIVE) {
        ............
        //1、通知PMS等Doze模式結束
        scheduleReportActiveLocked(activeReason, activeUid);

        //更新DeviceIdleController本地維護的狀態
        //在DeviceIdleController的onStart函數中。我們已經知道了
        //初始時,mState和mLightState均為Active狀態
        mState = STATE_ACTIVE;//state是指設備通過傳感器推斷進入idle
        mLightState = LIGHT_STATE_ACTIVE;//mLight是背光的狀態

        mInactiveTimeout = mConstants.INACTIVE_TIMEOUT;
        mCurIdleBudget = 0;
        mMaintenanceStartTime = 0;

        //2、重置一些事件
        resetIdleManagementLocked();
        resetLightIdleManagementLocked();

        addEvent(EVENT_NORMAL);
    }
}

scheduleReportActiveLocked函數就是發送MSG_REPORT_ACTIVE消息

void scheduleReportActiveLocked(String activeReason, int activeUid) {
    //發送MSG_REPORT_ACTIVE消息
    Message msg = mHandler.obtainMessage(MSG_REPORT_ACTIVE, activeUid, 0, activeReason);
    mHandler.sendMessage(msg);
}

我們再看下消息的處理,主要調用了PowerManagerService的setDeviceIdleMode函數來退出Doze狀態,然后又一次更新wakelock的enable狀態。 以及通知NetworkPolicyManagerService不再限制應用上網,還有發送Doze模式改變的廣播。


.........
case MSG_REPORT_ACTIVE: {
    .........
    //通知PMS Doze模式結束,
    //於是PMS將一些Doze模式下。disable的WakeLock又一次enable
    //然后調用updatePowerStateLocked函數更新終端的狀態
    final boolean deepChanged = mLocalPowerManager.setDeviceIdleMode(false);
    final boolean lightChanged = mLocalPowerManager.setLightDeviceIdleMode(false);

    try {
        //通過NetworkPolicyManagerService更改Ip-Rule。不再限制終端應用上網
        mNetworkPolicyManager.setDeviceIdleMode(false);
        //BSS做好相應的記錄
        mBatteryStats.noteDeviceIdleMode(BatteryStats.DEVICE_IDLE_MODE_OFF,
                activeReason, activeUid);
    } catch (RemoteException e) {
    }

    //發送廣播
    if (deepChanged) {
        getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
    }
    if (lightChanged) {
        getContext().sendBroadcastAsUser(mLightIdleIntent, UserHandle.ALL);
    }
}
........

resetIdleManagementLocked函數就是取消alarm。檢測等。

void resetIdleManagementLocked() {
    //復位一些狀態變量
    mNextIdlePendingDelay = 0;
    mNextIdleDelay = 0;
    mNextLightIdleDelay = 0;

    //停止一些工作,主要是位置檢測相關的
    cancelAlarmLocked();
    cancelSensingTimeoutAlarmLocked();
    cancelLocatingLocked();
    stopMonitoringMotionLocked();
    mAnyMotionDetector.stop();
}


becomeInactiveIfAppropriateLocked函數開始進入Doze模式

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。


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

        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);//設置alarm,時間到后到下個步驟
                    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);
                mHandler.sendEmptyMessage(MSG_REPORT_IDLE_ON_LIGHT);//發送消息。這個消息處理就會關閉網絡,禁止wakelock
                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);
                    mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);//醒一下(開啟網絡、恢復wakelock)
                } 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;
        }
    }

可是這里僅僅是一個light idle。一旦進入deep idle,light idle設置的alarm會無效的(這個后面細說)。也就是說light idle一旦進入deep idle后無效了(由於idle step主要靠alarm驅動,而alarm無效后自然就驅動不了)。

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會被滯后(除非在白名單中)

scheduleAlarmLocked(mNextIdleDelay, true);

而每隔一段時間會進入Maintenance window的時間,此時是通過發送MSG_REPORT_IDLE_OFF消息,來恢復網絡和wakelock。

而這個時候之前設置的mAlarmManager.setIdleUntil的alarm也到期了,因此其它alarm也恢復了。

可是這個時間僅僅有5分鍾,又一次設置了alarm再次進入deep idle狀態。

Idle總結

當手機關閉屏幕或者拔掉電源的時候。手機開始推斷是否進入Doze模式。

Doze模式分兩種,第一種是light idle:

1.light idle

light idle在手機滅屏且沒有充電狀態下。5分鍾開始進入light idle流程。

然后第一次進入LIGHT_STATE_INACTIVE流程時,會再定義一個10分鍾的alarm。然后系統進入light idle狀態。這個狀態會使不是白名單的應用禁止訪問網絡,以及持wakelock鎖。

2.deep idle

deep idle除了light idle的狀態還會把非白名單中應用的alarm也禁止了。
 此時,系統中非白名單的應用將被禁止訪問網絡,它們申請的Wakelock也會被disable。


 從上面的代碼能夠看出,系統會周期性的退出Idle狀態。進入到MAINTENANCE狀態。集中處理相關的任務。


 一段時間后,會又一次再次回到IDLE狀態。

每次進入IDLE狀態,停留的時間都會是上次的2倍。最大時間限制為6h。

當手機運動。或者點亮屏幕,插上電源等。系統都會又一次返回到ACTIVIE狀態。

這里盜用別人的一樣圖,但不過deep idle的狀態:

(這里特別說明下,alarm和wakelock都是由DeviceIdleController主動調用相關接口設置的。而網絡是調用了DeviceIdleController的getAppIdWhitelist接口來獲取應用的白名單的,從而禁止非白名單訪問網絡。)


網絡我們不分析了,之前我們在Android6.0時分析過idle狀態下alarm和wakelock。7.0略微有點不一樣,以下兩篇博客又一次分析下吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM