Android Fk:[Doze] Android Doze Base on N
Android Fk:[Doze] Android Doze (base on N)
一、概述
Base on Android N
當系統滅屏並長時間處於靜止狀態時,系統會進入Doze狀態,此時
(1)限制網路訪問
(2)限制wakelock(cpu鎖)申請
(3)通過alarm manger設定的alarm 被推遲,除非設定了允許在idle狀態也能工作的flag
(4)wifi scan被取消
(5)jobscheduler和sync被限制
二、DeviceIdleController
2.1 DeviceIdleController
DIC架構:
DIC初始化:
看到初始化裡最重要的就是
1. 註冊廣播和監聽器監聽系統狀態(充電狀態和螢幕狀態)
2. 從deviceidle.xml裡讀出白名單,然後將白名單設定到了對應的service中
2.2 進入doze模式
2.2.1 light Idle
看程式碼light idle在進入後僅僅限制了網路連結:
//frameworks/base/services/core/java/com/android/server/DeviceIdleController.java
void stepLightIdleStateLocked(String reason) {
switch (mLightState) {
// 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();
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);
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;
}
}
2.2.2 deep idle
在idle下會通過服務例項去設定idle模式從而進入idle:
void stepIdleStateLocked(String reason) {
switch (mState) {
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);
mNextIdleDelay = Math.min(mNextIdleDelay, mConstants.MAX_IDLE_TIMEOUT);
if (mNextIdleDelay < mConstants.IDLE_TIMEOUT) {
mNextIdleDelay = mConstants.IDLE_TIMEOUT;
}
mState = STATE_IDLE;
if (mLightState != LIGHT_STATE_OVERRIDE) {
mLightState = LIGHT_STATE_OVERRIDE;
cancelLightAlarmLocked();
}
EventLogTags.writeDeviceIdle(mState, reason);
addEvent(EVENT_DEEP_IDLE);
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();
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;
EventLogTags.writeDeviceIdle(mState, reason);
addEvent(EVENT_DEEP_MAINTENANCE);
mHandler.sendEmptyMessage(MSG_REPORT_IDLE_OFF);
break;
}
}
2.2.3 DIC-狀態變化因素
(1)screen 狀態
(2)充電狀態(charging)
(3)AnyMonitionDetetor 監測到的狀態,其作用區間為進入STATE_SENSING開始到離開STATE_LOCATING。
(4)SignificantMotionSensor監測狀態,其作用區間為進入STATE_IDLE_PENDING開始,到重新進入ACTIVE或者INACTIVE狀態
(5)外部模組直接呼叫exitIdle
(6)使用dumpsys 的force-idle
(7)控制IDLE功能的總開關:config.xml:config_enableAutoPowerModes,預設為false
(8)當檢測到線控耳機按鈕事件(voice-search)時,會退出idle模式,實際還是呼叫DIC的exitIdle方法實現。
三、Doze模式的影響
3.1 DIC-Wakelock限制
3.1 DIC-網路限制
3.1 DIC-alarm限制
3.1 DIC-jobservice限制
四、注意事項
1.白名單設定方式:
2.應用開發驗證:
在Doze時測試APP
1 .系統版本大於等於6.0
2 . 連線USB,執行被測app,保持app在活動狀態
3 . 關閉裝置螢幕
4 . 通過下面的adb命令強制系統進入Doze模式
$ adb shell dumpsys battery unplug
$ adb shell dumpsys deviceidle step
5. 觀察app表現行為是否有需優化改進的地方。
測試App Standby模式
1. 執行以下adb命令迫使應用程式進入App Standby模式:
$ adb shell dumpsys battery unplug
$ adb shell am set-inactive <packageName> true
2. 模擬喚醒你的應用程式使用以下命令:
$ adb shell am set-inactive <packageName> false
$ adb shell am get-inactive <packageName>
3. 觀察你的App,確保應用程式恢復正常從待機模式過程中,App的通知及其背部活動能達到預期結果。
應用開發時需要驗證doze模式下或者退出doze模式後行為是否正常:
3.Doze和App Standby的區別
Doze模式需要螢幕關閉(通常晚上睡覺或長時間螢幕關閉才會進入),而App Standby不需要螢幕關閉,App進入後臺一段時間也會受到連線網路等限制。
觸發以下任意條件退出App Standby狀態:
1. 使用者主動啟動該App;
2. 該App當前有一個前臺程序(或包含一個活動的前臺服務,或被另一個activity或前臺service使用);
3. App生成一個使用者所能在鎖屏或通知托盤看到的Notification, 而當用戶裝置插入電源時,系統將會釋放App的待機狀態,允許他們自由的連線網路及其執行未完成的工作和同步。如果裝置空閒很長一段時間,系統將允許空閒App一天一次訪問網路。