PowerManagerService分析(三)之WakeLock機制

收藏待读
PowerManagerService分析(三)之WakeLock機制
PowerManagerService分析(三)之WakeLock機制

極力推薦Android 開發大總結文章:歡迎收藏

程序員Android 力薦 ,Android 開發者需要的必備技能
PowerManagerService分析(三)之WakeLock機制

註:文章轉於網絡, 點擊查看原文

PowerManagerService分析(三)之WakeLock機制

PowerManagerService之前系列文章請參考如下

1. PowerManagerService分析(一)之PMS啟動

2. PowerManagerService分析(二)之updatePowerStateLocked()核心

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);//超時鎖
 // ... do work...
 //釋放鎖
 wl.release();

Wakelock申請和釋放流程如下:

PowerManagerService分析(三)之WakeLock機制

在整個 WakeLock 機制中,對應不同的範圍,有三種表現形式:

  • 1.PowerManger.WakeLock:
    PowerManagerService和其他應用、服務交互的接口;
  • 2.PowerManagerService.WakeLock:
    PowerManager.WakeLock在PMS中的表現形式;
  • 3.SuspendBlocker:
    PowerManagerService.WakeLock在向 底層節點操作 時的表現形式。

下面開始對wakelock的詳細分析。

1.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類型及其特點見下表:

LevelCPUScreenKeyboardPower Button備註
PARTIAL_WAKE_LOCKOnOn/OffOn/OffNo InfluenceCpu不受power鍵影響,一直運行直到所有鎖被釋放
FULL_WAKE_LOCKOnBrightBrightreleaseAPI17中已啟用,改用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,當用戶在不同的應用程序間切換時可以正確的管理,同時不需要權限
SCREEN_DIM_WAKE_LOCKOnDim/BrightOffrelease同上
SCREEN_BRIGHT_WAKE_LOCKOnBrightOffrelease同上
PROXIMITY_SCREEN_OFF_WAKE_LOCKOn/OffBright/offOffrelesase不能和ACQUIRE_CAUSES_WAKEUP一起使用
DOZE_WAKE_LOCKOn/OffOffOffrelease@hide,允許在doze狀態下使cpu進入suspend狀態,僅在doze狀態下有效,需要android.Manifest.permission.DEVICE_POWER權限
DRAW_WAKE_LOCKOn/OffOffOffNo@hide,允許在doze狀態下進行屏幕繪製,僅在doze狀態下有效,需要DEVICE_POWER權限
ACQUIRE_CAUSES_WAKEUPWakelock 標記,一般情況下,獲取wakelock並不能喚醒設備,加上這個標誌後,申請wakelock後也會喚醒屏幕。如通知、鬧鐘… 不能和PARTIAL_WAKE_LOCK一起使用
ON_AFTER_RELEASEWakelock 標記,當釋放該標記的鎖時,會亮一小會再滅屏 同上

源碼注釋如下:

/**
 * The following wake lock levels are defined, with varying effects on system power.
 * These levels are mutually exclusive - you may only specify one of them.
 *
 * 
 *     
 *     
 *
 *     
 *         
 *     
 *
 *     
 *         
 *     
 *
 *     
 *         
 *     
 *
 *     
 *         
 *     
 * 
Flag ValueCPU Screen Keyboard
{@link #PARTIAL_WAKE_LOCK}On* Off Off
{@link #SCREEN_DIM_WAKE_LOCK}On Dim Off
{@link #SCREEN_BRIGHT_WAKE_LOCK}On Bright Off
{@link #FULL_WAKE_LOCK}On Bright Bright
*

* *If you hold a partial wake lock, the CPU will continue to run, regardless of any * display timeouts or the state of the screen and even after the user presses the power button. * In all other wake locks, the CPU will run, but the user can still put the device to sleep * using the power button. ***/

對於開發者來說,只能申請非@hide的鎖,即 PARTIAL_WAKE_LOCKSCREEN_DIM_WAKE_LOCKSCREEN_BRIGHT_WAKE_LOCKFULL_WAKE_LOCK 四類。

