PowerManagerService之手動滅屏流程示例分析
前言
PowerManagerService之亮屏流程分析 分析了亮屏的流程,并歸納出了一個適用于亮屏或滅屏的通用的流程。 但是,滅屏流程還有一些獨特的東西,例如 dream 這個晦澀的東西,就是在滅屏流程中啟動的。
滅屏分為手動滅屏和自動滅屏,手動滅屏一般就是指 Power 鍵滅屏,自動滅屏就是大家常說的屏幕超時而導(dǎo)致的滅屏。本文以 Power 鍵滅屏為例來分析手動滅屏,自動滅屏留到下一篇文章分析。
本文以 PowerManagerService之亮屏流程分析 作為基礎(chǔ),重復(fù)內(nèi)容不做過多分析,希望讀者務(wù)必先打好基礎(chǔ),再來閱讀本文。
1. 滅屏流程
Power 鍵滅屏?xí){(diào)用 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 表示設(shè)備處于何種狀態(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)寫得很詳細,但是有些概念你可能還真不知道,我獻丑來描述下這四種設(shè)備狀態(tài)。
- WAKEFULNESS_ASLEEP : 表示設(shè)備處于休眠狀態(tài)。屏幕處于滅屏狀態(tài),或者處于滅屏的過程中。設(shè)備只能被 wakeup() 喚醒,例如 Power 鍵喚醒設(shè)備。
- WAKEFULNESS_AWAKE :表示設(shè)備處于喚醒狀態(tài)。屏幕處于亮屏或者暗屏的狀態(tài)。當(dāng)用戶行為超時(屏幕超時),設(shè)備可能會開啟夢境(指屏保)或者進入休眠狀態(tài)。當(dāng)然 goToSleep() 也能使設(shè)備進入休眠狀態(tài)。
- WAKEFULNESS_DREAMING :設(shè)備處于夢境狀態(tài),這里指的是顯示屏保。可以通過 wakeup() 結(jié)束屏保,喚醒設(shè)備,例如點擊屏保。 也可以通過 goToSleep() 結(jié)束屏保,使設(shè)備進入休眠,例如,屏保時按 Power 鍵。
- WAKEFULNESS_DOZING : 設(shè)備處于打盹狀態(tài)(dozing)。這種狀態(tài)幾乎是一種休眠狀態(tài),但是屏幕處于一種低功耗狀態(tài)。系統(tǒng)會讓 dream manager 啟動一個 doze 組件,這個組件會繪制一些簡單的信息在屏幕上,但是前提是屏幕要支持 AOD(always on display)功能。我曾經(jīng)買了一款 OLED 屏的手機,當(dāng)關(guān)閉屏幕時,屏幕會顯示時間、通知圖標(biāo),等等一些信息,當(dāng)時就覺得相當(dāng)炫酷,其實這就是 AOD 功能的實現(xiàn)。如果 doze 組件啟動失敗,那么設(shè)備就進入休眠狀態(tài)??梢酝ㄟ^ wakeup() 結(jié)束這個打盹狀態(tài),使設(shè)備處于喚醒狀態(tài),例如 Power 鍵亮屏。
好,回歸正題,剛剛我們分析到,當(dāng) Power 鍵滅屏的時候,根據(jù)參數(shù) flags 不同,wakefulness 可能會更新為 WAKEFULNESS_DOZING 或 WAKEFULNESS_ASLEEP,也就是設(shè)備進入打盹或者休眠狀態(tài)。
設(shè)備進入休眠狀態(tài)的過程與前面一篇文章分析的設(shè)備亮屏的過程是一致的,并且流程上更簡單,請讀者自行分析,本文分析設(shè)備進入打盹狀態(tài)的流程。
2. 設(shè)備進入打盹狀態(tài)
根據(jù)剛才的分析,wakefulness 更新為 WAKEFULNESS_DOZING,使設(shè)備進入打盹狀態(tài)。并且根據(jù)前面一篇文章可知,此時 mDirty 設(shè)置了 DIRTY_DISPLAY_GROUP_WAKEFULNESS 和 DIRTY_WAKEFULNESS 標(biāo)記位,分別表示顯示屏分組的 wakefulness 改變 和 PowerManagerService 的 wakefulness 改變,其實就是表示設(shè)備狀態(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 鍵滅屏流程相關(guān)步驟如下
- 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 結(jié)束
// ...
// 3. 定時更新電源狀態(tài)
// 這一步?jīng)Q定自動滅屏
if (hasUserActivitySummary && nextTimeout >= 0) {
scheduleUserInactivityTimeout(nextTimeout);
}
}
更新用戶行為,其實就是 DisplayGroupPowerStateMapper 保存用戶行為。現(xiàn)在我們分析的情況是,Power 鍵導(dǎo)致屏幕由亮到滅的過程,因此這里獲取的用戶行為是 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)的標(biāo)志是,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 設(shè)置為 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)在的分析結(jié)果是屏幕狀態(tài)繼續(xù)保持不變。你沒有看錯,我也沒有寫錯。剛剛我們還提到,現(xiàn)在設(shè)備還沒有正式處于打盹狀態(tài),因為還沒有啟動 doze dream。當(dāng)成功啟動 doze dream 后,dream manager 會獲取喚醒鎖 PowerManager.DOZE_WAKE_LOCK,此時 PowerManagerService 再次更新電源狀態(tài)時,策略就會更新為 DisplayPowerRequest.POLICY_DOZE,它會讓屏幕進入一個doze狀態(tài),并開啟 AOD 功能。
如果你不想在滅屏?xí)r暫時保持屏幕狀態(tài)不變,可以把 mDozeAfterScreenOff 設(shè)置為 true,這會導(dǎo)致請求策略更新為 DisplayPowerRequest.POLICY_OFF,也就是立即讓屏幕進入休眠。
2.3 更新夢境狀態(tài)
我們一直在強調(diào),設(shè)備真正進入打盹,是在啟動 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()) {
// 最終調(diào)用 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組件自己結(jié)束夢境,進入更新 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 時,也就是設(shè)備處于休眠狀態(tài)時,請求策略不受任何因素影響,直接為DisplayPowerRequest.POLICY_OFF,它會使屏幕休眠。
3. 啟動doze dream
現(xiàn)在我們離設(shè)備正式進入打盹狀態(tài),只有一步之遙,而這一步就是調(diào)用 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 組件,但是沒關(guān)系,經(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,因此當(dāng)服務(wù)成功連接上,會調(diào)用 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回調(diào),用于接收結(jié)果
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 后,會調(diào)用 DreamService#attach() ,然后發(fā)送 dream 啟動的廣播 Intent.ACTION_DREAMING_STARTED。
這里要結(jié)合具體的 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();
// 設(shè)置為無窗口模式
setWindowless(true);
mPluginManager.addPluginListener(this, DozeServicePlugin.class, false /* allowMultiple */);
DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
mDozeMachine = dozeComponent.getDozeMachine();
}
}
注意,它設(shè)置了一個無窗口模式,后面會用到。
剛才分析過,DreamService 綁定成功后,會調(diào)用 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 {
// 無窗口下,直接調(diào)用 onDreamingStarted()
mDispatchAfterOnAttachedToWindow.run();
}
}
對于 SystemUI 的 DozeService,它是無窗口的,因此直接調(diào)用了它的 onDreamingStarted() 方法
// DozeService.java
public void onDreamingStarted() {
super.onDreamingStarted();
mDozeMachine.requestState(DozeMachine.State.INITIALIZED);
// 調(diào)用父類的方法
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,這就是設(shè)備進入打盹狀態(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,這會導(dǎo)致屏幕進入 doze 狀態(tài),這是一種幾乎休眠的狀態(tài),也是一種低功耗狀態(tài),并且開啟了 AOD 功能,當(dāng)然前提是屏幕支持這個功能,據(jù)說只有 OLED 屏支持 AOD 功能。
此時,設(shè)備才真正的處于了打盹狀態(tài)。而 PowerManagerService 更新電源狀態(tài)的后續(xù)過程,我覺得就不重要了,留給讀者自行分析。
結(jié)束
本文分析了滅屏過程,設(shè)備如何進入打盹狀態(tài),可謂是波瀾起伏。但是注意,這只是手動滅屏。而我們平常看到了屏幕超時導(dǎo)致的自動滅屏,下一篇文章繼續(xù)分析。
在本文中,我們還提到設(shè)備有一種狀態(tài),夢境狀態(tài),其實指的就是屏保。你說它沒用吧,有時候還是有點作用的,你說它有用吧,現(xiàn)在幾乎看不到用它的地方。對于這種食之無味,棄之可惜的東西,要不要分析呢,我還在猶豫。
以上就是PowerManagerService之手動滅屏的詳細內(nèi)容,更多關(guān)于PowerManagerService 滅屏的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程設(shè)計模式之責(zé)任鏈模式詳解
這篇文章主要介紹了Android編程設(shè)計模式之責(zé)任鏈模式,詳細分析了Android設(shè)計模式中責(zé)任鏈模式的概念、原理、應(yīng)用場景、使用方法及相關(guān)操作技巧,需要的朋友可以參考下2017-12-12
Android 優(yōu)雅的實現(xiàn)通用格式化編輯
這篇文章主要介紹了Android 優(yōu)雅的實現(xiàn)通用格式化編輯,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
Android編程實現(xiàn)ImageView圖片拋物線動畫效果的方法
這篇文章主要介紹了Android編程實現(xiàn)ImageView圖片拋物線動畫效果的方法,實例分析了Android實現(xiàn)拋物線運動的算法原理與相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
Android 應(yīng)用的歡迎界面實現(xiàn)代碼
本文主要介紹Android 應(yīng)用歡迎界面的開發(fā),這里提供實現(xiàn)方法和實現(xiàn)代碼以供大家參考,有需要的朋友可以參考下2016-07-07
android 九宮格滑動解鎖開機實例源碼學(xué)習(xí)
開機密碼的樣式種類多種多樣,五花八門.本文接下來介紹滑動九宮格來達到開機目的,感興趣的朋友可以了解下2013-01-01

