主頁 > 移動端開發 > 從Android應用層及Framework層的角度分析WakeLock鎖機制

從Android應用層及Framework層的角度分析WakeLock鎖機制

2020-11-21 11:55:10 移動端開發

??從Android應用層及Framework層的角度分析WakeLock鎖機制


本篇博客撰寫思路總結和關鍵點說明:
在這里插入圖片描述
為了更加方便的讀者閱讀博客,通過導讀思維圖的形式將本博客的關鍵點列舉出來,從而方便讀者取舍和閱讀!



前言

??好久沒有寫點偏重實戰型別的博客了,最近一直都在搗鼓原始碼分析和專案相關事情,是時候來點偏重實戰型別的博客了,捯飭點啥實戰的呢,正好前兩天有一個同事詢問我關于Android的WakeLock鎖相關的問題,雖然網上說有不少關于WakeLock鎖相關分析的博客但是都不是很完善,基本只側重了某一個點,這里我們從Android應用層及Framework層的角度出發來對Android的WakeLock鎖機制分析一番,

注意:本篇的介紹是基于Android 7.xx平臺為基礎的,其中涉及的代碼路徑如下:

frameworks/base/core/java/android/os/PowerManager.java
frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
hardware/libhardware_legacy/power/power.c

在開啟本篇博客正式分析前,先奉上關于WakeLock鎖在Android原始碼中的整個層次關系,以便讀者先從整體脈絡上把握一下Android是怎么設計WakeLock鎖相關架構的,

在這里插入圖片描述




一.WakeLock鎖機制概述和設計用途

通常我們在使用新事物之前,都有必有先對其有個大概了解,然后才能決定是否使用以及怎么使用!所以對于WakeLock鎖我們也遵循如上的邏輯進行處理,

1.1 WakeLock鎖機制概述

??在Android的世界中被叫做鎖的有很多種,譬如檔案鎖啊,執行緒鎖啊等!那么這里的WakeLock鎖又是一個啥東東呢,從字面意思理解就是休眠喚醒鎖,但是站在Android的設計者高度以及角度來看WakeLock確實也是一種鎖,它是Android框架層提供的一套機制(需要從kernel到framework層的一起配合),無論內核空間或者用戶空間只要持有了WakeLock鎖,就可以達到控制Android設備運行狀態的目的,這里的設備運行狀態主要指螢屏的常亮滅屏,鍵盤燈的常亮,CPU等保持運行的,如果一定要對WakeLock鎖有一個定義,那就是Android提供的一種機制使Android終端保持一種運行狀態,不至于CPU完全睡眠“清醒”劑!

1.2 WakeLock鎖設計用途

??那么Android為社么要設計這么一種機制呢,也許讀者會說了存在即合理了(哥不帶這么玩的嗎),我們想象一下,當我們吃著火鍋唱著歌,吃得正嗨的時候突然停電了或者沒有菜了,估計讀者此時會有罵娘的沖動了,在Android的現實世界里也會存在著這種情況,在某些場景下我們需要我們的Android終端在滅屏以后后臺任務還依然能夠運行,譬如音樂,后臺下載等,但是通常情況下手機滅屏狀態下保持一段時間后,系統會進入休眠,上述的任務就不能夠完美的執行了,而WakeLock正是為了解決這類問題應運而生的,只要我們申請了WakeLock,那么在釋放WakeLock之前,系統不會進入休眠,即使在滅屏的狀態下,應用要執行的任務依舊不會被打斷,

這里我們從實際使用角度出發簡單概括一下WakeLock鎖的各種使用場景:

  • 滅屏后,要保持CPU一直運轉,然后可以正常執行后臺任務,通常是在Service中執行
  • 通知、鬧鐘來臨之后,想點亮螢屏通知用戶
  • 在某些情況下,應用需要保持螢屏高亮



二.WakeLock鎖分類

??WakeLock鎖機制概述和設計用途我們已經介紹清楚了,這個就好像媒婆給你介紹男女朋友,開場白已經好了,是時候開始真正的了解了不是,WakeLock鎖根據不同的使用場景可以劃分為如下幾類情況(這個沒有必要記住,只要我們能在合適的場景下使用和合適的WakeLock鎖就OK了):

  • 根據WakeLock鎖有效時間劃分:WakeLock可以分為永久鎖和超時鎖,永久鎖表示只要獲取了WakeLock鎖,必須顯式的進行釋放,否則系統會一直持有該鎖,對于這種鎖一定要記得顯示釋放,否則會造成Android終端功耗過大等問題;超時鎖則是在到達給定時間后,若沒有顯示釋放鎖,則會啟動自動釋放WakeLock鎖的,其實作原理為方法內部維護了一個Handler來實作的,

  • 根據釋放原則劃分:WakeLock可以分為計數鎖和非計數鎖,默認為計數鎖,如果一個WakeLock物件為計數鎖,則一次申請必須對應一次釋放;如果為非計數鎖,則不管申請多少次,一次就可以釋放該WakeLock,如果我們在創建的時候不特殊指定通常創建的是非計數鎖,

