PowerManagerService之喚醒鎖的使用獲取創(chuàng)建示例解析
前言
在開發(fā)中,或多或少會使用喚醒鎖(wake lock),有的是為了保持屏幕長亮,有的是為了保持 CPU 運行。
喚醒鎖的本質,其實是對屏幕狀態(tài)的控制,以及對 CPU 掛起的控制。
屏幕狀態(tài)的控制,指的是保持屏幕處于點亮的狀態(tài),或者直接喚醒屏幕,或者延長亮屏時間。
CPU 掛起的控制,指的是否阻止 CPU 掛起,如果阻止了 CPU 掛起,其實就是保持 CPU 運行。
本文重點分析喚醒鎖是如何實現(xiàn)對屏幕狀態(tài)的控制,以及對 CPU 掛起的控制。
本文仍以前面的三篇文章為基礎,重復的過程不會分析,只會簡要概述,因此請讀者務必仔細閱讀如下三篇文章
使用喚醒鎖
首先介紹下如何使用喚醒鎖,如下
PowerManager pm = mContext.getSystemService(PowerManager.class); // 1. 創(chuàng)建喚醒鎖 // 保持屏幕處于點亮狀態(tài),但是允許變暗 PowerManager.WakeLock wl = pm.newWakeLock( PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG); // 2. 獲取喚醒鎖 wl.acquire(); // ... 執(zhí)行任務 ... // 3. 釋放喚醒鎖 wl.release();
使用喚醒鎖的步驟為
- 創(chuàng)建喚醒鎖
- 獲取喚醒鎖
- 在不需要喚醒鎖的時候,釋放它。
注意,使用喚醒時,還需要在 AndroidManifest.xml 中聲明權限 android.Manifest.permission.WAKE_LOCK。
創(chuàng)建喚醒鎖
首先介紹下創(chuàng)建喚醒鎖的API
// PowerManager.java public WakeLock newWakeLock(int levelAndFlags, String tag) { validateWakeLockParameters(levelAndFlags, tag); return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(), Display.INVALID_DISPLAY); }
參數(shù) levelAndFlags 是由 level 和 flag 以按位或的方式組成,其中必須指定一個 level,但是 flag 是可選的。
第三方 app 能使用的 level 有如下幾個
level | 描述 |
---|---|
PARTIAL_WAKE_LOCK | 保證 CPU 運行,但是屏幕和鍵盤背光可以關閉 |
FULL_WAKE_LOCK | 保證屏幕和鍵盤背光處于最大亮度 |
SCREEN_DIM_WAKE_LOCK | 確保屏幕處于點亮狀態(tài),但是可以變暗,鍵盤背光允許關閉 |
SCREEN_BRIGHT_WAKE_LOCK | 確認屏幕處于最大亮度,但是鍵盤背光允許關閉 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK | 當距離傳感器檢測到物體靠近時,滅屏,檢測到物體遠離時,點亮屏幕 |
注意,F(xiàn)ULL_WAKE_LOCK 、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK 不僅會使屏幕處于點亮狀態(tài),同時也會保持 CPU 處于運行狀態(tài),我們將在后面的分析得到驗證。
第三方 app 能使用的 flag 有如下幾個
flag | 描述 |
---|---|
ACQUIRE_CAUSES_WAKEUP | 當喚醒鎖被獲取時,點亮屏幕 |
ON_AFTER_RELEASE | 當喚醒鎖被釋放時,如果屏幕處于點亮的狀態(tài),那么延長亮屏的時間 |
注意,ACQUIRE_CAUSES_WAKEUP 和 ON_AFTER_RELEASE 要配合屏幕喚醒鎖 FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK, SCREEN_DIM_WAKE_LOCK一起使用。我們將在后面的分析得到驗證。
這里介紹的 level 和 flag 只適用于第三方 app 使用,其實系統(tǒng)還定義了一些,用于完成特殊的功能。
參數(shù) tag,名字其實可以隨意,但是官方說,最好以 app:mytag
的方式命名,例如 gmail:mytag
。
參數(shù)介紹完了,我現(xiàn)在想提另外一個話題,與多屏相關。 不知從何時起,Android 把多屏進行了分組,內置的屏幕是在默認的分組中。PowerManager#newWakeLock(int levelAndFlags, String tag) 這個 API 會作用于所有的屏幕分組,但是如果我們想指定某組顯示屏呢,那么需要使用下面的 API,但是它是系統(tǒng) API
// PowerManager.java /** * @hide */ public WakeLock newWakeLock(int levelAndFlags, String tag, int displayId) { validateWakeLockParameters(levelAndFlags, tag); return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(), displayId); }
參數(shù) displayId 其實應該叫做 display group id,它表示喚醒鎖作用于指定分組顯示屏。
現(xiàn)在看下 WakeLock 的構造函數(shù)
// PowerManager.java WakeLock(int flags, String tag, String packageName, int displayId) { mFlags = flags; mTag = tag; mPackageName = packageName; mToken = new Binder(); mTraceName = "WakeLock (" + mTag + ")"; mDisplayId = displayId; }
構造函數(shù)就是簡單保存幾個參數(shù),但是有一點需要注意,mToken 是一個 Binder,它會傳給服務端 PowerManagerService,服務端會注冊它的死亡事件。那么這個 Binder 對象其實就是為了監(jiān)控服務端進程的生死。這個技術大家要學會,我曾經(jīng)用這個技術優(yōu)化過自己寫的服務端代碼。
獲取喚醒鎖
// PowerManager.java public void acquire() { synchronized (mToken) { acquireLocked(); } } public void acquire(long timeout) { synchronized (mToken) { acquireLocked(); // 發(fā)送一個延時消息,自動釋放喚醒鎖 mHandler.postDelayed(mReleaser, timeout); } } private void acquireLocked() { mInternalCount++; mExternalCount++; // mRefCounted 默認為 true,它表示對喚醒鎖引用計數(shù) if (!mRefCounted || mInternalCount == 1) { mHandler.removeCallbacks(mReleaser); Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0); try { mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource, mHistoryTag, mDisplayId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mHeld = true; } }
獲取喚醒鎖時,可以指定一個超時時間,如果時間到了,喚醒鎖還沒有釋放,那么會自動釋放喚醒鎖。
默認的情況下,喚醒鎖是計數(shù)的。如果多次獲取喚醒鎖,需要進行相應次數(shù)的釋放。
而如果通過 wakeLock.setReferenceCounted(false)
設置喚醒鎖為不計數(shù)
// PowerManager.java public void setReferenceCounted(boolean value) { synchronized (mToken) { mRefCounted = value; } }
那么多次獲取喚醒鎖后,只需要釋放一次。
現(xiàn)在讓我們看下服務端 PowerManagerService 是如何獲取喚醒鎖的
// PowerManagerService.java public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, WorkSource ws, String historyTag, int displayId) { // ... 省略權限檢測 try { acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag, uid, pid); } finally { Binder.restoreCallingIdentity(ident); } } private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag, String packageName, WorkSource ws, String historyTag, int uid, int pid) { synchronized (mLock) { // ... 省略顯示屏分組的檢測 WakeLock wakeLock; int index = findWakeLockIndexLocked(lock); boolean notifyAcquire; if (index >= 0) { // 喚醒鎖已經(jīng)存在 // ... } else { // 喚醒鎖不存在 // mUidState 由 ActivityManagerService 同步給 PowerManagerService // UidState 代表一個 app 進程的狀態(tài) UidState state = mUidState.get(uid); if (state == null) { state = new UidState(uid); state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; mUidState.put(uid, state); } // 保存喚醒鎖的數(shù)量 state.mNumWakeLocks++; // 1. 創(chuàng)建喚醒鎖 wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag, uid, pid, state); try { // 2. 監(jiān)聽客戶端進程的死亡 // 當客戶端進程死亡時,釋放它所申請的喚醒鎖 lock.linkToDeath(wakeLock, 0); } catch (RemoteException ex) { throw new IllegalArgumentException("Wake lock is already dead."); } // 3. 保存喚醒鎖 mWakeLocks.add(wakeLock); // 4. 更新喚醒鎖 PowerManager.PARTIAL_WAKE_LOCK 的 disable 狀態(tài) setWakeLockDisabledStateLocked(wakeLock); notifyAcquire = true; } // 5. 處理 PowerManager.ACQUIRE_CAUSES_WAKEUP 喚醒鎖亮屏的情況 applyWakeLockFlagsOnAcquireLocked(wakeLock, uid); // 6. 標記喚醒鎖已經(jīng)改變 mDirty |= DIRTY_WAKE_LOCKS; // 7. 更新電源狀態(tài) updatePowerStateLocked(); if (notifyAcquire) { // 記錄喚醒鎖 notifyWakeLockAcquiredLocked(wakeLock); } } }
先大致了解下,首次向 PowerManagerService 申請喚醒鎖的過程
- 創(chuàng)建服務端的 WakeLock。
- 監(jiān)聽客戶端傳遞過來的 Binder 的死亡事件,其實就是監(jiān)聽客戶端進程的死亡。當客戶端進程死亡時,釋放它所申請的喚醒鎖。
- PowerManagerService 使用 ArrayList< WakeLock > mWakeLocks 保存創(chuàng)建的喚醒鎖。
更新喚醒鎖 PowerManager.PARTIAL_WAKE_LOCK 的 disable 狀態(tài),因此有些情況下,是不允許獲取這種喚醒鎖的,這些特殊情況如下
private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) { if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.PARTIAL_WAKE_LOCK) { boolean disabled = false; final int appid = UserHandle.getAppId(wakeLock.mOwnerUid); if (appid >= Process.FIRST_APPLICATION_UID) { // Cached inactive processes are never allowed to hold wake locks. // 1. 緩存的不活躍的進程的喚醒鎖需要disable if (mConstants.NO_CACHED_WAKE_LOCKS) { disabled = mForceSuspendActive || (!wakeLock.mUidState.mActive && wakeLock.mUidState.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT && wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER); } if (mDeviceIdleMode) { // 2. idle 模式下,不處理白名單的進程的喚醒鎖,也需要 disable final UidState state = wakeLock.mUidState; if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 && Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 && state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT && state.mProcState > ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) { disabled = true; } } } // 3. 更新喚醒鎖的 disable 狀態(tài) if (wakeLock.mDisabled != disabled) { wakeLock.mDisabled = disabled; return true; } } return false; }
處理 PowerManager.ACQUIRE_CAUSES_WAKEUP 喚醒鎖亮屏的情況。
private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) { // 注意,PowerManager.ACQUIRE_CAUSES_WAKEUP 要與如下幾個屏幕鎖一起使用才有效 // PowerManager.FULL_WAKE_LOCK // PowerManager.SCREEN_BRIGHT_WAKE_LOCK // PowerManager.SCREEN_DIM_WAKE_LOCK if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0 && isScreenLock(wakeLock)) { String opPackageName; int opUid; if (wakeLock.mWorkSource != null && !wakeLock.mWorkSource.isEmpty()) { // ... } else { opPackageName = wakeLock.mPackageName; opUid = wakeLock.mOwnerUid; } for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { // 更新 wakefulness 為 WAKEFULNESS_AWAKE wakeDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag, opUid, opPackageName, opUid); } } }
注意,PowerManager.ACQUIRE_CAUSES_WAKEUP 是要下屏幕鎖一直使用,屏幕鎖為 PowerManager.FULL_WAKE_LOCK、PowerManager.SCREEN_BRIGHT_WAKE_LOCK、PowerManager.SCREEN_DIM_WAKE_LOCK。很顯然,這些屏幕鎖,都是保持屏幕處于點亮的狀態(tài)。
根據(jù)前面的文章,wakeDisplayGroupNoUpdateLocked() 其實就是更新 wakefulness 為 WAKEFULNESS_AWAKE。當后面更新電源狀態(tài)時,會向 DisplayManagerService 發(fā)起屏幕請示,從而進行亮屏。這個過程,請讀者參考前面的文章,自行分析。
- 標記喚醒鎖已經(jīng)改變。
- 更新電源狀態(tài),根據(jù) mDirty 處理喚醒鎖的改變 。
現(xiàn)在來看下最后一步,更新電源狀態(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; // 1. 歸納喚醒鎖 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. // 2. 更新顯示屏的電源狀態(tài) 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! // 3. 喚醒鎖保持 CPU 運行 updateSuspendBlockerLocked(); } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } }
與喚醒鎖相關的主要流程如下
- 歸納喚醒鎖。這個過程會把所有的喚醒鎖整合到一起,它會影響請求策略,也就是會影響屏幕最終狀態(tài)。并用它也會決定是否阻止CPU掛起,也就是是否保持CPU運行。詳見【歸納喚醒鎖】
- 更新電源狀態(tài)。根據(jù)前面的文章可知,屏幕的最終狀態(tài)是由請求的策略所決定的,而喚醒鎖可以響應策略。詳見【更新請求策略】
- 如果有喚醒鎖需要保證 CPU 運行,那么 PMS 會向底層獲取鎖,保證 CPU 運行。詳見【喚醒鎖保持 CPU 運行】
歸納喚醒鎖
private void updateWakeLockSummaryLocked(int dirty) { if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS | DIRTY_DISPLAY_GROUP_WAKEFULNESS)) != 0) { //1. wake lock summary 清 0 mWakeLockSummary = 0; final int numProfiles = mProfilePowerState.size(); for (int i = 0; i < numProfiles; i++) { mProfilePowerState.valueAt(i).mWakeLockSummary = 0; } for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId, 0); } // 2. 獲取 wake lock summary int invalidGroupWakeLockSummary = 0; final int numWakeLocks = mWakeLocks.size(); // 遍歷所有的 WakeLock for (int i = 0; i < numWakeLocks; i++) { final WakeLock wakeLock = mWakeLocks.get(i); final Integer groupId = wakeLock.getDisplayGroupId(); if (groupId == null) { continue; } // 把 PowerManager 定義的 WakeLock flag 轉化為 PowerManagerService 定義的 WakeLock flag final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock); // 更新 PMS 的 mWakeLockSummary mWakeLockSummary |= wakeLockFlags; if (groupId != Display.INVALID_DISPLAY_GROUP) { int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked( groupId); wakeLockSummary |= wakeLockFlags; mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId, wakeLockSummary); } else { // 沒有指定 group id 的喚醒鎖,保存到 invalidGroupWakeLockSummary invalidGroupWakeLockSummary |= wakeLockFlags; } for (int j = 0; j < numProfiles; j++) { // ... } } // 遍歷所有 WakeLock 結束 // 3. 調整的 wake lock summary for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { // 從這里可以看出,invalidGroupWakeLockSummary 應用到了所有的 display group 中 // 因此,在獲取 WakeLock 沒有指定 group id 時,這個 WakeLock 是應用到所有的 display group 上 final int wakeLockSummary = adjustWakeLockSummaryLocked( mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId), invalidGroupWakeLockSummary | mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId)); mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId, wakeLockSummary); } mWakeLockSummary = adjustWakeLockSummaryLocked(getWakefulnessLocked(), mWakeLockSummary); for (int i = 0; i < numProfiles; i++) { // ... } } }
這里的邏輯很清晰,其實就是遍歷所有的喚醒鎖,然后歸納保存到 mWakeLockSummary。當然這其中有幾個重要的函數(shù)需要搞清楚
通過 getWakeLockSummaryFlags() 把 PowerManager 定義的喚醒鎖轉化為 PowerManagerService 定義的喚醒鎖
private int getWakeLockSummaryFlags(WakeLock wakeLock) { switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { // 這個喚醒鎖用于保持 CPU 運行 case PowerManager.PARTIAL_WAKE_LOCK: // disabled 狀態(tài)的喚醒鎖,是不能保證 CPU 運行的 if (!wakeLock.mDisabled) { return WAKE_LOCK_CPU; } break; // 以下三個喚醒鎖用于保持屏幕處于點亮狀態(tài) case PowerManager.FULL_WAKE_LOCK: return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT; case PowerManager.SCREEN_BRIGHT_WAKE_LOCK: return WAKE_LOCK_SCREEN_BRIGHT; case PowerManager.SCREEN_DIM_WAKE_LOCK: return WAKE_LOCK_SCREEN_DIM; // 用距離傳感器進行滅屏、亮屏 case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: return WAKE_LOCK_PROXIMITY_SCREEN_OFF; // PowerManager.DOZE_WAKE_LOCK 由 DreamManagerService 獲取 // 它使設備真正進入打盹狀態(tài) case PowerManager.DOZE_WAKE_LOCK: return WAKE_LOCK_DOZE; // 由 window manager 獲取,允許應用在系統(tǒng)doze狀態(tài)下能夠繪制 case PowerManager.DRAW_WAKE_LOCK: return WAKE_LOCK_DRAW; } return 0; }
通過 adjustWakeLockSummaryLocked() 調整歸納的喚醒鎖
private static int adjustWakeLockSummaryLocked(int wakefulness, int wakeLockSummary) { // 系統(tǒng)處于 非doze 狀態(tài),PowerManager.DOZE_WAKE_LOCK 和 PowerManager.DRAW_WAKE_LOCK 無效 // 看來,這兩個鎖只有當系統(tǒng)處于 doze 狀態(tài),才有效果 if (wakefulness != WAKEFULNESS_DOZING) { wakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW); } // 系統(tǒng)處于休眠或者doze狀態(tài)下,如下三個保持屏幕點亮狀態(tài)的鎖是無效的 // PowerManager.FULL_WAKE_LOCK // PowerManager.SCREEN_BRIGHT_WAKE_LOCK // PowerManager.SCREEN_DIM_WAKE_LOCK if (wakefulness == WAKEFULNESS_ASLEEP || (wakeLockSummary & WAKE_LOCK_DOZE) != 0) { wakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM | WAKE_LOCK_BUTTON_BRIGHT); // 甚至,當系統(tǒng)處于休眠狀態(tài),PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK 無法喚醒屏幕 if (wakefulness == WAKEFULNESS_ASLEEP) { wakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF; } } // 如下三個保持屏幕點亮狀態(tài)的鎖 // PowerManager.FULL_WAKE_LOCK // PowerManager.SCREEN_BRIGHT_WAKE_LOCK // PowerManager.SCREEN_DIM_WAKE_LOCK // 當系統(tǒng)處于喚醒狀態(tài)或者屏保狀態(tài),這兩個其實都是亮屏狀態(tài) // 需要保證 CPU 運行,也就是下面添加的 WAKE_LOCK_CPU // 并且系統(tǒng)處于喚醒狀態(tài)時,還要屏幕長亮,這正好符合上面三個喚醒鎖的定義 if ((wakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) { // 并且如果系統(tǒng)處理喚醒狀態(tài),還要保持長亮 if (wakefulness == WAKEFULNESS_AWAKE) { wakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE; } else if (wakefulness == WAKEFULNESS_DREAMING) { // 系統(tǒng)處于屏保狀態(tài) wakeLockSummary |= WAKE_LOCK_CPU; } } // 系統(tǒng)處于 doze 狀態(tài),PowerManager.DRAW_WAKE_LOCK 需要保持 CPU 運行 if ((wakeLockSummary & WAKE_LOCK_DRAW) != 0) { wakeLockSummary |= WAKE_LOCK_CPU; } return wakeLockSummary; }
調整歸納的喚醒鎖,其實就是針對系統(tǒng)處于不同的狀態(tài),去掉一些不兼容的喚醒鎖或者添加一些合適的鎖。
例如,前面說過,PowerManager.FULL_WAKE_LOCK、PowerManager.SCREEN_BRIGHT_WAKE_LOCK、PowerManager.SCREEN_DIM_WAKE_LOCK 不僅僅要保持屏幕的亮度,而且還要保持 CPU 運行,這里就可以看出端倪。
更新請求策略
通過前面的文章可知,屏幕最終的狀態(tài)是通過請求策略控制的,函數(shù)如下
int getDesiredScreenPolicyLocked(int groupId) { final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId); final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId); if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) { // 1. 系統(tǒng)處于休眠狀態(tài),任何喚醒鎖都不起作用,屏幕會進入關閉狀態(tài) return DisplayPowerRequest.POLICY_OFF; } else if (wakefulness == WAKEFULNESS_DOZING) { // 2. 系統(tǒng)處于 doze 狀態(tài),PowerManager.DRAW_WAKE_LOCK 會讓屏幕進入 doze 狀態(tài) // 當 dream manager 成功啟動 doze dream,才會獲取 PowerManager.DRAW_WAKE_LOCK,此時系統(tǒng)才真正進入 doze 狀態(tài) if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) { return DisplayPowerRequest.POLICY_DOZE; } if (mDozeAfterScreenOff) { return DisplayPowerRequest.POLICY_OFF; } } if (mIsVrModeEnabled) { return DisplayPowerRequest.POLICY_VR; } // 下面處理的是系統(tǒng)處于喚醒和屏保狀態(tài),都是亮屏的狀態(tài) // 3. PowerManager.FULL_WAKE_LOCK 和 PowerManager.SCREEN_BRIGHT_WAKE_LOCK 會 // 讓屏幕處于亮屏狀態(tài) if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0 || !mBootCompleted || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId) & USER_ACTIVITY_SCREEN_BRIGHT) != 0 || mScreenBrightnessBoostInProgress) { return DisplayPowerRequest.POLICY_BRIGHT; } // 4. PowerManager.SCREEN_DIM_WAKE_LOCK 允許屏幕變暗 // 當屏幕快要超時時,會進入變暗的狀態(tài),此時持有 PowerManager.SCREEN_DIM_WAKE_LOCK 會保持屏幕 // 一直處于 dim 狀態(tài) return DisplayPowerRequest.POLICY_DIM; }
從獲取請求策略的過程,我們可以看到,當系統(tǒng)處于不同的狀態(tài),不同的喚醒鎖,是如何影響屏幕狀態(tài)的。
例如,PowerManager.FULL_WAKE_LOCK 和 PowerManager.SCREEN_BRIGHT_WAKE_LOCK 會保證屏幕一直處于亮屏狀態(tài),而 PowerManager.SCREEN_DIM_WAKE_LOCK 會保證屏幕也處于亮屏狀態(tài),但是允許變暗。
喚醒鎖保持 CPU 運行
private void updateSuspendBlockerLocked() { // 1. 檢測是否有喚醒鎖需要保持 CPU 運行 final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); // 2. 檢測屏幕的某些狀態(tài)是否需要保持 CPU 運行 final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked(); final boolean autoSuspend = !needDisplaySuspendBlocker; final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); boolean interactive = false; for (int id : groupIds) { interactive |= mDisplayGroupPowerStateMapper.getPowerRequestLocked(id).isBrightOrDim(); } // mDecoupleHalAutoSuspendModeFromDisplayConfig 默認為 false if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(false); } // 3. 向底層獲取鎖,保證 CPU 運行 // First acquire suspend blockers if needed. if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.acquire(); mHoldingWakeLockSuspendBlocker = true; } if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) { mDisplaySuspendBlocker.acquire(); mHoldingDisplaySuspendBlocker = true; } // mDecoupleHalInteractiveModeFromDisplayConfig 默認為 false if (mDecoupleHalInteractiveModeFromDisplayConfig) { // ... } // 下面表示沒有對應的喚醒鎖,就需要向底層釋放鎖 if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) { mWakeLockSuspendBlocker.release(); mHoldingWakeLockSuspendBlocker = false; } if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) { mDisplaySuspendBlocker.release(); mHoldingDisplaySuspendBlocker = false; } // mDecoupleHalAutoSuspendModeFromDisplayConfig 默認為 flase if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) { setHalAutoSuspendModeLocked(true); } }
如下幾個鎖會保持 CPU 運行
- PowerManager.PARTIAL_WAKE_LOCK(緩存的后臺進程 或 idle模式下,不處于白名單的進程,喚醒鎖無效)
- PowerManager.FULL_WAKE_LOCK(系統(tǒng)處于喚醒或屏保狀態(tài))
- PowerManager.SCREEN_BRIGHT_WAKE_LOCK(系統(tǒng)處于喚醒或屏保狀態(tài))
- PowerManager.SCREEN_DIM_WAKE_LOCK(系統(tǒng)處于喚醒或屏保狀態(tài))
- PowerManager.DOZE_WAKE_LOCK(系統(tǒng)處于doze狀態(tài))
- PowerManager.DRAW_WAKE_LOCK(系統(tǒng)處于doze狀態(tài))
屏幕的幾種狀態(tài)也需要保持 CPU 運行,請看下面代碼所展示的所有情況
private boolean needDisplaySuspendBlockerLocked() { // 1. DisplayManagerService 正在處理請求, 需要保持 CPU 運行 if (!mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) { return true; } // 2. 屏幕亮度正在增強中,需要保持 CPU 運行 if (mScreenBrightnessBoostInProgress) { return true; } // When we transition to DOZING, we have to keep the display suspend blocker // up until the Doze service has a change to acquire the DOZE wakelock. // Here we wait for mWakefulnessChanging to become false since the wakefulness // transition to DOZING isn't considered "changed" until the doze wake lock is // acquired. // 3. doze狀態(tài)的轉換中,需要保持 CPU 運行 if (getWakefulnessLocked() == WAKEFULNESS_DOZING && mDozeStartInProgress) { return true; } final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked(); for (int id : groupIds) { final DisplayPowerRequest displayPowerRequest = mDisplayGroupPowerStateMapper.getPowerRequestLocked(id); // 3. 亮屏狀態(tài)下,需要保持 CPU 運行 // 屏幕處于點亮或者變暗的狀態(tài),都是亮屏的狀態(tài) if (displayPowerRequest.isBrightOrDim()) { // If we asked for the screen to be on but it is off due to the proximity // sensor then we may suspend but only if the configuration allows it. // On some hardware it may not be safe to suspend because the proximity // sensor may not be correctly configured as a wake-up source. if (!displayPowerRequest.useProximitySensor || !mProximityPositive || !mSuspendWhenScreenOffDueToProximityConfig) { return true; } } // 4. 系統(tǒng)真正處于 doze 狀態(tài),也需要保持 CPU 運行 // 因此需要在屏幕繪制一些東西,例如時間 if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE && displayPowerRequest.dozeScreenState == Display.STATE_ON) { // Although we are in DOZE and would normally allow the device to suspend, // the doze service has explicitly requested the display to remain in the ON // state which means we should hold the display suspend blocker. return true; } } // Let the system suspend if the screen is off or dozing. return false; }
為何屏幕的狀態(tài)有時候也需要 CPU 保持運行?舉個最簡單的例子,如果處于亮屏狀態(tài),CPU 允許掛起的話,app 進程就無法運行了。
釋放鎖
// PowerManager.java public void release() { release(0); } /** * Releases the wake lock with flags to modify the release behavior. * <p> * This method releases your claim to the CPU or screen being on. * The screen may turn off shortly after you release the wake lock, or it may * not if there are other wake locks still held. * </p> * * @param flags Combination of flag values to modify the release behavior. * Currently only {@link #RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY} is supported. * Passing 0 is equivalent to calling {@link #release()}. */ public void release(int flags) { synchronized (mToken) { if (mInternalCount > 0) { // internal count must only be decreased if it is > 0 or state of // the WakeLock object is broken. mInternalCount--; } if ((flags & RELEASE_FLAG_TIMEOUT) == 0) { mExternalCount--; } if (!mRefCounted || mInternalCount == 0) { mHandler.removeCallbacks(mReleaser); if (mHeld) { Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0); try { mService.releaseWakeLock(mToken, flags); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } mHeld = false; } } if (mRefCounted && mExternalCount < 0) { throw new RuntimeException("WakeLock under-locked " + mTag); } } }
從這里我們可以驗證前面說的一個結論,mRefCounted 默認為 true,表示喚醒鎖是引用計數(shù)的,如果多少獲取喚醒鎖,需要釋放相應次數(shù)的喚醒鎖。如果不計數(shù),那么只需要釋放一次。
現(xiàn)在看下服務端 PowerManagerService 是如何釋放鎖的
public void releaseWakeLock(IBinder lock, int flags) { if (lock == null) { throw new IllegalArgumentException("lock must not be null"); } // 需要 android.Manifest.permission.WAKE_LOCK 權限 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); final long ident = Binder.clearCallingIdentity(); try { releaseWakeLockInternal(lock, flags); } finally { Binder.restoreCallingIdentity(ident); } } private void releaseWakeLockInternal(IBinder lock, int flags) { synchronized (mLock) { // 1. 找到服務端保存的喚醒鎖 int index = findWakeLockIndexLocked(lock); if (index < 0) { return; } WakeLock wakeLock = mWakeLocks.get(index); // 延遲釋放 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK // 直到距離傳感器檢測到物體遠離 if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) { mRequestWaitForNegativeProximity = true; } // 不再監(jiān)聽客戶端進程的死亡 wakeLock.mLock.unlinkToDeath(wakeLock, 0); // 移除喚醒鎖 removeWakeLockLocked(wakeLock, index); } } private void removeWakeLockLocked(WakeLock wakeLock, int index) { // 2. 從數(shù)據(jù)結構中移除喚醒鎖 mWakeLocks.remove(index); // 進程狀態(tài)中減少喚醒鎖的數(shù)量 UidState state = wakeLock.mUidState; state.mNumWakeLocks--; if (state.mNumWakeLocks <= 0 && state.mProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) { mUidState.remove(state.mUid); } // 記錄喚醒鎖被釋放的數(shù)據(jù) notifyWakeLockReleasedLocked(wakeLock); // 3. 處理 PowerManager.ON_AFTER_RELEASE // 帶有這個 flag 的鎖,在釋放的時候,會更新用戶行為時間,從而可以延長亮屏的時間 applyWakeLockFlagsOnReleaseLocked(wakeLock); // 4.標記喚醒已經(jīng)改變,并更新電源狀態(tài) mDirty |= DIRTY_WAKE_LOCKS; updatePowerStateLocked(); }
PowerManagerService 移除喚醒鎖的過程一般如下
- 從數(shù)據(jù)結構中移除。
- 處于帶有 PowerManager.ON_AFTER_RELEASE 這個 flag 的喚醒鎖。在釋放帶有這個 flag 的喚醒鎖的時候,會更新用戶行為時間,從而可以延長亮屏的時間。
- 標記喚醒鎖已經(jīng)改變,更新電源狀態(tài)。
距離傳感器鎖的原理,在看完本文后,大家可以自行分析。
現(xiàn)在來看下,釋放帶有 PowerManager.ON_AFTER_RELEASE 的喚醒鎖,是如何延長亮屏的時間的
private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) { // PowerManager.ON_AFTER_RELEASE 必須與如下的屏幕鎖一起使用 // PowerManager.FULL_WAKE_LOCK // PowerManager.SCREEN_BRIGHT_WAKE_LOCK // PowerManager.SCREEN_DIM_WAKE_LOCK if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0 && isScreenLock(wakeLock)) { userActivityNoUpdateLocked(mClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS, wakeLock.mOwnerUid); } } private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) { boolean updatePowerState = false; // 注意,PowerManager.ON_AFTER_RELEASE 影響了所有的 display group 的 用戶行為時間 // 那么也說明,它會導致所有的屏幕延長亮屏的時間 for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) { if (userActivityNoUpdateLocked(id, eventTime, event, flags, uid)) { updatePowerState = true; } } return updatePowerState; } // PowerManagerService.java private boolean userActivityNoUpdateLocked(int groupId, long eventTime, int event, int flags, int uid) { // ... try { // ... if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) { // ... } else { if (eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked( groupId)) { // 記錄用戶行為的時間 mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked(groupId, eventTime); // 標記用戶活動有改變 mDirty |= DIRTY_USER_ACTIVITY; if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) { mDirty |= DIRTY_QUIESCENT; } return true; } } } finally { Trace.traceEnd(Trace.TRACE_TAG_POWER); } return false; }
處理 PowerManager.ON_AFTER_RELEASE 的過程,我們要注意以下幾點事情
- PowerManager.ON_AFTER_RELEASE 必須要與屏幕喚醒鎖 PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK 一起使用.
- PowerManager.ON_AFTER_RELEASE 更新了所有屏幕分組的用戶行為時間,也就是說最終會導致所有屏幕都延長亮屏的時間。
- 更新用戶行為時間,根據(jù) PowerManagerService之自動滅屏 可知,用戶行為時間的更新,最終會導致延長亮屏的時間
結束
通過本文的分析,我們可以看到喚醒鎖是如何控制屏幕狀態(tài),以及如何保持CPU運行。但是本文寫的比較簡潔,是因為很多東西已經(jīng)在前文分析過了,如果讀者看本文的時候,有點壓力,不妨再回頭看看前面的文章。
PowerManagerService 系列的文章,就此結束。雖然還有一些功能我并未分析,但是我寫的這些文章都是基礎,只要掌握基礎,其它功能的分析,豈不是信手拈來。
以上就是PowerManagerService之喚醒鎖的使用獲取創(chuàng)建示例解析的詳細內容,更多關于PowerManagerService 喚醒鎖的資料請關注腳本之家其它相關文章!
相關文章
Android UI:ListView - SimpleAdapter實例詳解
這篇文章主要介紹了Android UI:ListView - SimpleAdapter實例詳解,SimpleAdapter是擴展性最好的適配器,可以定義各種你想要的布局,而且使用很方便,需要的朋友可以參考下2016-11-11Android?Fragment實現(xiàn)頂部、底部導航欄
這篇文章主要為大家詳細介紹了Android?Fragment實現(xiàn)頂部、底部導航欄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09Android中AnimationDrawable使用的簡單實例
這篇文章介紹了Android中AnimationDrawable使用的簡單實例,有需要的朋友可以參考一下2013-10-10Android的Launcher啟動器中添加快捷方式及小部件實例
這篇文章主要介紹了在Android的Launcher啟動器中添加快捷方式及窗口小部件的方法,包括在自己的應用程序中添加窗口小部件AppWidget的例子,需要的朋友可以參考下2016-02-02