从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", 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.
*/
@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锁调用流程分析
本来是将调用流程放在这篇博客中进行相关的分析,然后一网打尽的!但是分析分析着,一看这内容有点多啊,所以这个章节放在后面单独成一个博客来分析!但是我们可以简单的看下其整体框架调用流程图,如下:
总之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 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锁的调用流程的详细分析,这个我们将会在后面的博客中补上。如果本篇博客对你有所帮助,欢迎点赞和评论,当然也可以拍砖,总之欢迎留下你的脚步!