在創建了 PowerManager.WakeLock 后,有兩種機制,第一種是不計數鎖機制,另一種是計數鎖機制,可以通過 setReferenceCounted(boolean value) 來指定,一般默認為計數機制,這兩種機制的區別在于,前者無論 acquire() 了多少次,只要通過一次 release()即可解鎖,而后者正真解鎖是在( --count == 0 )的時候,同樣當 (count == 0) 的時候才會去申請加鎖,其他情況 isHeld 狀態是不會改變的,所以 PowerManager.WakeLock 的計數機制并不是正真意義上的對每次請求進行申請/釋放每一把鎖,它只是對同一把鎖被申請/釋放的次數進行了統計再正真意義上的去操作,一下進行了永久鎖的測驗: 從測驗我們可以看到使用計數和計數鎖的區別,

  • 根據持鎖層級劃分:分為用戶空間層透過PowerManager拿鎖,以及kernel層直接持有Wakelock鎖(這里小伙們先有一個概念,后邊的分析就知道了)



三.WakeLock鎖類常用的方法和變數

??在正式開始介紹WakeLock鎖的使用前,我們先從原始碼角度介紹一下WakeLock鎖,它是一個PowerManager的一個內部類,其類圖如下:

在這里插入圖片描述

其常用的的幾個方法和功能如下所示:

	public void setReferenceCounted(boolean value)//設定鎖的型別是否為計數鎖,和我們前面介紹的鎖的型別對應
	public void acquire()//獲取鎖
	public void acquire(long timeout);//獲取鎖的型別為計時鎖
	public void release();//釋放獲取的鎖
	public void release(int flags);//釋放所示帶標志,只有唯一一個取值為RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY
	public boolean isHeld();//判斷當前的WakeLock物件是否只持有鎖



四.WakeLock鎖的使用以及相關的引數

??好嗎,前面啰嗦了一大截!讀者也許會說這個和我們的標題是不是有沖突了,說好的實戰,實戰呢(本人承諾絕對不掛羊頭賣狗肉)!這不實戰就來了,我們這里從上層應用以及Native層使用來介紹!


4.1 Android應用層使用WakeLock鎖

Android應用層使用WakeLock鎖的程序比較簡單,可以按照如下流程進行:

  • 首先在AndroidManifest.xml中申請WakeLock鎖相關的權限,如下:
<uses-permission android:name="android.permission.WAKE_LOCK" />
  • 接著根據具體應用場景,申請WakeLock鎖,并使用WakeLock鎖(一定不要濫用),如下:
package com.example.test;
import android.content.Context;
import android.os.PowerManager;
public class WakeLockUtils {

    private static WakeLockUtils instance = null;
    private PowerManager mPowerManager = null;
    private PowerManager.WakeLock mWakeLock = null;

    public static WakeLockUtils getInstance(Context mContext,
            final int levelAndFlags, final String tag) {
        if (instance == null) {
            synchronized (WakeLockUtils.class) {
                if (instance == null) {
                    instance = new WakeLockUtils(mContext, levelAndFlags, tag);
                }
            }
        }
        return instance;
    }

    private WakeLockUtils(Context mContext, final int levelAndFlags,
            final String tag) {

        mPowerManager = (PowerManager) mContext
                .getSystemService(Context.POWER_SERVICE);//獲取PowerManager建立和PowerManagerService的Binder通信通道
        mWakeLock = mPowerManager.newWakeLock(levelAndFlags, tag);//獲取鎖
    }

    //持有鎖
    public void accquire(final long timeout){
        if(timeout >= 0){
            mWakeLock.acquire(timeout);
        }else{
            mWakeLock.acquire();
        }
    }
    
    //釋放鎖
    public void release(){
        if(mWakeLock.isHeld()){//判斷是否持有鎖
            mWakeLock.release();
        }
    }    
}

上述的流程肯定是難不倒各位讀者的了,但是這里我們需要重點關注的是newWakeLock(final int levelAndFlags, final String tag)這個方法的使用,因為傳入引數的不同那么獲取的WakeLock鎖就不同,這我們放在后面再細說!
如果覺得上面的封裝有點復雜,那么下面的代碼可能會更加得直接明了:

    private void wakeLockFun(){        
        PowerManager mPowerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);        
        PowerManager.WakeLock mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "WakeLock_FUN");
        
        mWakeLock.acquire();//獲取鎖      
        mWakeLock.release();//釋放鎖
    }