比如,我要獲取一個 wakelock 類型為 PARTIAL_WAKE_LOCKWakeLock 鎖,則在申請這個鎖後,雖然屏幕、鍵盤燈可以關閉,但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 是一個判斷是否持有鎖的標記,在應用中可以通過調用 WakeLockisHeld() 來判斷是否持有 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 權限(見上表),檢查完畢後重置 BinderIPC 標誌,然後調用下一個方法 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中查找是否存在該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() 方法的結尾處,當檢查完 WakeLockACQUIRE_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;
    }
    //如果不再需要屏幕保持亮屏,釋放mDisplaySuspendBlocker鎖
    if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
        mDisplaySuspendBlocker.release();
        mHoldingDisplaySuspendBlocker = false;
    }
    //啟動自動掛起模式
    if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
        setHalAutoSuspendModeLocked(true);
    }
}

updateSuspendBlockerLocked() 方法中,會根據當前系統是否持有 PARTIAL_WAKELOCK 類型的鎖,來決定是否要申請或釋放 mWakeLockSuspendBlocker 鎖,然後會根據當前系統是否要屏幕亮屏來決定是否要申請或釋放 mDisplaySuspendBlocker 鎖。

我們在分析PMS的啟動時提到過,在PMS的構造方法中創建了兩個 SuspendBlocker 對象: mWakeLockSuspendBlockermDisplaySuspendBlocker ,前者表示獲取一個 PARTIAL_WAKELOCK 類型的 WakeLock 使CPU保持活動狀態,後者表示當屏幕亮屏、用戶活動時使CPU保持活動狀態。因此實際上,上層 PowerManager 申請和釋放鎖,最終在PMS中都交給了 SuspendBlocker 去申請和釋放鎖。也可以說 SuspendBlocker 類的兩個對象是 WakeLock 鎖反映到底層的對象。只要持有二者任意鎖,都會使得CPU處於活動狀態。

NOTE 這裡還有兩個值含義很重要:

mDecoupleHalAutoSuspendModeFromDisplayConfig:

該值表示是否將自動掛起狀態和設備的屏幕顯示狀態(開/關)進行解耦。如果為 false ,則 autosuspend_disable() 函數會在屏幕亮之前調用, autosuspend_enable() 函數會在屏幕滅之後調用,這種模式為使用傳統電源管理功能的設備(如提前暫停/延遲恢復)提供最佳兼容性。如果為 true ,則 autosuspend_display()autosuspend_enable() 將會被單獨調用,不管屏幕正在亮或滅,這種模式使電源管理器可以在屏幕打開時掛起應用程序處理器。當指定了 dozeComponent 組件時,應將此資源設置為「true」,最大限度地節省電力,但並非所有設備都支持它。

mDecoupleHalInteractiveModeFromDisplayConfig:

表示是否將交互狀態和設備的屏幕顯示狀態進行解耦。

如果為 false ,則當屏幕亮時調用 setInteractive(…, true) 函數,當屏幕滅時調用 setInteractive(…, false) 函數,此模式為希望將交互式狀態綁定到顯示狀態的設備提供最佳兼容性。

如果為 truesetInteractive() 函數將會獨立地調用,不管屏幕處於亮或滅,這種模式使電源管理器可以在顯示器打開時減少時鐘並禁用觸摸控制器

再來看看 needDisplaySuspendBlockerLocked() 的實現:

private boolean needDisplaySuspendBlockerLocked() {
        //mDisplayReady表示是否顯示器準備完畢
        if (!mDisplayReady) {
            return true;
        }
        //請求Display策略狀態為Bright或DIM,這個if語句用來判斷當PSensor滅屏時是否需要Display鎖
        if (mDisplayPowerRequest.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.
            //如果沒有PROXIMITY_SCREEN_OFF_WAKE_LOCK類型的WakeLock鎖||PSensor正在處於遠離狀態
            //或在PSensor滅屏後不允許進入Suspend狀態,滿足之一,則申請misplaySuspendBlocker鎖
            if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
                    || !mSuspendWhenScreenOffDueToProximityConfig) {
                return true;
            }
        }
        if (mScreenBrightnessBoostInProgress) {
            return true;
        }
        // Let the system suspend if the screen is off or dozing.
        return false;
    }

SuspendBlocker是一個接口,並且只有 acquire()release() 兩個方法, PMS.SuspendBlockerImpl 實現了該接口,因此,最終申請流程執行到了 PMS.SuspendBlockerImplacquire() 中。

