• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

从Android应用层及Framework层的角度分析WakeLock锁机制

互联网 diligentman 2周前 (11-21) 5次浏览

  从Android应用层及Framework层的角度分析WakeLock锁机制

本篇博客编写思路总结和关键点说明:
从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锁相关架构的。

从Android应用层及Framework层的角度分析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的一个内部类,其类图如下:

从Android应用层及Framework层的角度分析WakeLock锁机制

其常用的的几个方法和功能如下所示:

	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 = %dn",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 = %dn",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工作状态,屏幕表现,键盘灯等的表现形式),突出重点直捣黄龙!

levelAndFlags CPU运行状态 屏幕状态 键盘灯状态 锁的释放
是否受Power
按键影响
备注以及需要注意地地方
PARTIAL_WAKE_LOCK On On/Off On/Off 没有影响 该锁比较特殊,是系列锁中释放状态唯一一个
不受power按键影响的,必须主动或者待持锁时间到来才会释放
SCREEN_DIM_WAKE_LOCK On Dim(低亮度) Off release API17以后已经被弃用,改用WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
用来替代该类型的锁
SCREEN_BRIGHT_WAKE_LOCK On Bright Off release 同上
SCREEN_BRIGHT_WAKE_LOCK On Bright Off release 同上
FULL_WAKE_LOCK On Bright Bright release 同上
PROXIMITY_SCREEN_OFF_WAKE_LOCK On/Offf Bright/Off release 不能和ACQUIRE_CAUSES_WAKEUP一起使用
DOZE_WAKE_LOCK On/Off Off release @hide标注,允许在doze状态下使cpu进入suspend状态,仅在doze状态下有效,需要android.Manifest.permission.DEVICE_POWER权限
DRAW_WAKE_LOCK On/Off Off No @hide,允许在doze状态下进行屏幕绘制,仅在doze状态下有效,需要DEVICE_POWER权限
ACQUIRE_CAUSES_WAKEUP Wakelock 标记,一般情况下,获取wakelock并不能唤醒设备,加上这个标志后,申请wakelock后也会唤醒屏幕。如通知、闹钟… 不能和PARTIAL_WAKE_LOCK一起使用
ON_AFTER_RELEASE Wakelock 标记,当释放该标记的锁时,会亮一小会再灭屏(注意必须是释放之前屏幕的状态是亮的,而不是主动点亮),并且不能和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锁调用流程分析

本来是将调用流程放在这篇博客中进行相关的分析,然后一网打尽的!但是分析分析着,一看这内容有点多啊,所以这个章节放在后面单独成一个博客来分析!但是我们可以简单的看下其整体框架调用流程图,如下:

从Android应用层及Framework层的角度分析WakeLock锁机制
总之WakeLock锁从用户空间下发设置操作,然后进入kernel空间,最终写入到了/sys/power/wake_lock文件节点,和我们的章节4.2的Native层使用WakeLock锁徐途同归!

并且我们这里需要注意地是上层应用获取锁时传递个wake_lock的信息为PowerManagerService.WakeLocks

//[power.c]
enum {
    ACQUIRE_PARTIAL_WAKE_LOCK = 0,
    RELEASE_WAKE_LOCK,
    OUR_FD_COUNT
};

const char * const OLD_PATHS[] = {
    "/sys/android_power/acquire_partial_wake_lock",
    "/sys/android_power/release_wake_lock",
};

const char * const NEW_PATHS[] = {
    "/sys/power/wake_lock",
    "/sys/power/wake_unlock",
};

//XXX static pthread_once_t g_initialized = THREAD_ONCE_INIT;
static int g_initialized = 0;
static int g_fds[OUR_FD_COUNT];
static int g_error = -1;

static int
open_file_descriptors(const char * const paths[])
{   
    int i;
    for (i=0; i<OUR_FD_COUNT; i++) {
        int fd = open(paths[i], O_RDWR | O_CLOEXEC);
        if (fd < 0) { 
            g_error = -errno;
            fprintf(stderr, "fatal error opening "%s": %sn", paths[i],
                strerror(errno));
            return -1;
        }
        g_fds[i] = fd;
    }

    g_error = 0;
    return 0;
}

static inline void
initialize_fds(void)
{
    // XXX: should be this:
    //pthread_once(&g_initialized, open_file_descriptors);
    // XXX: not this:
    if (g_initialized == 0) {
        if(open_file_descriptors(NEW_PATHS) < 0)
            open_file_descriptors(OLD_PATHS);
        g_initialized = 1;
    }
}

//获取锁
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;
}

//释放锁
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相关问题的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锁的调用流程的详细分析,这个我们将会在后面的博客中补上。如果本篇博客对你有所帮助,欢迎点赞和评论,当然也可以拍砖,总之欢迎留下你的脚步!


喜欢 (0)