4.2 Android的Native層使用WakeLock鎖

Android的Native層使用WakeLock鎖也不復雜,這里就不來什么具體步驟了,主要就是對wakelock的節點直接操作,寫入資料就OK了!

#define WAKE_LOCK  "/sys/power/wake_lock"
#define RELEASE_WAKE_LOCK "/sys/power/wake_unlock"
static int gfd_wake_lock = -1;
static int gfd_wake_unlock = -1;
const char * wake_lock_cmd = "rpc_wake_lock_timeout";
static long long wake_lock_timeout_s = 10000000000;

//這里直接操作節點,向/sys/power/wake_lock寫入資料
static int wake_lock_fun(const char* id,int lock_type = 0, int time = 0)
{
    if(gfd_wake_lock <=0)
    {
        gfd_wake_lock = open(WAKE_LOCK, 0x02);
    }

    if (gfd_wake_lock < 0)
    {
        fprintf(stderr, "fatal error opening \"%s\"\n", WAKE_LOCK);
        LOGE(TAG,"fatal error opening \"%s\"\n", WAKE_LOCK);
        return -1;
    }

    if(strlen(id) > 64)
    {
        LOGE(TAG, "rpc_wake_lock failed");
        return -83;
    }

    int result = 0;
    char wake_lock_str[256];
    memset(wake_lock_str, 0, sizeof(wake_lock_str));
    //默認時間是10秒鐘
    if(lock_type == 0)
    {
        sprintf(wake_lock_str,"%s  %lld", id, wake_lock_timeout_s);
    }
    //此處表示是我們要執行自動釋放鎖
    else
    {
        long actual_time = 10000 + time; //計算出毫秒
        char* ns_suffix = "000000"; //毫秒轉納秒的后綴
        sprintf(wake_lock_str,"%s %ld%s", id, actual_time, ns_suffix);
    }
    result =  write(gfd_wake_lock, wake_lock_str, strlen(wake_lock_str));
    if(result < 0)
    {
        LOGE(TAG,"rpc_wake_lock The error result = %d,errno is = %d\n",errno);
        return result;
    }
    return 0;
}




static int wake_unlock_fun(const char* id)
{
    if(gfd_wake_unlock <= 0)
    {
        gfd_wake_unlock = open(RELEASE_WAKE_LOCK, 0x02);
    }
    if (gfd_wake_unlock < 0)
    {
        fprintf(stderr, "fatal error opening \"%s\"\n", RELEASE_WAKE_LOCK);
        LOGE(TAG,"fatal error opening \"%s\"\n", RELEASE_WAKE_LOCK);
        return -1;
    }

    if(strlen(id) > 64)
    {
        LOGE(TAG, "rpc_wake_unlock failed");
        return -83;
    }

    int result = 0;
    result = write(gfd_wake_unlock, id, strlen(id));
    if(result < 0)
    {
        LOGE(TAG,"rpc_wake_unlock The error result = %d,errno is = %d\n",errno);
        return result;
    }
    return 0;
}

int main(void){
	wake_lock_fun("native_wake_lock", 110000000000)sleep(5000000000);
	wake_unlock_fun("native_wake_lock");
}

4.3 Android應用層獲取WakeLock鎖時引數詳解

還記得在3.1章節的時候說newWakeLock這個方法的使用是,因為傳入引數的不同那么獲取的WakeLock鎖就不同嗎,其中具體的指代的是levelAndFlags這個引數的值,和tag沒有啥關系(這個只是相當于用戶對這個WakeLock鎖的一個別名而已)!通過獲取不同的WakeLock鎖,從而影響CPU,螢屏,以及鍵盤燈的狀態的目的!

//[PowerManager.java]
	public WakeLock newWakeLock(final int levelAndFlags, final String tag) {
		validateWakeLockParameters(levelAndFlags, tag);
		return new WakeLock(levelAndFlags, tag,
				this.mContext.getOpPackageName());
	}

上述的場景也比較也比較好理解,譬如有的App界面希望一直不要滅屏的運行著,有些App可以滅屏然后只需要保持CPU運轉就可以了,那這里的levelAndFlags引數既然這么重要,看來我們有必要深挖挖了!

如果讀者只是想快速的獲取想要的鎖,那么看這里就夠了,而不需要下面的從原始碼角度理解了,但是本人還是建議讀者從原始碼角度出發理解,畢竟別人給你的永遠是別人的不是你的!

