[Android Framework] 8.1 PowerManagerService分析(三)——WakeLock機制
WakeLock機制概述
WakeLock是android系統中一種鎖的機制,只要有程序持有這個鎖,系統就無法進入休眠狀態。應用程式要申請WakeLock時,需要在清單檔案中配置android.Manifest.permission.WAKE_LOCK許可權。
根據作用時間,WakeLock可以分為永久鎖和超時鎖,永久鎖表示只要獲取了WakeLock鎖,必須顯式的進行釋放,否則系統會一直持有該鎖;後者表示在到達給定時間後,自動釋放WakeLock鎖,其實現原理為方法內部維護了一個Handler。
根據釋放原則,WakeLock可以分為計數鎖和非計數鎖,預設為計數鎖,如果一個WakeLock物件為計數鎖,則一次申請必須對應一次釋放;如果為非計數鎖,則不管申請多少次,一次就可以釋放該WakeLock。以下程式碼為WakeLock申請釋放示例,要申請WakeLock,必須有PowerManager例項,如下:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
wl.acquire();
Wl.acquire(int timeout);//超時鎖
釋放時:
wl.release();
Wakelock申請和釋放流程如下:
在整個WakeLock機制中,對應不同的範圍,有三種表現形式:
- PowerManger.WakeLock:PowerManagerService和其他應用、服務互動的介面;
- PowerManagerService.WakeLock:PowerManager.WakeLock在PMS中的表現形式;
- SuspendBlocker:PowerManagerService.WakeLock在向底層節點操作時的表現形式。
下面開始對wakelock的詳細分析。
PowerManager中的WakeLock
要獲取、申請Wakelock時,直接通過PowerManager的WakeLock進行。它作為系統服務的介面來供應用呼叫。
1.1.獲取WakeLock物件
獲取WakeLock例項在PowerManager中進行。
在應用中獲取WakeLock物件,方式如下:
PowerManager. WakeLock mWakeLock =
mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
應用中獲取wakelock物件,獲取的是位於PowerManager中的內部類——WakeLock的例項,在PowerManager中看看相關方法:
public WakeLock newWakeLock(int levelAndFlags, String tag) {
validateWakeLockParameters(levelAndFlags, tag);
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
}
在PowerManager的newWakeLock()方法中,首先進行了引數的校驗,然後呼叫WakeLock構造方法獲取例項,構造方法如下:
WakeLock(int flags, String tag, String packageName) {
//表示wakelock型別或等級
mFlags = flags;
//一個tag,一般為當前類名
mTag = tag;
//獲取wakelock的包名
mPackageName = packageName;
//一個Binder標記
mToken = new Binder();
mTraceName = "WakeLock (" + mTag + ")";
}
除了構造方法中必須要傳入的引數之外,還有如下幾個屬性:
//表示內部計數
private int mInternalCount;
//表示內部計數
private int mExternalCount;
//表示是否是計數鎖
private boolean mRefCounted = true;
//表示是否已經持有該鎖
private boolean mHeld;
//表示和該wakelock相關聯的工作源,這在當一個服務獲取wakelock執行工作時很有用,以便計算工作成本
private WorkSource mWorkSource;
//表示一個歷史標籤
private String mHistoryTag;
1.2.WakeLock等級(類別)
Wakelock共有以下幾種等級:
//如果持有該型別的wakelock鎖,則按Power鍵滅屏後,即使允許螢幕、按鍵燈滅,也不會釋放該鎖,CPU不會進入休眠狀態
public static final int PARTIAL_WAKE_LOCK;
//Deprecated,如果持有該型別的wakelock鎖,則使螢幕保持亮/Dim的狀態,鍵盤燈允許滅,按Power鍵滅屏後,會立即釋放
public static final int SCREEN_DIM_WAKE_LOCK;
//Deprecated,如果持有該型別的wakelock鎖,則使螢幕保持亮的狀態,鍵盤燈允許滅,按Power鍵滅屏後,會立即釋放
public static final int SCREEN_BRIGHT_WAKE_LOCK
//Deprecated,如果持有該型別的wakelock鎖,則使螢幕、鍵盤燈都保持亮,按Power鍵滅屏後,會立即釋放
public static final int FULL_WAKE_LOCK
//如果持有該鎖,則當PSensor檢測到有物體靠近時關閉螢幕,遠離時又亮屏,該型別鎖不會阻止系統進入睡眠狀態,比如
//當到達休眠時間後會進入睡眠狀態,但是如果當前螢幕由該wakelock關閉,則不會進入睡眠狀態。
public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK
//如果持有該鎖,則會使螢幕處於DOZE狀態,同時允許CPU掛起,該鎖用於DreamManager實現Doze模式,如SystemUI的DozeService
public static final int DOZE_WAKE_LOCK
//如果持有該鎖,則會時裝置保持喚醒狀態,以進行繪製螢幕,該鎖常用於WindowManager中,允許應用在系統處於Doze狀態下時進行繪製
public static final int DRAW_WAKE_LOCK
這些值會在下面以圖示的形式總結。除了等級之外,還有幾個標記:
//該值為0x0000FFFF,用於根據flag判斷Wakelock的級別,如:
//if((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) == PowerManager.PARTIAL_WAKE_LOCK){}
public static final int WAKE_LOCK_LEVEL_MASK
//用於在申請鎖時喚醒裝置,一般情況下,申請wakelock鎖時不會喚醒裝置,它只會導致螢幕保持開啟狀態,如果帶有這個flag,則會在申
//請wakelock時就點亮螢幕,如常見通知來時螢幕亮,該flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用。
public static final int ACQUIRE_CAUSES_WAKEUP
//在釋放鎖時,如果wakelock帶有該標誌,則會小亮一會再滅屏,該flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用。
public static final int ON_AFTER_RELEASE
//和其他標記不同,該標記是作為release()方法的引數,且僅僅用於釋放PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK型別的
//鎖,如果帶有該引數,則會延遲釋放鎖,直到感測器不再感到物件接近
public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY
申請WakeLock
當獲取到WakeLock例項後,就可以申請WakeLock了。前面說過了,根據作用時間,WakeLock鎖可以分為永久鎖和超時鎖,根據釋放原則,WakeLock可以分為計數鎖和非計數鎖。申請方式如下:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "My Tag");
wl.acquire();//申請一個永久鎖
Wl.acquire(int timeout);//申請一個超時鎖
acquire()方法原始碼如下:
public void acquire() {
synchronized (mToken) {
acquireLocked();
}
}
public void acquire(long timeout) {
synchronized (mToken) {
acquireLocked();
//申請鎖之後,內部會維護一個Handler去完成自動釋放鎖
mHandler.postDelayed(mReleaser, timeout);
}
}
可以看到這兩種方式申請方式完全一樣,只不過如果是申請一個超時鎖,則會通過Handler延時傳送一個訊息,到達時間後去自動釋放鎖。
到這一步,對於申請wakelock的應用或系統服務來說就完成了,具體的申請在PowerManager中進行,繼續看看
acquireLocked()方法:
private void acquireLocked() {
//應用每次申請wakelock,內部計數和外部計數加1
mInternalCount++;
mExternalCount++;
//如果是非計數鎖或者內部計數值為1,即第一次申請該鎖,才會真正去申請
if (!mRefCounted || mInternalCount == 1) {
mHandler.removeCallbacks(mReleaser);
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
try {
//向PowerManagerService申請鎖
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
mHistoryTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
//表示此時持有該鎖
mHeld = true;
}
}
是否是計數鎖可以通過setReferenceCount()
來設定,預設為計數鎖:
public void setReferenceCounted(boolean value) {
synchronized (mToken) {
mRefCounted = value;
}
}
從acquire()方法可以看出,對於計數鎖來說,只會在第一次申請時向PowerManagerService去申請鎖,當該wakelock例項第二次、第三次去申請時,如果沒有進行過釋放,則只會對計數引用加1,不會向PowerManagerService去申請。如果是非計數鎖,則每次申請,都會調到PowerManagerService中去。
釋放WakeLock鎖
如果是通過acquire(long timeout)
方法申請的超時鎖,則會在到達時間後自動去釋放,如果是通過acquire()
方法申請的永久鎖,則必須進行顯式的釋放,否則由於系統一直持有wakelock鎖,將導致無法進入休眠狀態,從而導致耗電過快等功耗問題。
在前面分析申請鎖時已經說了,如果是超時鎖,通過Handler.post(Runnable)的方式進行釋放,該Runnable定義如下:
private final Runnable mReleaser = new Runnable() {
public void run() {
release(RELEASE_FLAG_TIMEOUT);
}
};
RELEASE_FLAG_TIMEOUT
是一個用於release()方法的flag,表示釋放的為超時鎖。
如果是永久鎖,則必須通過呼叫release()方法進行釋放了,該方法如下:
public void release() {
release(0);
}
因此,不管是哪種鎖的釋放,其實都是在release(int)
中進行的,只不過引數不同,該方法如下:
public void release(int flags) {
synchronized (mToken) {
//內部計數-1
mInternalCount--;
//如果釋放超時鎖,外部計數-1
if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
mExternalCount--;
}
//如果釋放非計數鎖或內部計數為0,並且該鎖還在持有,則通過PowerManagerService去釋放
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;
}
}
//如果時計數鎖,並且外部計數小於0,則丟擲異常
if (mRefCounted && mExternalCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
}
對於計數鎖的釋放,每次都會對內部計數值減一,只有當你內部計數值減為0時,才會去呼叫PowerManagerService去真正的釋放鎖;如果釋放非計數鎖,則每次都會呼叫PowerManagerService進行釋放。
WakeLock型別及其特點見下表:
Level | CPU | Screen | Keyboard | Power Button | 備註 |
---|---|---|---|---|---|
PARTIAL_WAKE_LOCK | On | On/Off | On/Off | No Influence | Cpu不受power鍵影響,一直執行直到所有鎖被釋放 |
FULL_WAKE_LOCK | On | Bright | Bright | release | API17中已啟用,改用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,當用戶在不同的應用程式間切換時可以正確的管理,同時不需要許可權 |
SCREEN_DIM_WAKE_LOCK | On | Dim/Bright | Off | release | 同上 |
SCREEN_BRIGHT_WAKE_LOCK | On | Bright | Off | release | 同上 |
PROXIMITY_SCREEN_OFF_WAKE_LOCK | On/Off | Bright/off | Off | relesase | 不能和ACQUIRE_CAUSES_WAKEUP一起使用 |
DOZE_WAKE_LOCK | On/Off | Off | Off | release | @hide,允許在doze狀態下使cpu進入suspend狀態,僅在doze狀態下有效,需要android.Manifest.permission.DEVICE_POWER許可權 |
DRAW_WAKE_LOCK | On/Off | Off | Off | No | @hide,允許在doze狀態下進行螢幕繪製,僅在doze狀態下有效,需要DEVICE_POWER許可權 |
ACQUIRE_CAUSES_WAKEUP | Wakelock 標記,一般情況下,獲取wakelock並不能喚醒裝置,加上這個標誌後,申請wakelock後也會喚醒螢幕。如通知、鬧鐘… | 不能和PARTIAL_WAKE_LOCK一起使用 | |||
ON_AFTER_RELEASE | Wakelock 標記,當釋放該標記的鎖時,會亮一小會再滅屏 | 同上 |
對於開發者來說,只能申請非@hide的鎖,即
PARTIAL_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK
四類。
比如,我要獲取一個wakelock型別為PARTIAL_WAKE_LOCK的WakeLock鎖,則在申請這個鎖後,雖然螢幕、鍵盤燈可以關閉,但CPU將一直處於活動狀態,不受power鍵的控制。獲得WakeLock物件後,可以根據自己的需求來申請不同形式的鎖。接下來我們繼續分析在申請、釋放鎖時PowerManagerService中的流程。
PowerManagerService中的WakeLock
WakeLock申請
在申請WakeLock時,當應用層呼叫完acquire()
方法後,由PowerManager去處理了。對於兩種申請方式,最終都呼叫了acquireLocked()
進行申請,acquireLocked()
又向下呼叫,讓mService去處理,我們通過上面的分析知道,這個mService就是PMS.BinderService,該方法如下:
private void acquireLocked() {
if (!mRefCounted || mCount++ == 0) {
mHandler.removeCallbacks(mReleaser);
try {
//向下呼叫PMS去處理
mService.acquireWakeLock(mToken, mFlags, mTag,
mPackageName, mWorkSource,mHistoryTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mHeld = true;//持有鎖標記置為true
}
其中mHeld
是一個判斷是否持有鎖的標記,在應用中可以通過呼叫WakeLock的isHeld()
來判斷是否持有WakeLock。
PMS中的acquireWakeLock()
方法如下:
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag,
String packageName,WorkSource ws, String historyTag) {
......
//檢查wakelock級別
PowerManager.validateWakeLockParameters(flags, tag);
//檢查WAKE_LOCK許可權
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LO
CK, null);
//如果是DOZE_WAKE_LOCK級別wakelock,還要檢查DEVICE_POWER許可權
if ((flags & PowerManager.DOZE_WAKE_LOCK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
//ws = null
......
//重置當前執行緒上傳入的IPC標誌
final long ident = Binder.clearCallingIdentity();
try {
acquireWakeLockInternal(lock, flags, tag, packageName, ws, historyTag,
uid, pid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
在這個方法中,首先進行了WakeLock型別檢查,避免無效的WakeLock型別,然後進行許可權的檢查,WakeLock需要android.Manifest.permission.WAKE_LOCK
許可權,如果申請的WakeLock型別是DOZE_WAKE_LOCK,則還需要android.Manifest.permission.DEVICE_POWER
許可權(見上表),檢查完畢後重置Binder的IPC標誌,然後呼叫下一個方法acquireWakeLockInternal()
:
private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag, int uid, int pid) {
synchronized (mLock) {
//PMS中的WakeLock類
WakeLock wakeLock;
//查詢是否已存在該PM.WakeLock例項
int index = findWakeLockIndexLocked(lock);
boolean notifyAcquire;
//是否存在wakelock
if (index >= 0) {
wakeLock = mWakeLocks.get(index);
if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) {
//更新wakelock
notifyWakeLockChangingLocked(wakeLock, flags, tag, packageName,
uid, pid, ws, historyTag);
wakeLock.updateProperties(flags, tag, packageName,
ws, historyTag, uid, pid);
}
notifyAcquire = false;
} else {
//從SpareArray<UidState>中查詢是否存在該uid
UidState state = mUidState.get(uid);
if (state == null) {
state = new UidState(uid);
//設定該Uid的程序狀態
state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
mUidState.put(uid, state);
}
//將該uid申請的WakeLock計數加1
//建立新的PMS.WakeLock例項
wakeLock = new WakeLock(lock, flags, tag, packageName, ws,
historyTag, uid, pid);
try {
lock.linkToDeath(wakeLock, 0);
} catch (RemoteException ex) {
throw new IllegalArgumentException("Wake lock is already dead.");
}
//新增到wakelock集合中
mWakeLocks.add(wakeLock);
//用於設定PowerManger.PARTIAL_WAKE_LOCK能否可用
//1.快取的不活動程序不能持有wakelock鎖
//2.如果處於idle模式,則會忽略掉所有未處於白名單中的應用申請的鎖
setWakeLockDisabledStateLocked(wakeLock);
//表示有新的wakelock申請了
notifyAcquire = true;
}
//判斷是否直接點亮螢幕,如果帶有點亮螢幕標誌值,並且wakelock型別為
//FULL_WAKE_LOCK,SCREEN_BRIGHT_WAKE_LOCK,SCREEN_DIM_WAKE_LOCK,則進行下
//步處理
applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
//更新標誌位
mDirty |= DIRTY_WAKE_LOCKS;
updatePowerStateLocked();
if (notifyAcquire) {
//當申請了鎖後,在該方法中進行長時鎖的判斷,通知BatteryStatsService
// 進行統計持鎖時間等
notifyWakeLockAcquiredLocked(wakeLock);
}
}
}
首先通過傳入的第一個引數IBinder進行查詢WakeLock是否已經存在,若存在,則不再進行例項化,在原有的WakeLock上更新其屬性值;若不存在,則建立一個WakeLock物件,同時將該WakeLock儲存到List中。此時已經獲取到了WakeLock物件,這裡需要注意的是,此處的WakeLock物件和PowerManager中獲取的不是同一個WakeLock哦!
獲取到WakeLock例項後,還通過setWakeLockDisabledStateLocked(wakeLock)
進行了判斷該WakeLock是否可用,主要有兩種情況:
- 1.快取的不活動程序不能持有WakeLock鎖;
- 2.如果處於idle模式,則會忽略掉所有未處於白名單中的應用申請的鎖。
根據情況會設定WakeLock例項的disable屬性值表示該WakeLock是否不可用。
下一步進行判斷是否直接點亮螢幕,如果獲得的WakeLock帶有ACQUIRE_CAUSES_WAKEUP
標誌,並且WakeLock型別為FULL_WAKE_LOCK,SCREEN_BRIGHT_WAKE_LOCK,SCREEN_DIM_WAKE_LOCK
這三種其中之一(isScreenLock判斷),則會直接喚醒螢幕,如下程式碼中的applyWakeLockFlagsOnAcquireLocked(wakeLock, uid)
方法:
private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) {
if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
&& isScreenLock(wakeLock)) {
......
wakeUpNoUpdateLocked(SystemClock.uptimeMillis(), wakeLock.mTag, opUid,
opPackageName, opUid);
}
}
在wakeUpNoUpdateLocked()
中:
private boolean wakeUpNoUpdateLocked(long eventTime, String reason, int reasonUid,
String opPackageName, int opUid) {
//如果eventTime<上次休眠時間、裝置當前處於喚醒狀態、沒有啟動完成、沒有準備
//完成,則不需要更新,返回false
if (eventTime < mLastSleepTime || mWakefulness == WAKEFULNESS_AWAKE
|| !mBootCompleted || !mSystemReady) {
return false;
}
//更新最後一次喚醒時間值
mLastWakeTime = eventTime;
//設定wakefulness
setWakefulnessLocked(WAKEFULNESS_AWAKE, 0);
//通知BatteryStatsService/AppService螢幕狀態發生改變
mNotifier.onWakeUp(reason, reasonUid, opPackageName, opUid);
//更新使用者活動事件時間值
userActivityNoUpdateLocked(
eventTime, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, reasonUid);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_POWER);
}
return true;
}
wakeUpNoUpdateLocked()
方法是喚醒裝置的主要方法。在這個方法中,首先更新了mLastWakeTime
這個值,表示上次喚醒裝置的時間,在系統超時休眠時用到這個值進行判斷。現在,只需要知道每次亮屏,都走的是這個方法,關於具體是如何喚醒螢幕的,在第5節中進行分析。
現在我們繼續回到acquireWakeLockInternal()
方法的結尾處,當檢查完WakeLock的ACQUIRE_CAUSES_WAKEUP
標誌後,更新mDirty,然後呼叫updatePowerStateLocked()
方法,這個方法在第二篇文章中說過了,是整個PMS的核心方法,在這個方法中呼叫了幾個關鍵方法,這些方法已經進行了分析,只剩一個WakeLock相關的updateSuspendBlockerLocked()
沒有分析,現在開始分析這個方法,該方法如下:
private void updateSuspendBlockerLocked() {
//是否需要保持CPU活動狀態的SuspendBlocker鎖,具體表現為持有Partical WakeLock
final boolean needWakeLockSuspendBlocker =
((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
//是否需要保持CPU活動狀態的SuspendBlocker鎖,具體表現保持螢幕亮
final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
//是否自動掛起,如果不需要螢幕保持喚醒,則說明可以自動掛起CPU
final boolean autoSuspend = !needDisplaySuspendBlocker;
//是否處於互動模式,螢幕處於Bright或者Dim狀態時為true
final boolean interactive = mDisplayPowerRequest.isBrightOrDim();
//mDecoupleHalAutoSuspendModeFromDisplayConfig:自動掛起模式和顯示狀態解偶
if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
//禁止CPU自動掛起模式
setHalAutoSuspendModeLocked(false);
}
//如果存在PARTIAL_WAKE_LOCK型別的WakeLock,申請mWakeLockSuspendBlocker鎖
if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
mWakeLockSuspendBlocker.acquire();
mHoldingWakeLockSuspendBlocker = true;
}
//如果當前螢幕需要保持亮屏,申請mDisplaySuspendBlocker鎖
if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
mDisplaySuspendBlocker.acquire();
mHoldingDisplaySuspendBlocker = true;
}
//???
if (mDecoupleHalInteractiveModeFromDisplayConfig) {
//設定Hal層互動模式?
if (interactive || mDisplayReady) {
//???
setHalInteractiveModeLocked(interactive);
}
}
//如果不再持有PARTIAL_WAKELOCK型別的WakeLock鎖,釋放mWakeLockSuspendBlocker鎖
if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
mWakeLockSuspendBlocker.release();
mHoldingWakeLockSuspendBlocker = false;
}
//如果不再需要螢幕保持亮屏,釋放mDispla