从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锁相关架构的。
一.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_PROXIMITYpublic 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", 1, 10000000000);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.*/@Deprecatedpublic 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.*/@Deprecatedpublic 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.*/@Deprecatedpublic 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 屏幕打开时允许锁屏*/@Deprecatedpublic static final int SCREEN_DIM_WAKE_LOCK = 0x00000006;/*注意:该WakeLock锁级别已经被标注为废弃并且该类型的锁和前面的SCREEN_DIM_WAKE_LOCK非常类似,唯一的区别就是持有该锁屏幕会一直保持最亮的模式,不会进入渐暗的模式并且当用户按power按键熄灭屏幕后也会释放该WakeLock锁,从而CPU会进入休眠状态如果对该锁特点用一句话概述就是:保持CPU运转,屏幕会常亮,键盘灯可能关闭并且Android官方也是推荐替代的方案和SCREEN_DIM_WAKE_LOCK一样,这里就复制粘贴了*/@Deprecatedpublic static final int SCREEN_BRIGHT_WAKE_LOCK = 0x0000000a;/*注意:该WakeLock锁级别已经被标注为废弃持有该类锁的最大特点是键盘灯和屏幕保持常亮的状态并且当用户按power按键熄灭屏幕后也会释放该WakeLock锁,从而CPU会进入休眠状态如果对该锁特点用一句话概述就是:保持CPU运转,屏幕会常亮,键盘灯都常亮并且Android官方也是推荐替代的方案和SCREEN_BRIGHT_WAKE_LOCK一样,这里就复制粘贴了*/@Deprecatedpublic static final int FULL_WAKE_LOCK = 0x0000001a;/*该锁比较特殊,用于和距离传感器配合使用持有该类型锁的特点是:当距离传感器检测到有物体(包括)靠近,会将屏幕熄灭相反,当检测到物体远离后会点亮屏幕上述锁不会影响终端的正常进入休眠状态,只有当前屏幕由该wakelock锁灭掉,才不会进入休眠状态应用场景在通话中比较常见 */public static final int PROXIMITY_SCREEN_OFF_WAKE_LOCK = 0x00000020;/**************************************************************///上面的几个锁类型都是公开的,第三方App可以调用到的,下面的两个比较特殊是隐藏的/*如果持有该锁,则会使屏幕处于DOZE状态,同时允许CPU挂起,该锁用于DreamManager实现Doze模式,如SystemUI的DozeServiceDoze模式是在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锁调用流程分析
本来是将调用流程放在这篇博客中进行相关的分析,然后一网打尽的!但是分析分析着,一看这内容有点多啊,所以这个章节放在后面单独成一个博客来分析!但是我们可以简单的看下其整体框架调用流程图,如下:
总之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\": %s\n", 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 wakemWakefulness=DozingmWakefulnessChanging=falsemWakeLockSummary=0x40mLastWakeTime=4068623 (137029 ms ago)mHoldingWakeLockSuspendBlocker=falsemWakeUpWhenPluggedOrUnpluggedConfig=truemWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=falsemDoubleTapWakeEnabled=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;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);setContentView(R.layout.viewtest);PowerManager mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"WakeLock_FUN");mWakeLock.acquire();// 获取锁}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();if (mWakeLock.isHeld())mWakeLock.release();// 获取锁}
}
我们来看下此时的实际情况如何:
λ adb shell dumpsys power | grep -i wakemWakefulness=AwakemWakefulnessChanging=falsemWakeLockSummary=0x1mLastWakeTime=4353954 (17858 ms ago)mHoldingWakeLockSuspendBlocker=truemWakeUpWhenPluggedOrUnpluggedConfig=truemWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig=falsemDoubleTapWakeEnabled=false
Wake Locks: size=1PARTIAL_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锁的调用流程的详细分析,这个我们将会在后面的博客中补上。如果本篇博客对你有所帮助,欢迎点赞和评论,当然也可以拍砖,总之欢迎留下你的脚步!
从Android应用层及Framework层的角度分析WakeLock锁机制相关推荐
- android 8.1 framework层Ethernet多个以太网口切换连接实现流程
之前写过一篇<android 8.1 framework层修改以太网静态ip功能>的文章,这篇是在该基础上实现的.之前的功能,只在设备仅支持单个网口的情况下.当大佬拿来一个设备说:这个还可 ...
- Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(一)
文章大纲 引言 一.Android Storage Access Framework 二.Storage Access Framework 的主要角色成员 1.Document Provider 文件 ...
- Android 进阶——Framework 核心之Android Storage Access Framework(SAF)存储访问框架机制详解(二)
文章大纲 引言 一.DirectFragment 1.当选中DirectoryFragment中RecyclerView的Item时 2.选中DirectoryFragment中RecyclerVie ...
- 用Android Studio调试Framework层代码
Android程序员不得不知的调试技巧. 本文以webview loadUrl和域名解析为例,介绍配合使用LLDB和Android Studio调试Framework代码的技巧. java 层调试 首 ...
- android 8 修改ip,android 8.1 framework层修改以太网静态ip功能
Android5.0基础上到Android7.0,Android都自带了以太网的设置功能,基本上都是将ip地址和网关之类的信息通过StaticIpConfiguration该对象实现联网功能.到了An ...
- Android功耗优化(7)---如何分析wakelock(wakeup source)持锁问题
如何分析wakelock(wakeup source)持锁问题 锁一般分为:APP透过PowerManager拿锁,以及kernel wakelock. 分析上层持锁的问题: 目前PowerManag ...
- Android wifi探究二:Wifi framework层源码分析
上一篇博客初步认识了wpa_supplicant的作用和使用方法,并且尝试着梳理了wifi的大框架,不过,java层的框架我们忽略了,没有分析,也就是说上一篇博客简单的指出了wifi代码的大框架,那么 ...
- android 代码控制音量,Android的framework层音量控制原理分析--hot(key)处理
Android.media.AudioManager中包含了对android.media.AudioService的跨进程AIDL调用封装. 正常处理过程: 1.调整音量是通过AudioManager ...
- android四大组件在哪层,Android四大组件framework层
activity https://www.kancloud.cn/alex_wsc/android-deep2/413484 当前Activity Activity向AMS发送StartActivit ...
最新文章
- linux高效办公环境配置(vim、tmux、bash_profile)
- oracle中聚合比较函数,Oracle聚合函数/分析函数
- node-rsa加密,java解密调试
- Python编程语言学习:判断变量是否为NONE或False的几种常见写法(if not用法教程)
- 四元数左乘右乘_四元数、欧拉角学习笔记个人理解
- 美团差评数据分析,python代码实现
- python安装百度aip_Python3.6安装aip
- java中容易被忽视的基本概念
- Git如何配置多个SSH-Key呢?
- 那些高曝光的Annotation(@ComponentScan、@PropertySource与@PropertySources、@Import与ImportResource)
- 素数判定 [2009年哈尔滨工业大学计算机研究生机试真题]
- “搞机器学习没前途”
- 滴滴开源的APM方案Dokit接入
- 免费分享一套狂雨小说cms采集规则
- Java三大特性详解
- protocol buffer 使用
- 用python证明给定范围的欧拉常数
- 倒计时3天!云栖大会龙蜥操作系统峰会最新议程一览
- 广西壮族自治区公安厅信息中心异地容灾系统(三期)项目招标
- Oracle Spatial 安装和使用