各種鎖的型別對CPU 、螢屏、鍵盤的影響:
PARTIAL_WAKE_LOCK:保持CPU 運轉,螢屏和鍵盤燈有可能是關閉的,
SCREEN_DIM_WAKE_LOCK:保持CPU 運轉,允許保持螢屏顯示但有可能是灰的,允許關閉鍵盤燈
SCREEN_BRIGHT_WAKE_LOCK:保持CPU 運轉,允許保持螢屏高亮顯示,允許關閉鍵盤燈
FULL_WAKE_LOCK:保持CPU 運轉,保持螢屏高亮顯示,鍵盤燈也保持亮度
ACQUIRE_CAUSES_WAKEUP:強制使螢屏亮起,這種鎖主要針對一些必須通知用戶的操作.
ON_AFTER_RELEASE:當鎖被釋放時,保持螢屏亮起一段時間

從newWakeLock的第一個入參的名稱就可以看出,它分為兩部分即level(級別)和Flags(標記),我們來一一捯飭捯飭一下!

4.3.1 WakeLock鎖級別

WakeLock鎖的級別都被定義在PowerManager中,它的每個引數取值有何意思,或者蹊蹺呢,讓我們來探究一番(這里留下英文注釋,讀者可以自行理解,在最后我會加上自己的理解!最后可以比對比對,我們的理解是否一致)!

//[PowerManager.java]
    /**
     * Wake lock level: Ensures that the CPU is running; the screen and keyboard
     * backlight will be allowed to go off.
     * <p>
     * If the user presses the power button, then the screen will be turned off
     * but the CPU will be kept on until all partial wake locks have been released.
     * </p>
     */
    public static final int PARTIAL_WAKE_LOCK = 0x00000001;

    /**
     * Wake lock level: Ensures that the screen is on (but may be dimmed);
     * the keyboard backlight will be allowed to go off.
     * <p>
     * If the user presses the power button, then the {@link #SCREEN_DIM_WAKE_LOCK} will be
     * implicitly released by the system, causing both the screen and the CPU to be turned off.
     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
     * </p>
     *
     * @deprecated Most applications should use
     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
     * of this type of wake lock, as it will be correctly managed by the platform
     * as the user moves between applications and doesn't require a special permission.
     */
    @Deprecated
    public static final int SCREEN_DIM_WAKE_LOCK = 0x00000006;

    /**
     * Wake lock level: Ensures that the screen is on at full brightness;
     * the keyboard backlight will be allowed to go off.
     * <p>
     * If the user presses the power button, then the {@link #SCREEN_BRIGHT_WAKE_LOCK} will be
     * implicitly released by the system, causing both the screen and the CPU to be turned off.
     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
     * </p>
     *
     * @deprecated Most applications should use
     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
     * of this type of wake lock, as it will be correctly managed by the platform
     * as the user moves between applications and doesn't require a special permission.
     */
    @Deprecated
    public static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a;

    /**
     * Wake lock level: Ensures that the screen and keyboard backlight are on at
     * full brightness.
     * <p>
     * If the user presses the power button, then the {@link #FULL_WAKE_LOCK} will be
     * implicitly released by the system, causing both the screen and the CPU to be turned off.
     * Contrast with {@link #PARTIAL_WAKE_LOCK}.
     * </p>
     *
     * @deprecated Most applications should use
     * {@link android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON} instead
     * of this type of wake lock, as it will be correctly managed by the platform
     * as the user moves between applications and doesn't require a special permission.
     */
    @Deprecated
    public static final int FULL_WAKE_LOCK = 0x0000001a;

    /**
     * Wake lock level: Turns the screen off when the proximity sensor activates.
     * <p>
     * If the proximity sensor detects that an object is nearby, the screen turns off
     * immediately.  Shortly after the object moves away, the screen turns on again.
     * </p><p>
     * A proximity wake lock does not prevent the device from falling asleep
     * unlike {@link #FULL_WAKE_LOCK}, {@link #SCREEN_BRIGHT_WAKE_LOCK} and
     * {@link #SCREEN_DIM_WAKE_LOCK}.  If there is no user activity and no other
     * wake locks are held, then the device will fall asleep (and lock) as usual.
     * However, the device will not fall asleep while the screen has been turned off
     * by the proximity sensor because it effectively counts as ongoing user activity.
     * </p><p>
     * Since not all devices have proximity sensors, use {@link #isWakeLockLevelSupported}
     * to determine whether this wake lock level is supported.
     * </p><p>
     * Cannot be used with {@link #ACQUIRE_CAUSES_WAKEUP}.
     * </p>
     */
    public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020;

    /**
     * Wake lock level: Put the screen in a low power state and allow the CPU to suspend
     * if no other wake locks are held.
     * <p>
     * This is used by the dream manager to implement doze mode.  It currently
     * has no effect unless the power manager is in the dozing state.
     * </p><p>
     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
     * </p>
     *
     * {@hide}
     */
    public static final int DOZE_WAKE_LOCK = 0x00000040;

    /**
     * Wake lock level: Keep the device awake enough to allow drawing to occur.
     * <p>
     * This is used by the window manager to allow applications to draw while the
     * system is dozing.  It currently has no effect unless the power manager is in
     * the dozing state.
     * </p><p>
     * Requires the {@link android.Manifest.permission#DEVICE_POWER} permission.
     * </p>
     *
     * {@hide}
     */
    public static final int DRAW_WAKE_LOCK = 0x00000080;

    /**
     * Mask for the wake lock level component of a combined wake lock level and flags integer.
     *
     * @hide
     */
    public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff;