PMS.SuspendBlockerImpl.acquire() 中進行申請時,首先將成員變量計數加1,然後調用到JNI層去進行申請,對應的JNI層文件路徑為: frameworksbaseservicescorejnicom_android_server_power_PowerManagerService.cpp ,具體代碼如下:

@Override
public void acquire() {
    synchronized (this) {
        //引用計數
        mReferenceCount += 1;  
        if (mReferenceCount == 1) {
            nativeAcquireSuspendBlocker(mName);
        }
    }
}

這裡使用了引用計數法,如果 mReferenceCount >1 ,則不會進行鎖的申請,而是僅僅將 mReferenceCount +1 ,只有當沒有申請的鎖時,才會其正真執行申請鎖操作,之後不管申請幾次,都是 mReferenceCount 加1.

在JNI層中可以明確的看到有一個申請鎖的 acquire_wake_lock() 方法,代碼如下:

static void nativeAcquireSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
    ScopedUtfChars name(env, nameStr);
    acquire_wake_lock(PARTIAL_WAKE_LOCK, name.c_str());
}

這個方法位於HAL層,實現在 /hardware/libhardware_legacy/power/power.c 中,先看看具體代碼:

int acquire_wake_lock(int lock, const char* id)
{
    initialize_fds();
    ALOGI("acquire_wake_lock lock=%d id='%s'n", lock, id);
    if (g_error) return g_error;
    int fd;
    size_t len;
    ssize_t ret;
    if (lock != PARTIAL_WAKE_LOCK) {
        return -EINVAL;
    }
    fd = g_fds[ACQUIRE_PARTIAL_WAKE_LOCK];
    ret = write(fd, id, strlen(id));
    if (ret < 0) {
        return -errno;
    }
    return ret;
}

在這裡,向 /sys/power/wake_lock 文件寫入了 id ,這個id就是我們上層中實例化 SuspendBlocker 時傳入的 String 類型的 name ,這裡在這個節點寫入文件以後,就說明獲得了 wakelock 。可通過adb命令查看該文件:

$ adb root
adbd is already running as root
$ adb remount
remount succeeded
$ adb shell cat /sys/power/wake_lock
PowerManagerService.Display
$

到這裡,整個 WakeLock 的申請流程就結束了。

現在還有一點剩餘代碼需要看看,繼續回到 PMSacquireWakeLockInternal() 方法中,當執行完 updatePowerStateLocked() 方法後,如果有新的 WakeLock 實例創建,則 notifyAcquire 值為 true ,通過以下這個方法通知 NotifierNotifier 中則會根據該鎖申請的時間開始計時,並以此來判斷是否是一個長時間持有的鎖:

private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) {
    if (mSystemReady && !wakeLock.mDisabled) {
        wakeLock.mNotifiedAcquired = true;
        wakeLock.mStartTimeStamp = SystemClock.elapsedRealtime();
        //Called when a wake lock is acquired.
        mNotifier.onWakeLockAcquired(wakeLock.mFlags, 
                        wakeLock.mTag, wakeLock.mPackageName,
                wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource,
                wakeLock.mHistoryTag);
        ......
        //重新開始檢查持鎖時間
        restartNofifyLongTimerLocked(wakeLock);
    }
}

申請 WakeLock 時序圖如下:

PowerManagerService分析(三)之WakeLock機制

WakeLock的釋放

當應用持有 WakeLock 鎖、更準確地說是 PARTIAL_WAKELOCK 類型的 WakeLock 鎖,執行完相應的任務後,要及時對其進行釋放,否則會導致設備一直處於喚醒狀態,CPU無法休眠,造成電量消耗過快。 WakeLock 的釋放流程和申請流程很類似,我們還是從應用層開始逐步分析.

應用中執行釋放鎖操作的前提是該應用當前持有一個鎖,通過 PowerManager.WakeLock 調用 release() 進行釋放,在對應用層開放的 API 中,有兩種釋放形式,除了最常用的直接調用 release() 方法,還有一種是調用 release(int flag) 方法,這個方法允許用戶傳入一個標誌值,以達到修改釋放行為的目的,目前而言(API 27),只支持一個值:

public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1;

表示延遲釋放 PROXIMITY_SCREEN_OFF_WAKE_LOCK 類型的 WakeLock (該類型的 WakeLock 最常見於通話過程中,為了有更好的用戶體驗,在臉靠近屏幕時防止誤觸,屏幕會熄滅,遠離屏幕時屏幕又會開啟,就是使用了該 WakeLock ),傳入 0 相當於調用 release() ,具體代碼如下:

public void release() {
    release(0);
}
public void release(int flags) {
    synchronized (mToken) {
        if (!mRefCounted || --mCount == 0) {
            mHandler.removeCallbacks(mReleaser);
            if (mHeld) {
                try {
                    //PMS中進行處理
                    mService.releaseWakeLock(mToken, flags);
                } catch (RemoteException e) {
                }
                mHeld = false;
            }
        }
        if (mCount < 0) {
            throw new RuntimeException("WakeLock under-locked " + mTag);
        }
    }
}

release() 中,向下調用了 PMS.BinderServicereleaseWakeLock() 方法:

@Override // Binder call
public void releaseWakeLock(IBinder lock, int flags) {
    if (lock == null) {
        throw new IllegalArgumentException("lock must not be null");
    }
    //檢查權限
    mContext.enforceCallingOrSelfPermission(android.Manifest.permission.
         WAKE_LOCK, null);
    //重置當前線程的IPC標誌
    final long ident = Binder.clearCallingIdentity();
    try {
        //去釋放鎖
        releaseWakeLockInternal(lock, flags);
    } finally {
        //設置新的IPC標誌
        Binder.restoreCallingIdentity(ident);
    }
}

在這個方法中,進行了權限檢查後,就交給下一個方法去處理了,具體代碼如下:

private void releaseWakeLockInternal(IBinder lock, int flags) {
    synchronized (mLock) {
        //查找WakeLock是否存在
        int index = findWakeLockIndexLocked(lock);
        if (index < 0) {
            return;
        }
        WakeLock wakeLock = mWakeLocks.get(index);
            //該flag用來推遲釋放PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK類型的鎖,它會在傳感器感覺不在靠近的時候才釋放該鎖
            if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
                //表示在點亮屏幕前需要等待PSensor返回負值
                mRequestWaitForNegativeProximity = true;
            }
        if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
            mRequestWaitForNegativeProximity = true;
        }
        //取消Binder訃告
        wakeLock.mLock.unlinkToDeath(wakeLock, 0);
        //釋放鎖
        removeWakeLockLocked(wakeLock, index);
    }
}

releaseWakeLockInternal() 中處理時,首先查找WakeLock是否存在,若不存在,直接返回;然後檢查是否帶有影響釋放行為的標誌值,上面已經提到過,目前只有一個值,之後取消了 Binder 的死亡代理,最後調用了 removeWakeLockLocked() 方法:

private void removeWakeLockLocked(WakeLock wakeLock, int index) {
    //從List中移除
    mWakeLocks.remove(index);
    //得到該wakelock中的UidState屬性
    UidState state = wakeLock.mUidState;
    state.mNumWakeLocks--;
    if (state.mNumWakeLocks <= 0 &&
            state.mProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
        //從SpareArray中移除該wakelock的UidState
        //注意,下面的mUidState是SpareArray,而上面的mUidState是wakeLock.mUidState
        mUidState.remove(state.mUid);
    }
    //使用Notifier通知其他應用
    notifyWakeLockReleasedLocked(wakeLock);
    //對帶有ON_AFTER_RELEASE標誌的wakelock進行處理
    applyWakeLockFlagsOnReleaseLocked(wakeLock);
    mDirty |= DIRTY_WAKE_LOCKS;
    //更新電源狀態信息
    updatePowerStateLocked();
}

removeWakeLockLocked() 中,對帶有 ON_AFTER_RELEASE 標誌的 wakelock 進行處理,前面分析過了,該標誌和用戶體驗相關,當有該標誌時,釋放鎖後會亮一段時間後滅屏,這裡來看看 applyWakeLockFlagsOnReleaseLocked(wakeLock) 方法:

/**
     *如果當前釋放的wakelock帶有PowerManager.ON_AFTER_RELEASE標誌,則會屏幕在滅屏時小亮一會兒才會熄滅
     */
    private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
        if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
                && isScreenLock(wakeLock)) {
            //更新用戶活動時間,並帶有PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS標誌,用於延緩滅屏時間
            userActivityNoUpdateLocked(SystemClock.uptimeMillis(),
                    PowerManager.USER_ACTIVITY_EVENT_OTHER,
                    PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
                    wakeLock.mOwnerUid);
        }
    }

