PowerManagerService之手動滅屏流程示例分析
前言
PowerManagerService之亮屏流程分析 分析了亮屏的流程,并歸納出了一個適用于亮屏或滅屏的通用的流程。 但是,滅屏流程還有一些獨特的東西,例如 dream 這個晦澀的東西,就是在滅屏流程中啟動的。
滅屏分為手動滅屏和自動滅屏,手動滅屏一般就是指 Power 鍵滅屏,自動滅屏就是大家常說的屏幕超時而導致的滅屏。本文以 Power 鍵滅屏為例來分析手動滅屏,自動滅屏留到下一篇文章分析。
本文以 PowerManagerService之亮屏流程分析 作為基礎,重復內容不做過多分析,希望讀者務必先打好基礎,再來閱讀本文。
1. 滅屏流程
Power 鍵滅屏會調用 PowerManagerService#goToSleep()
// PowerManagerService.java @Override // Binder call public void goToSleep(long eventTime, int reason, int flags) { // ... try { sleepDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, flags, uid); } } private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags, int uid) { synchronized (mLock) { // 1. 更新 wakefulness if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) { // 2. 更新電源狀態(tài) updatePowerStateLocked(); } } }
很熟悉的流程,先更新 wakefulness ,再更新電源狀態(tài)。
不過,Power 鍵滅屏的更新 wakefulness 過程,有點小插曲,如下
private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason, int flags, int uid) { // ... try { // ... // 召喚睡夢精靈,其實就是保存一個狀態(tài) mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true); // 更新 wakefulness 為 WAKEFULNESS_DOZING setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason, /* opUid= */ 0, /* opPackageName= */ null, /* details= */ null); // 如果 flags 帶有 PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE,更新 wakefulness 為 WAKEFULNESS_ASLEEP if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) { reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid); } } return true; }
默認地,wakefulness 更新為 WAKEFULNESS_DOZING,然而,如果參數(shù) flags 帶有 PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE,wakefulness 更新為 WAKEFULNESS_ASLEEP。
wakefulness 表示設備處于何種狀態(tài),其實它有四個值,如下
public abstract class PowerManagerInternal { /** * Wakefulness: The device is asleep. It can only be awoken by a call to wakeUp(). * The screen should be off or in the process of being turned off by the display controller. * The device typically passes through the dozing state first. */ public static final int WAKEFULNESS_ASLEEP = 0; /** * Wakefulness: The device is fully awake. It can be put to sleep by a call to goToSleep(). * When the user activity timeout expires, the device may start dreaming or go to sleep. */ public static final int WAKEFULNESS_AWAKE = 1; /** * Wakefulness: The device is dreaming. It can be awoken by a call to wakeUp(), * which ends the dream. The device goes to sleep when goToSleep() is called, when * the dream ends or when unplugged. * User activity may brighten the screen but does not end the dream. */ public static final int WAKEFULNESS_DREAMING = 2; /** * Wakefulness: The device is dozing. It is almost asleep but is allowing a special * low-power "doze" dream to run which keeps the display on but lets the application * processor be suspended. It can be awoken by a call to wakeUp() which ends the dream. * The device fully goes to sleep if the dream cannot be started or ends on its own. */ public static final int WAKEFULNESS_DOZING = 3; }
雖然注釋已經(jīng)寫得很詳細,但是有些概念你可能還真不知道,我獻丑來描述下這四種設備狀態(tài)。
- WAKEFULNESS_ASLEEP : 表示設備處于休眠狀態(tài)。屏幕處于滅屏狀態(tài),或者處于滅屏的過程中。設備只能被 wakeup() 喚醒,例如 Power 鍵喚醒設備。
- WAKEFULNESS_AWAKE :表示設備處于喚醒狀態(tài)。屏幕處于亮屏或者暗屏的狀態(tài)。當用戶行為超時(屏幕超時),設備可能會開啟夢境(指屏保)或者進入休眠狀態(tài)。當然 goToSleep() 也能使設備進入休眠狀態(tài)。
- WAKEFULNESS_DREAMING :設備處于夢境狀態(tài),這里指的是顯示屏保。可以通過 wakeup() 結束屏保,喚醒設備,例如點擊屏保。 也可以通過 goToSleep() 結束屏保,使設備進入休眠,例如,屏保時按 Power 鍵。
- WAKEFULNESS_DOZING : 設備處于打盹狀態(tài)(dozing)。這種狀態(tài)幾乎是一種休眠狀態(tài),但是屏幕處于一種低功耗狀態(tài)。系統(tǒng)會讓 dream manager 啟動一個 doze 組件,這個組件會繪制一些簡單的信息在屏幕上,但是前提是屏幕要支持 AOD(always on display)功能。我曾經(jīng)買了一款 OLED 屏的手機,當關閉屏幕時,屏幕會顯示時間、通知圖標,等等一些信息,當時就覺得相當炫酷,其實這就是 AOD 功能的實現(xiàn)。如果 doze 組件啟動失敗,那么設備就進入休眠狀態(tài)??梢酝ㄟ^ wakeup() 結束這個打盹狀態(tài),使設備處于喚醒狀態(tài),例如 Power 鍵亮屏。
好,回歸正題,剛剛我們分析到,當 Power 鍵滅屏的時候,根據(jù)參數(shù) flags 不同,wakefulness 可能會更新為 WAKEFULNESS_DOZING 或 WAKEFULNESS_ASLEEP,也就是設備進入打盹或者休眠狀態(tài)。
設備進入休眠狀態(tài)的過程與前面一篇文章分析的設備亮屏的過程是一致的,并且流程上更簡單,請讀者自行分析,本文分析設備進入打盹狀態(tài)的流程。
2. 設備進入打盹狀態(tài)
根據(jù)剛才的分析,wakefulness 更新為 WAKEFULNESS_DOZING,使設備進入打盹狀態(tài)。并且根據(jù)前面一篇文章可知,此時 mDirty 設置了 DIRTY_DISPLAY_GROUP_WAKEFULNESS 和 DIRTY_WAKEFULNESS 標記位,分別表示顯示屏分組的 wakefulness 改變 和 PowerManagerService 的 wakefulness 改變,其實就是表示設備狀態(tài)改變。
現(xiàn)在根據(jù) mDirty 來更新電源狀態(tài)
// PowerManagerService.java private void updatePowerStateLocked() { if (!mSystemReady || mDirty == 0) { return; } if (!Thread.holdsLock(mLock)) { Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked"); } Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState"); try { // Phase 0: Basic state updates. updateIsPoweredLocked(mDirty); updateStayOnLocked(mDirty); updateScreenBrightnessBoostLocked(mDirty); // Phase 1: Update wakefulness. // Loop because the wake lock and user activity computations are influenced // by changes in wakefulness. final long now = mClock.uptimeMillis(); int dirtyPhase2 = 0; for (;;) { int dirtyPhase1 = mDirty; dirtyPhase2 |= dirtyPhase1; mDirty = 0; updateWakeLockSummaryLocked(dirtyPhase1); updateUserActivitySummaryLocked(now, dirtyPhase1); updateAttentiveStateLocked(now, dirtyPhase1); if (!updateWakefulnessLocked(dirtyPhase1)) { break; } } // Phase 2: Lock profiles that became inactive/not kept awake. updateProfilesLocked(now); // Phase 3: Update display power state. final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2); // Phase 4: Update dream state (depends on display ready signal). updateDreamLocked(dirtyPhase2, displayBecameReady); // Phase 5: Send notifications, if needed. finishWakefulnessChangeIfNeededLocked(); // Phase 6: Update suspend blocker. // Because we might release the last suspend blocker here, we need to make sure // we finished everything else first! updateSuspendBlockerLocked(); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } }
根據(jù)前面文章可知,這里包含了很多功能,但是與 Power 鍵滅屏流程相關步驟如下
- updateUserActivitySummaryLocked() 更新用戶行為。屏幕請求策略的影響之一的因素就是用戶行為。詳見【2.1 更新用戶行為】
- updateDisplayPowerStateLocked() 更新顯示屏的電源狀態(tài),它會對 DisplayManagerService 發(fā)起電源請求,從而決定屏幕屏的狀態(tài),例如亮、滅、暗,等等。詳見【2.2 更新顯示屏的電源狀態(tài)】
- updateDreamLocked() 更新夢境狀態(tài),其實就是通過 dream manager 啟動 doze 組件,然后更新 PowerManagerService 的夢境狀態(tài)。詳見【2.3 更新夢境狀態(tài)】
屏保和 doze 組件都是由 dream manager 啟動的,在 PowerManagerService 中,屏保被稱為 dream,doze 組件稱為 doze dream。
2.1 更新用戶行為
// PowerManagerService.java private void updateUserActivitySummaryLocked(long now, int dirty) { // ... // 遍歷 display group id for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { int groupUserActivitySummary = 0; long groupNextTimeout = 0; // 注意,休眠狀態(tài)是無法決定用戶行為的 if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != WAKEFULNESS_ASLEEP) { final long lastUserActivityTime = mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId); final long lastUserActivityTimeNoChangeLights = mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked( groupId); // 1. 獲取用戶行為與超時時間 // 上一次用戶行為的時間 >= 上一次喚醒屏幕的時間 if (lastUserActivityTime >= mLastWakeTime) { groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration; if (now < groupNextTimeout) { // 沒有到 dim 時間 groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT; } else { groupNextTimeout = lastUserActivityTime + screenOffTimeout; if (now < groupNextTimeout) { // 處于 dim 時間段 groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM; } } } // ... } // 2. DisplayGroupPowerStateMapper 保存用戶行為 mDisplayGroupPowerStateMapper.setUserActivitySummaryLocked(groupId, groupUserActivitySummary); } } // 遍歷 display group id 結束 // ... // 3. 定時更新電源狀態(tài) // 這一步?jīng)Q定自動滅屏 if (hasUserActivitySummary && nextTimeout >= 0) { scheduleUserInactivityTimeout(nextTimeout); } }
更新用戶行為,其實就是 DisplayGroupPowerStateMapper 保存用戶行為。現(xiàn)在我們分析的情況是,Power 鍵導致屏幕由亮到滅的過程,因此這里獲取的用戶行為是 USER_ACTIVITY_SCREEN_BRIGHT。如果是由暗到滅的過程,那么用戶行為是 USER_ACTIVITY_SCREEN_DIM。
那么,你是否有疑惑,系統(tǒng)正在進入打盹狀態(tài),為何用戶行為是 USER_ACTIVITY_SCREEN_BRIGHT 或 USER_ACTIVITY_SCREEN_DIM,它們并不是滅屏的用戶行為。因為系統(tǒng)根本沒有定義滅屏的用戶行為。
所有的用戶行為如下
private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0; private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1; private static final int USER_ACTIVITY_SCREEN_DREAM = 1 << 2;
用戶行為只有三種,屏幕變亮,變暗,或者進入屏保,根本沒有打盹的用戶行為。那么為何是這樣呢?
雖然此時 wakefulness 為 WAKEFULNESS_DOZING,只是表示正在進入打盹狀態(tài),實際上還沒有正式處于打盹狀態(tài)。系統(tǒng)真正進入打盹狀態(tài)的標志是,dream manager 成功啟動 doze 組件,并且獲取到喚醒鎖 PowerManager.DOZE_WAKE_LOCK。因此,不好定義一個打盹的用戶行為。
2.2 更新顯示屏的電源狀態(tài)
根據(jù)前篇文章可知,更新顯示屏的電源狀態(tài),其實就是向 DisplayManagerService 發(fā)起請求,并且請求策略才是真正決定屏幕狀態(tài)(亮、滅、暗,等等)。
請求策略的獲取方式如下
// PowerManagerService.java int getDesiredScreenPolicyLocked(int groupId) { final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId); if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { return DisplayPowerRequest.POLICY_OFF; } else if (wakefulness == WAKEFULNESS_DOZING) { // 如下的條件表示dream manager獲取了 PowerManager.DOZE_WAKE_LOCK 喚醒鎖 if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) { return DisplayPowerRequest.POLICY_DOZE; } // mDozeAfterScreenOff 默認為 false // 不過,可以被 SystemUI 設置為 true,表示屏幕先滅屏,再進入doze狀態(tài) if (mDozeAfterScreenOff) { return DisplayPowerRequest.POLICY_OFF; } } if (mIsVrModeEnabled) { return DisplayPowerRequest.POLICY_VR; } if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 || !mBootCompleted || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & USER_ACTIVITY_SCREEN_BRIGHT) != 0 || mScreenBrightnessBoostInProgress) { return DisplayPowerRequest.POLICY_BRIGHT; } return DisplayPowerRequest.POLICY_DIM; }
雖然現(xiàn)在的 wakefulness 為 WAKEFULNESS_DOZING,但是此時決定請求策略的仍然是用戶行為。而根據(jù)剛剛的分析,此時用戶行為是 USER_ACTIVITY_SCREEN_BRIGHT 或 USER_ACTIVITY_SCREEN_DIM,因此策略為 DisplayPowerRequest.POLICY_BRIGHT 或 DisplayPowerRequest.POLICY_DIM。也就是說屏幕狀態(tài)繼續(xù)保持不變。
看到這里,我和我的小伙伴都驚呆了!現(xiàn)在的狀況明明是 Power 鍵滅屏,為何現(xiàn)在的分析結果是屏幕狀態(tài)繼續(xù)保持不變。你沒有看錯,我也沒有寫錯。剛剛我們還提到,現(xiàn)在設備還沒有正式處于打盹狀態(tài),因為還沒有啟動 doze dream。當成功啟動 doze dream 后,dream manager 會獲取喚醒鎖 PowerManager.DOZE_WAKE_LOCK,此時 PowerManagerService 再次更新電源狀態(tài)時,策略就會更新為 DisplayPowerRequest.POLICY_DOZE,它會讓屏幕進入一個doze狀態(tài),并開啟 AOD 功能。
如果你不想在滅屏時暫時保持屏幕狀態(tài)不變,可以把 mDozeAfterScreenOff 設置為 true,這會導致請求策略更新為 DisplayPowerRequest.POLICY_OFF,也就是立即讓屏幕進入休眠。
2.3 更新夢境狀態(tài)
我們一直在強調,設備真正進入打盹,是在啟動 doze dream 之后。doze dream 的啟動過程是在更新夢境狀態(tài)的過程中
// PowerManagerService.java private void updateDreamLocked(int dirty, boolean displayBecameReady) { if ((dirty & (DIRTY_WAKEFULNESS | DIRTY_USER_ACTIVITY | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_ATTENTIVE | DIRTY_WAKE_LOCKS | DIRTY_BOOT_COMPLETED | DIRTY_SETTINGS | DIRTY_IS_POWERED | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE | DIRTY_BATTERY_STATE)) != 0 || displayBecameReady) { if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { // 最終調用 handleSandman() scheduleSandmanLocked(); } } } private void handleSandman(int groupId) { // runs on handler thread // Handle preconditions. final boolean startDreaming; final int wakefulness; synchronized (mLock) { mSandmanScheduled = false; final int[] ids = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); if (!ArrayUtils.contains(ids, groupId)) { // Group has been removed. return; } wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); if ((wakefulness == WAKEFULNESS_DREAMING || wakefulness == WAKEFULNESS_DOZING) && mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId) && mDisplayGroupPowerStateMapper.isReady(groupId)) { // 1. 決定是否能啟動夢境 // canDreamLocked() 表示是否啟動夢境中的屏保 // canDozeLocked() 表示是否啟動夢境中的doze組件,判斷條件就是 wakefulness 為 WAKEFULNESS_DOZING startDreaming = canDreamLocked(groupId) || canDozeLocked(); // 重置"召喚睡夢精靈"狀態(tài) mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, false); } else { startDreaming = false; } } final boolean isDreaming; if (mDreamManager != null) { // Restart the dream whenever the sandman is summoned. if (startDreaming) { mDreamManager.stopDream(false /*immediate*/); // 2. 啟動夢境 doze 組件 mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING); } // 判斷是否正在啟動中 isDreaming = mDreamManager.isDreaming(); } else { isDreaming = false; } mDozeStartInProgress = false; synchronized (mLock) { final int[] ids = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); if (!ArrayUtils.contains(ids, groupId)) { return; } if (startDreaming && isDreaming) { // ... } if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId) || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != wakefulness) { return; // wait for next cycle } // Determine whether the dream should continue. long now = mClock.uptimeMillis(); if (wakefulness == WAKEFULNESS_DREAMING) { // ... } else if (wakefulness == WAKEFULNESS_DOZING) { // 3. 正在啟動夢境的doze組件,那繼續(xù) if (isDreaming) { return; // continue dozing } // 4. 沒有啟動成功,或者doze組件自己結束夢境,進入更新 wakefulness 為 WAKEFULNESS_ASLEEP 流程 // Doze has ended or will be stopped. Update the power state. reallySleepDisplayGroupNoUpdateLocked(groupId, now, Process.SYSTEM_UID); updatePowerStateLocked(); } } // ... }
更新夢境狀態(tài)過程如下
- 判斷是否能進入夢境。夢境其實有兩種功能,一種是屏保,另一種是 doze 組件。由于此時 wakefulness 為 WAKEFULNESS_DOZING,因此可以滿足啟動 doze dream 的條件。
- 通過 dream manager 啟動 dream。此時,啟動的就是 doze dream,而不是屏保。
- 如果正在啟動,那就繼續(xù)。繼續(xù)什么呢?詳見【啟動doze dream】
- 如果啟動失敗,進入休眠的流程。
休眠的流程最簡單了,首先更新 wakefulenss 為 WAKEFULNESS_ASLEEP,然后更新請求策略為 DisplayPowerRequest.POLICY_OFF, 這使屏幕直接滅屏。更新請求策略的過程如下
int getDesiredScreenPolicyLocked(int groupId) { final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId); if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { return DisplayPowerRequest.POLICY_OFF; } else if (wakefulness == WAKEFULNESS_DOZING) { // ... } // ... }
可以看到,wakefulness 為 WAKEFULNESS_ASLEEP 時,也就是設備處于休眠狀態(tài)時,請求策略不受任何因素影響,直接為DisplayPowerRequest.POLICY_OFF,它會使屏幕休眠。
3. 啟動doze dream
現(xiàn)在我們離設備正式進入打盹狀態(tài),只有一步之遙,而這一步就是調用 DreamManagerService#startDreamInternal() 啟動 doze dream
// DreamManagerService.java private void startDreamInternal(boolean doze) { final int userId = ActivityManager.getCurrentUser(); // 獲取 doze dream 或 screensaver final ComponentName dream = chooseDreamForUser(doze, userId); if (dream != null) { synchronized (mLock) { // 啟動 dream,此時啟動的 doze dream, startDreamLocked(dream, false /*isTest*/, doze, userId); } } } private ComponentName chooseDreamForUser(boolean doze, int userId) { if (doze) { ComponentName dozeComponent = getDozeComponent(userId); return validateDream(dozeComponent) ? dozeComponent : null; } // ... } private ComponentName getDozeComponent(int userId) { if (mForceAmbientDisplayEnabled || mDozeConfig.enabled(userId)) { // 從配置文件 config.xml 中獲取 config_dozeComponent return ComponentName.unflattenFromString(mDozeConfig.ambientDisplayComponent()); } else { return null; } }
系統(tǒng)沒有配置 doze dream 組件,但是沒關系,經(jīng)過研究源碼中的一些已經(jīng)實現(xiàn)的 doze 組件,我們可以發(fā)現(xiàn), doze 組件都是繼承自一個名為 DreamService 的 Service,這個 Service 就是四大組件的 Service。
現(xiàn)在繼續(xù)看看啟動 doze 組件的過程
// DreamManagerService.java private void startDreamLocked(final ComponentName name, final boolean isTest, final boolean canDoze, final int userId) { // ... final Binder newToken = new Binder(); mCurrentDreamToken = newToken; mCurrentDreamName = name; mCurrentDreamIsTest = isTest; mCurrentDreamCanDoze = canDoze; mCurrentDreamUserId = userId; PowerManager.WakeLock wakeLock = mPowerManager .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream"); // WakeLock#wrap() 方法,是保證在執(zhí)行 Runnbale 之前獲取鎖,并且執(zhí)行完 Runnable 后釋放鎖。 // 也就是說,保持 CPU 運行,直到啟動 doze dream 完畢。 mHandler.post(wakeLock.wrap(() -> { mAtmInternal.notifyDreamStateChanged(true); if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) { mUiEventLogger.log(DreamManagerEvent.DREAM_START); } // 開啟 dream mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock); })); }
通過 DreamController#startDream() 啟動 doze dream
// DreamController.java public void startDream(Binder token, ComponentName name, boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) { stopDream(true /*immediate*/, "starting new dream"); try { // 其實是發(fā)送 Intent.ACTION_CLOSE_SYSTEM_DIALOGS 廣播 // Close the notification shade. No need to send to all, but better to be explicit. mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL); // 1. 創(chuàng)建一條記錄 dream 記錄 // 注意,DreamRecord 實現(xiàn)了 ServiceConnection mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId, wakeLock); mDreamStartTime = SystemClock.elapsedRealtime(); MetricsLogger.visible(mContext, mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING); Intent intent = new Intent(DreamService.SERVICE_INTERFACE); intent.setComponent(name); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); try { // 2. 連接Service if (!mContext.bindServiceAsUser(intent, mCurrentDream, Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, new UserHandle(userId))) { Slog.e(TAG, "Unable to bind dream service: " + intent); stopDream(true /*immediate*/, "bindService failed"); return; } } catch (SecurityException ex) { Slog.e(TAG, "Unable to bind dream service: " + intent, ex); stopDream(true /*immediate*/, "unable to bind service: SecExp."); return; } mCurrentDream.mBound = true; mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } }
既然 doze dream 是一個 Service,那么啟動它的過程就是綁定 Service 的過程。
由于 DreamRecord 實現(xiàn)了 ServiceConnection,因此當服務成功連接上,會調用 DreamRecord#onServiceConnected()
// DreamRecord.java public void onServiceConnected(ComponentName name, final IBinder service) { mHandler.post(() -> { mConnected = true; if (mCurrentDream == DreamRecord.this && mService == null) { attach(IDreamService.Stub.asInterface(service)); // Wake lock will be released once dreaming starts. } else { releaseWakeLockIfNeeded(); } }); } private void attach(IDreamService service) { try { // DreamRecord 監(jiān)聽 Service 生死 service.asBinder().linkToDeath(mCurrentDream, 0); // 第三個參數(shù)是一個binder回調,用于接收結果 service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze, mCurrentDream.mDreamingStartedCallback); } catch (RemoteException ex) { Slog.e(TAG, "The dream service died unexpectedly.", ex); stopDream(true /*immediate*/, "attach failed"); return; } mCurrentDream.mService = service; // 發(fā)送 Intent.ACTION_DREAMING_STARTED 廣播 if (!mCurrentDream.mIsTest) { mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL); mCurrentDream.mSentStartBroadcast = true; } }
可以看到,成功啟動 Service 后,會調用 DreamService#attach() ,然后發(fā)送 dream 啟動的廣播 Intent.ACTION_DREAMING_STARTED。
這里要結合具體的 doze dream Service 來分析,既然系統(tǒng)沒有配置,那么以 SystemUI 中的 DozeService 為例進行分析,首先看看它的 onCreate() 過程
public class DozeService extends DreamService implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> { @Override public void onCreate() { super.onCreate(); // 設置為無窗口模式 setWindowless(true); mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */); DozeComponent dozeComponent = mDozeComponentBuilder.build(this); mDozeMachine = dozeComponent.getDozeMachine(); } }
注意,它設置了一個無窗口模式,后面會用到。
剛才分析過,DreamService 綁定成功后,會調用 DreamService#attach()
private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) { // ... mDreamToken = dreamToken; mCanDoze = canDoze; // 只有 doze dream 才能無窗口 if (mWindowless && !mCanDoze) { throw new IllegalStateException("Only doze dreams can be windowless"); } mDispatchAfterOnAttachedToWindow = () -> { if (mWindow != null || mWindowless) { mStarted = true; try { // 由子類實現(xiàn) onDreamingStarted(); } finally { try { started.sendResult(null); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } } }; if (!mWindowless) { // ... } else { // 無窗口下,直接調用 onDreamingStarted() mDispatchAfterOnAttachedToWindow.run(); } }
對于 SystemUI 的 DozeService,它是無窗口的,因此直接調用了它的 onDreamingStarted() 方法
// DozeService.java public void onDreamingStarted() { super.onDreamingStarted(); mDozeMachine.requestState(DozeMachine.State.INITIALIZED); // 調用父類的方法 startDozing(); if (mDozePlugin != null) { mDozePlugin.onDreamingStarted(); } }
再次進入 DreamService#startDozing()
public void startDozing() { if (mCanDoze && !mDozing) { mDozing = true; updateDoze(); } } private void updateDoze() { if (mDreamToken == null) { Slog.w(TAG, "Updating doze without a dream token."); return; } if (mDozing) { try { // 通知 dream mananger,service 正在啟動 doze 功能 mDreamManager.startDozing(mDreamToken, mDozeScreenState, mDozeScreenBrightness); } catch (RemoteException ex) { // system server died } } }
最終,把啟動 doze 的信息返回給 DreamManagerService
// DreamManagerService.java private void startDozingInternal(IBinder token, int screenState, int screenBrightness) { synchronized (mLock) { if (mCurrentDreamToken == token && mCurrentDreamCanDoze) { mCurrentDreamDozeScreenState = screenState; mCurrentDreamDozeScreenBrightness = screenBrightness; // 1. 通知PowerManagerService,保存doze狀態(tài)下的屏幕狀態(tài)和屏幕亮度 mPowerManagerInternal.setDozeOverrideFromDreamManager( screenState, screenBrightness); if (!mCurrentDreamIsDozing) { mCurrentDreamIsDozing = true; //2. 獲取 PowerManager.DOZE_WAKE_LOCK 喚醒鎖 mDozeWakeLock.acquire(); } } } }
DreamManagerService 又把信息傳遞給了 PowerManagerService,這些信息包括 doze 下的屏幕狀態(tài),以及屏幕亮度,而PowerManagerService 也只是簡單保存了這些信息。
然后獲取了一個 PowerManager.DOZE_WAKE_LOCK,這就是設備進入打盹狀態(tài)的最重要的一步。它會影響屏幕請求策略,如下
// PowerManagerService.java int getDesiredScreenPolicyLocked(int groupId) { final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId); if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { return DisplayPowerRequest.POLICY_OFF; } else if (wakefulness == WAKEFULNESS_DOZING) { // 如下的條件表示dream manager獲取了 PowerManager.DOZE_WAKE_LOCK 喚醒鎖 if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) { return DisplayPowerRequest.POLICY_DOZE; } if (mDozeAfterScreenOff) { return DisplayPowerRequest.POLICY_OFF; } } // ... }
可以看到 DreamManagerService 成功獲取到 PowerManager.DOZE_WAKE_LOCK 喚醒鎖后,請求策略現(xiàn)在為 DisplayPowerRequest.POLICY_DOZE,這會導致屏幕進入 doze 狀態(tài),這是一種幾乎休眠的狀態(tài),也是一種低功耗狀態(tài),并且開啟了 AOD 功能,當然前提是屏幕支持這個功能,據(jù)說只有 OLED 屏支持 AOD 功能。
此時,設備才真正的處于了打盹狀態(tài)。而 PowerManagerService 更新電源狀態(tài)的后續(xù)過程,我覺得就不重要了,留給讀者自行分析。
結束
本文分析了滅屏過程,設備如何進入打盹狀態(tài),可謂是波瀾起伏。但是注意,這只是手動滅屏。而我們平??吹搅似聊怀瑫r導致的自動滅屏,下一篇文章繼續(xù)分析。
在本文中,我們還提到設備有一種狀態(tài),夢境狀態(tài),其實指的就是屏保。你說它沒用吧,有時候還是有點作用的,你說它有用吧,現(xiàn)在幾乎看不到用它的地方。對于這種食之無味,棄之可惜的東西,要不要分析呢,我還在猶豫。
以上就是PowerManagerService之手動滅屏的詳細內容,更多關于PowerManagerService 滅屏的資料請關注腳本之家其它相關文章!
相關文章
Android 優(yōu)雅的實現(xiàn)通用格式化編輯
這篇文章主要介紹了Android 優(yōu)雅的實現(xiàn)通用格式化編輯,幫助大家更好的理解和學習使用Android,感興趣的朋友可以了解下2021-03-03Android編程實現(xiàn)ImageView圖片拋物線動畫效果的方法
這篇文章主要介紹了Android編程實現(xiàn)ImageView圖片拋物線動畫效果的方法,實例分析了Android實現(xiàn)拋物線運動的算法原理與相關技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10