好了上面原版的英文注釋給出了,先給讀者5分鐘看看,我們接著引入我對上述 WakeLock鎖級別的理解,如下:

//[PowerManager.java]
	/*
		當我們創建的WakeLock持有該型別的鎖時候,即使我們按power按鍵使我們的Android
		終端熄滅螢屏和鍵盤燈,CPU也不會進入休眠狀態從而達到保持后臺任務完美運行的目的
		這種模式也是我們最經常用到的,如果對該鎖特點用一句話概述就是:
		保持CPU運轉,但是鍵盤燈和螢屏可以關閉(人為,系統控制的譬如設定了多久沒有用戶操作)
        
        注意:螢屏和鍵盤燈不受該鎖影響,可以正常熄滅不會導致該鎖釋放
	*/
	public static final int PARTIAL_WAKE_LOCK = 0x00000001;	
	
	
	/*
		注意:該WakeLock鎖級別已經被標注為廢棄
		當我們創建的WakeLock持有該型別的鎖時候,會保持螢屏亮著(此時螢屏也
		可能會進入dimed狀態,即我們螢屏在滅屏前的一種漸暗的狀態),此時鍵盤可能會關閉
		但是但是但是,當用戶按power按鍵熄滅螢屏后也會釋放該WakeLock鎖,從而CPU會進入休眠狀態
		
		如果對該鎖特點用一句話概述就是:
		保持CPU運轉,螢屏會常亮(但是可能會進入漸暗狀態),鍵盤燈可能關閉
		
    	假如Android App應用想通過此種鎖保持螢屏常亮,Android系統推薦如下方法(當Activity或view可見時,螢屏才保持常亮):
        在Activity.onCreate()中:  getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        或在xml布局中: android:keepScreenOn="true"
        或對View設定:  view.setKeepScreenOn(true);
  
        螢屏相關的其它FLAG:
        WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD    解鎖螢屏
        WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON      點亮螢屏
        WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED    螢屏鎖定時也能顯示
        WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON   螢屏打開時允許鎖屏
	*/
    @Deprecated
    public static final int SCREEN_DIM_WAKE_LOCK = 0x00000006;


	/*
		注意:該WakeLock鎖級別已經被標注為廢棄
		并且該型別的鎖和前面的SCREEN_DIM_WAKE_LOCK非常類似,唯一的區別就是持有該鎖螢屏
		會一直保持最亮的模式,不會進入漸暗的模式
		并且當用戶按power按鍵熄滅螢屏后也會釋放該WakeLock鎖,從而CPU會進入休眠狀態
		
		如果對該鎖特點用一句話概述就是:
		保持CPU運轉,螢屏會常亮,鍵盤燈可能關閉

		并且Android官方也是推薦替代的方案和SCREEN_DIM_WAKE_LOCK一樣,這里就復制粘貼了
	*/
    @Deprecated
    public static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a;



	/*
		注意:該WakeLock鎖級別已經被標注為廢棄
		持有該類鎖的最大特點是鍵盤燈和螢屏保持常亮的狀態
		并且當用戶按power按鍵熄滅螢屏后也會釋放該WakeLock鎖,從而CPU會進入休眠狀態

		如果對該鎖特點用一句話概述就是:
		保持CPU運轉,螢屏會常亮,鍵盤燈都常亮

		并且Android官方也是推薦替代的方案和SCREEN_BRIGHT_WAKE_LOCK一樣,這里就復制粘貼了
	*/
	@Deprecated
	public static final int FULL_WAKE_LOCK = 0x0000001a;

	/*
		該鎖比較特殊,用于和距離傳感器配合使用
		持有該型別鎖的特點是:當距離傳感器檢測到有物體(包括)靠近,會將螢屏熄滅
		相反,當檢測到物體遠離后會點亮螢屏
		上述鎖不會影響終端的正常進入休眠狀態,只有當前螢屏由該wakelock鎖滅掉,才不會進入休眠狀態
		應用場景在通話中比較常見		
	*/
	public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020;

	/**************************************************************/
	//上面的幾個鎖型別都是公開的,第三方App可以呼叫到的,下面的兩個比較特殊是隱藏的
	
	/*
		如果持有該鎖,則會使螢屏處于DOZE狀態,同時允許CPU掛起,該鎖用于DreamManager實作Doze模式,
		如SystemUI的DozeService
		
		Doze模式是在Android M中,Google就引入了Doze模式,它定義了一種全新的、低能耗的狀態,
 		在該狀態,后臺只有部分任務被允許運行,其它任務都被強制停止
	*/
	public static final int DOZE_WAKE_LOCK = 0x00000040;

	/*
		如果持有該鎖,則會使設備保持喚醒狀態,以進行繪制螢屏,該鎖常用于WindowManager中,
		允許應用在系統處于Doze狀態下時進行繪制
	*/
	public static final int DRAW_WAKE_LOCK = 0x00000080;