最後,又將調用 updatePowerStateLocked() ,其中和 WakeLock 申請和釋放相關的都 updateSuspendBlockerLocked() 中,釋放相關代碼如下:

//滿足兩個條件,釋放"PowerManagerService.WakeLocks"鎖
    if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
        mWakeLockSuspendBlocker.release();
        mHoldingWakeLockSuspendBlocker = false;
    }
    //釋放"PowerManagerService.Display"鎖
    if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
        mDisplaySuspendBlocker.release();
        mHoldingDisplaySuspendBlocker = false;
    }

如果滿足條件,則釋放 SuspendBlocker 鎖。申請 SuspendBlocker 流程已經分析過了,接下來我們分析釋放 SuspendBlocker 流程。在 SuspendBlocker 中釋放鎖如下:

@Override
public void release() {
    synchronized (this) {
        //計數-1
        mReferenceCount -= 1;
        if (mReferenceCount == 0) {
            //調用JNI層進行釋放
            nativeReleaseSuspendBlocker(mName);
        } else if (mReferenceCount < 0) {
            mReferenceCount = 0;
        }
    }
}

在釋放鎖時,如果有多個鎖,實際上是對鎖計數的屬性減1,直到剩餘一個時才會調用JNI層執行釋放操作。JNI層對應的文件也在 com_android_server_power_PowerManagerService.cpp 中,具體代碼如下:

static void nativeReleaseSuspendBlocker(JNIEnv *env, jclass /* clazz */, jstring nameStr) {
    ScopedUtfChars name(env, nameStr);
    release_wake_lock(name.c_str());
}

在JNI層方法中,調用了HAL層的方法,通過文件描述符向 /sys/power/wake_unlock 中寫值完成釋放:

int release_wake_lock(const char* id)
{
    initialize_fds();
    //    ALOGI("release_wake_lock id='%s'n", id);
    if (g_error) return g_error;
    ssize_t len = write(g_fds[RELEASE_WAKE_LOCK], id, strlen(id));
    if (len < 0) {
        return -errno;
    }
    return len;
}

到這裡為止, WakeLock 的釋放流程也就分析完畢了。

通過對 WakeLock 鎖的申請和釋放流程分析,知道實際上通過操作 /sys/power/wake_lock/sys/power/wake_unlock 節點來控制設備的喚醒和休眠,當應用需要喚醒設備時,申請一個 WakeLock 鎖,最終會在 /sys/power/wake_lock 中寫入 SuspendBlocker 鎖名,從而保持了設備的喚醒;當應用執行完操作後,則釋放 WakeLock 鎖,最終會在 /sys/power/wake_unlock 中寫入 SuspendBlocker 鎖名。

整個 wakelock 鎖釋放的時序圖如下:

PowerManagerService分析(三)之WakeLock機制
再談 WakeLock 鎖和 SuspendBlocker

通過對 Wakelock 鎖的申請和釋放,可以知道底層最終的鎖都是 SuspendBlocker 鎖, SuspendBlocker 鎖官方的解釋是: SuspendBlocker 相當於持有部分喚醒鎖,該接口在內部使用,以避免在高級別喚醒鎖機制上引入內部依賴關係。

當上層使用申請了 wakelock 鎖後,最終反映在底層的都是 SuspendBlocker 鎖,從 PowerManager到PowerManagerService再到HAL層 ,其鎖之間的關係如下圖:

PowerManagerService分析(三)之WakeLock機制

再來看下在PMS中實例化的SuspendBloker:

//通過PARTIAL_WAKE_LOCK類型WakeLock鎖使CPU激活
mWakeLockSuspendBlocker = createSuspendBlockerLocked("PowerManagerService.WakeLocks");
//通過屏幕亮屏使得CPU激活
mDisplaySuspendBlocker = createSuspendBlockerLocked("PowerManagerService.Display");
//防止在發送廣播時CPU休眠
mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats,
        mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"),
        mPolicy);

除了這三類鎖,在通過adb命令查看 /sys/power/wake_unlock 還可以看得出其他兩種 SuspendBlocker 鎖:

$ adb shell cat /sys/power/wake_unlock
KeyEvents PowerManagerService.Broadcasts PowerManagerService.Display PowerManagerService.WakeLocks radio-interface
$