上面咔咔一頓講,自我感覺有點啰嗦了,但是我覺得吧,既然是寫博客就應該整清楚,不能搞那種意猶未盡個的感覺,我們對上面的幾種WakeLock鎖整理一番,標注重點:

  • 如果想保持CPU一直運轉不進入休眠(那怕是用戶按power鍵主動滅屏),請使用PARTIAL_WAKE_LOCK級別型別鎖

  • 如果是想保持螢屏常量,Android建議盡量使用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)的方法,而不是使用WakeLock型別的鎖了

4.3.2 WakeLock鎖幾個常用的flag標志

通過前面我們知道WakeLock鎖不僅有各種級別,而且在同一個級別的時候采取不同的flag標志其表現形式也是不同的,讓我們來探究一番(這里留下英文注釋,讀者可以自行理解,在最后我會加上自己的理解!最后可以比對比對,我們的理解是否一致)!

//[PowerManager.java]
    /**
     * Mask for the wake lock level component of a combined wake lock level and flags integer.
     *
     * @hide
     */
    public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff;

    /**
     * Wake lock flag: Turn the screen on when the wake lock is acquired.
     * <p>
     * Normally wake locks don't actually wake the device, they just cause
     * the screen to remain on once it's already on.  Think of the video player
     * application as the normal behavior.  Notifications that pop up and want
     * the device to be on are the exception; use this flag to be like them.
     * </p><p>
     * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
     * </p>
     */
    public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;

    /**
     * Wake lock flag: When this wake lock is released, poke the user activity timer
     * so the screen stays on for a little longer.
     * <p>
     * Will not turn the screen on if it is not already on.
     * See {@link #ACQUIRE_CAUSES_WAKEUP} if you want that.
     * </p><p>
     * Cannot be used with {@link #PARTIAL_WAKE_LOCK}.
     * </p>
     */
    public static final int ON_AFTER_RELEASE = 0x20000000;

    /**
     * Wake lock flag: This wake lock is not important for logging events.  If a later
     * wake lock is acquired that is important, it will be considered the one to log.
     * @hide
     */
    public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;

    /**
     * Flag for {@link WakeLock#release WakeLock.release(int)}: Defer releasing a
     * {@link #PROXIMITY_SCREEN_OFF_WAKE_LOCK} wake lock until the proximity sensor
     * indicates that an object is not in close proximity.
     */
    public static final int RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY = 1;

好了上面原版的英文注釋給出了,先給讀者5分鐘看看,我們接著引入我對上述 WakeLock的flag標志的的理解,如下:

//[PowerManager.java]
	
	/*
		注意此變數被標注為hide,則代表只能在系統內部使用
		用于根據flag判斷Wakelock的級別,如:
		如內部方法中的validateWakeLockParameters判定WakeLock傳入的的是否正確
		levelAndFlags & WAKE_LOCK_LEVEL_MASK
	*/
	public static final int WAKE_LOCK_LEVEL_MASK = 0x0000ffff;

	/*
		通常wakelock鎖并不會真的主動去點亮螢屏,它們只會導致螢屏打開后將保持打開狀態
		如果帶有這個flag,則會在申請wakelock時就點亮螢屏,如常見通知來時螢屏亮,該flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用
		這個flag標志通常用于,當我們用戶在沒有進入深休眠時,接到一個廣播或者通知,主動來電量螢屏提醒用戶某些通知

	*/
	public static final int ACQUIRE_CAUSES_WAKEUP = 0x10000000;

	/*
		
		當我們盛情的鎖,在被釋放鎖時(主動或者被動),如果wakelock帶有該標志,則會小亮一會再滅屏(注意并不是說會點亮螢屏,而是說如果釋放鎖的時候螢屏是亮的),
		該flag不能和PowerManager.PARTIAL_WAKE_LOCE一起使用,
	*/
	public static final int ON_AFTER_RELEASE = 0x20000000;

	/*	
		和其他標記不同,該標記是作為release()方法的引數,且僅僅用于釋放PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK型別的
	鎖,如果帶有該引數,則會延遲釋放鎖,直到傳感器不再感到物件接近
	*/
	public static final int UNIMPORTANT_FOR_LOGGING = 0x40000000;

關于WakeLock鎖的flag標志位的不是很多,這幾個flag主要起輔助的作用,WakeLock鎖的關鍵還是由它的(level)級別決定的,那么WakeLock鎖的級別和flag標志放在一起應該怎么使用呢,當然是通過"|"的操作了,實體如下:

		mWakeLock = powerMg.newWakeLock(PowerManager.FULL_WAKE_LOCK
				| PowerManager.ACQUIRE_CAUSES_WAKEUP
				| PowerManager.ON_AFTER_RELEASE, "mWakeLock");

4.4 WakeLock各種型別鎖以及特點

經過前面的一番猛烈攻擊,我想讀者對于WakeLock的各種鎖應該有了初步的了解了,但是估計也還是有點云里霧里的,秉著擼到到底的原則,我們乘熱打鐵,將各種WakeLock各種型別鎖整理成表格的形式(主要是持有該鎖時,CPU作業狀態,螢屏表現,鍵盤燈等的表現形式),突出重點直搗黃龍!

levelAndFlagsCPU運行狀態螢屏狀態鍵盤燈狀態鎖的釋放
是否受Power
按鍵影響
備注以及需要注意地地方
PARTIAL_WAKE_LOCK OnOn/OffOn/Off沒有影響該鎖比較特殊,是系列鎖中釋放狀態唯一一個
不受power按鍵影響的,必須主動或者待持鎖時間到來才會釋放
SCREEN_DIM_WAKE_LOCK OnDim(低亮度)OffreleaseAPI17以后已經被棄用,改用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
用來替代該型別的鎖
SCREEN_BRIGHT_WAKE_LOCK OnBrightOffrelease同上
SCREEN_BRIGHT_WAKE_LOCK OnBrightOffrelease同上
FULL_WAKE_LOCK OnBrightBrightrelease同上
PROXIMITY_SCREEN_OFF_WAKE_LOCK On/OfffBright/Offrelease不能和ACQUIRE_CAUSES_WAKEUP一起使用
DOZE_WAKE_LOCK On/OffOffrelease@hide標注,允許在doze狀態下使cpu進入suspend狀態,僅在doze狀態下有效,需要android.Manifest.permission.DEVICE_POWER權限
DRAW_WAKE_LOCK On/OffOffNo@hide,允許在doze狀態下進行螢屏繪制,僅在doze狀態下有效,需要DEVICE_POWER權限
ACQUIRE_CAUSES_WAKEUPWakelock 標記,一般情況下,獲取wakelock并不能喚醒設備,加上這個標志后,申請wakelock后也會喚醒螢屏,如通知、鬧鐘… 不能和PARTIAL_WAKE_LOCK一起使用
ON_AFTER_RELEASEWakelock 標記,當釋放該標記的鎖時,會亮一小會再滅屏(注意必須是釋放之前螢屏的狀態是亮的,而不是主動點亮),并且不能和PARTIAL_WAKE_LOCK一起使用

是不是有點整懵了的感覺,其實我們只需記住一條一切只要從實際出發,抓取重點即可:

  • 假如想后臺的某個任務一直運行申請PARTIAL_WAKE_LOCK的鎖即可
  • 假如是想螢屏常量,使用getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)即可

總之對于上述鎖的型別,我們在獲得WakeLock物件后,可以根據自己的需求來申請不同形式的鎖,從而達到我們的最終目的即可!

這里感徑訓是有必要對上述表格解釋一下:
其中CPU運行狀態:表示持有該鎖的時候CPU會不會進入休眠狀態
螢屏狀態:表示持有該型別鎖的時候,螢屏的表現形式,譬如亮,滅,或者亮的時候狀態
鍵盤燈狀態:表示持有該型別鎖的時候,鍵盤燈的表現狀態
鎖的釋放是否受Power按鍵影響:表示按power按鍵,是否會導致持有的WakeLock鎖被釋放