mWakeLockSuspendBlocker 鎖在 updateSuspendBlockerLocked() 方法中看出, mWakeLockSuspendBlocker 申請的條件是:

final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);

mWakeLockSummary 在第二篇文章中分析了,是一個匯總了所有 WakeLock 鎖的二進制標誌位,同時在第二篇文章的 updateWakeLockSummaryLocked() 方法中分析了,滿足以下條件時,會將 mWakeLockSummary 置位為 WAKE_LOCK_CPU

//updateWakeLockSummaryLocked()中:
case PowerManager.PARTIAL_WAKE_LOCK:
case PowerManager.DRAW_WAKE_LOCK:
    mWakeLockSummary |= WAKE_LOCK_DRAW;
if (mWakefulness == WAKEFULNESS_AWAKE) {
    mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
} else if (mWakefulness == WAKEFULNESS_DREAMING) {
    mWakeLockSummary |= WAKE_LOCK_CPU;
}
if ((mWakeLockSummary & WAKE_LOCK_DRAW) != 0) {
    mWakeLockSummary |= WAKE_LOCK_CPU;
}

因此,當申請了 PARTIAL_WAKE_LOCK 類型的 WakeLock 鎖、 DRAW_WAKE_LOCK 類型的WakeLock鎖(前提是處於Doze模式)、屏幕處於喚醒、屏保時,都會持有一個 mWakeLockSuspendBlocker 鎖,會在 /sys/power/wake_lock 節點中寫入 」PowerManager.WakeLocks「 ,從而保持設備處於喚醒狀態。

mDisplaySuspendBlocker鎖

updateSuspendBlockerLocked() 方法中, mDisplaySuspendBlocker 申請的條件是:

final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();

private boolean needDisplaySuspendBlockerLocked() {
if (mDisplayPowerRequest.isBrightOrDim()) {//屏幕處於亮或Dim
    //沒有使用PSensor || PSensor值為負值(靠近) || 配置PSensor滅屏時掛起CPU為false 
    if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive
            || !mSuspendWhenScreenOffDueToProximityConfig) {
        return true;
    }
}
......
return false;
}

因此,只要在屏幕處於亮或Dim狀態時,滿足其中之一項,就可以申請 mDisplaySuspendBlocker 鎖,會向 /sys/power/wake_lock 中寫入 」PowerManagerService.Display「

當屏幕處於亮屏或Dim狀態時,一定持有 mDisplaySuspendBlocker

PowerManagerService.Broadcasts

這個類型的 SuspendBlocker 並沒有在PMS中進行實例化,它以構造方法的形式傳入了 Notifier 中,Notifier類相當於是PMS的」中介「,PMS中和其他服務的部分交互通過Notifier進行,還有比如亮屏廣播、滅屏廣播等,都是由PMS交給Notifier來發送,這點在下篇文章中進行分析。因此,如果CPU在廣播發送過程中進入休眠,則廣播無法發送完成,因此,需要一個鎖來保證Notifier中廣播的成功發送,這就是 PowerManagerService.Broadcasts 鎖的作用,當廣播發送完畢後,該鎖立即就釋放了。

PowerManagerService分析(三)之WakeLock機制
PowerManagerService分析(三)之WakeLock機制
PowerManagerService分析(三)之WakeLock機制

長按識別二維碼,領福利

至此,本篇已結束,如有不對的地方,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

PowerManagerService分析(三)之WakeLock機制

如有侵權,請聯繫小編,小編對此深感抱歉,屆時小編會刪除文章,立即停止侵權行為,請您多多包涵。

PowerManagerService分析(三)之WakeLock機制

既然都看到這裡,領兩個紅包在走吧!

以下兩個紅包每天都可以領取

1.支付寶搜索 522398497 ,或掃碼支付寶紅包海報。

PowerManagerService分析(三)之WakeLock機制

支付寶掃一掃,每天領取大紅包

2.微信紅包,微信掃一掃即可領取紅包

PowerManagerService分析(三)之WakeLock機制

微信掃一掃,每天領取微信紅包

相關閱讀

免责声明:本文内容来源于簡書,已注明原文出处和链接,文章观点不代表立场,如若侵犯到您的权益,或涉不实谣言,敬请向我们提出检举。