對于應用開發者來說,上述的鎖只能申請非@hide的鎖,即PARTIAL_WAKE_LOCK、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK、FULL_WAKE_LOCK四類,這個需要留意一下




五.WakeLock鎖呼叫流程分析

本來是將呼叫流程放在這篇博客中進行相關的分析,然后一網打盡的!但是分析分析著,一看這內容有點多啊,所以這個章節放在后面單獨成一個博客來分析!但是我們可以簡單的看下其整體框架呼叫流程圖,如下:

在這里插入圖片描述




六.WakeLock相關問題的debug除錯方法

在本篇博客的第三節中我們有詳細介紹了WakeLock鎖的使用方法,那么在使用使用程序中,我們除開根據實際使用效果確定WakeLock鎖是否有生效外,還有沒有更加快捷的方法呢?這個肯定有,這里就給大伙安排上!


6.1 應用層使用WakeLock鎖的Debug除錯

只能說Android為了我們的開發能流暢的進行,為我們提供了強大的命令工具dumpsys,我們可以借助它實作監控應用層WakeLock鎖的功能,我們先看下沒有執行任何鎖的前提下的情況:

λ adb shell dumpsys power | grep -i wake
  mWakefulness=Dozing
  mWakefulnessChanging=false
  mWakeLockSummary=0x40
  mLastWakeTime=4068623 (137029 ms ago)
  mHoldingWakeLockSuspendBlocker=false
  mWakeUpWhenPluggedOrUnpluggedConfig=true
  mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
  mDoubleTapWakeEnabled=false
Wake Locks: size=1			
  DOZE_WAKE_LOCK                 'DreamManagerService' ACQ=-1m16s43ms (uid=1000 pid=4346)
  PowerManagerService.WakeLocks: ref count=0

可以看到上述持有一個DOZE_WAKE_LOCK 型別的鎖,這個后面會介紹到!

下面我們來申請一個鎖,執行代碼如下:

public class WakeLockTest extends Activity {
    PowerManager.WakeLock mWakeLock;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.viewtest);

        PowerManager mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "WakeLock_FUN");
        mWakeLock.acquire();// 獲取鎖
    }

    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        if (mWakeLock.isHeld())
            mWakeLock.release();// 獲取鎖
    }
}

我們來看下此時的實際情況如何:

λ adb shell dumpsys power | grep -i wake
  mWakefulness=Awake
  mWakefulnessChanging=false
  mWakeLockSummary=0x1
  mLastWakeTime=4353954 (17858 ms ago)
  mHoldingWakeLockSuspendBlocker=true
  mWakeUpWhenPluggedOrUnpluggedConfig=true
  mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=false
  mDoubleTapWakeEnabled=false
Wake Locks: size=1
  PARTIAL_WAKE_LOCK              'WakeLock_FUN' ACQ=-6s372ms (uid=1000 pid=7182)
  PowerManagerService.WakeLocks: ref count=1

這里可以看到申請到了一個PARTIAL_WAKE_LOCK型別的鎖,并且其tag為WakeLock_FUN’和我們代碼申請的對上了,


6.2 Native層使用WakeLock鎖的Debug除錯

這個就沒有好說的了,簡單明了直接通過cat查看wake_lock節點即可,如下:

xxx:/ # cat /sys/power/wake_lock
PowerManagerService.Display native_wake_lock

6.3 Android系統層的WakeLock鎖的Debug除錯

系統層的WakeLock鎖Debug級別除錯起來就比較復雜了,這個涉及的知識層面比較多,并且需要對PowerManagerService有比較深入的了解和掌握了,這個屬于高階的范疇了,但是我們的dumpsy power依然能排上用場,并且最好將PowerManagerService中的除錯DEBUG打開!

關于這個我就不過多的介紹了,感興趣的讀者可以詳讀如何分析WakeLock持鎖問題




寫在最后

??到這里,本篇從Android應用層及Framework層的角度分析WakeLock鎖機制就到這里了,通過這篇博客我想讀者應該對WakeLock有了一個比較深入的了解了,無論是從它的設計角度出發,使用場景,具體的使用應該都是得心應手的了,限于篇幅這里還有所欠缺的是WakeLock鎖的呼叫流程的詳細分析,這個我們將會在后面的博客中補上,如果本篇博客對你有所幫助,歡迎點贊和評論,當然也可以拍磚,總之歡迎留下你的腳步!

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/225882.html

標籤:其他

上一篇:數星星(前綴和)

下一篇:微信瀏覽器通過WeixinJSBridge禁止右上角分享,安卓、iOS適配。(vue開發)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more