WakeLock是一种锁机制,只要有进程持有这个锁,CPU就一直在工作,系统就不会进入到休眠状态。
关于WakeLock的使用方法其实在PowerManager类中的注释已经给出了,来看描述:
/**
* This class gives you control of the power state of the device.
*
* <p>
* <b>Device battery life will be significantly affected by the use of this API.</b>
* Do not acquire {@link WakeLock}s unless you really need them, use the minimum levels
* possible, and be sure to release them as soon as possible.
* </p><p>
* The primary API you'll use is {@link #newWakeLock(int, String) newWakeLock()}.
* This will create a {@link PowerManager.WakeLock} object. You can then use methods
* on the wake lock object to control the power state of the device.
* </p><p>
* In practice it's quite simple:
* {@samplecode
* PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
* PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
* wl.acquire();
* ..screen will stay on during this section..
* wl.release();
* }
* </p><p>
* The following wake lock levels are defined, with varying effects on system power.
* <i>These levels are mutually exclusive - you may only specify one of them.</i>
即代码实现是:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
wl.acquire();
//screen will stay on during this section..
wl.release();
我们来看newWakeLock方法:
public WakeLock newWakeLock(int levelAndFlags, String tag) {
validateWakeLockParameters(levelAndFlags, tag);
// 创建一个WakeLock并返回
return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName());
}
/** @hide */
public static void validateWakeLockParameters(int levelAndFlags, String tag) {
switch (levelAndFlags & WAKE_LOCK_LEVEL_MASK) {
case PARTIAL_WAKE_LOCK:
case SCREEN_DIM_WAKE_LOCK:
case SCREEN_BRIGHT_WAKE_LOCK:
case FULL_WAKE_LOCK:
case PROXIMITY_SCREEN_OFF_WAKE_LOCK:
case DOZE_WAKE_LOCK:
case DRAW_WAKE_LOCK:
break;
default:
throw new IllegalArgumentException("Must specify a valid wake lock level.");
}
// tag不为空,否则会抛异常
if (tag == null) {
throw new IllegalArgumentException("The tag must not be null.");
}
}
来看WakeLock的创建:
WakeLock(int flags, String tag, String packageName) {
// flag参数
mFlags = flags;
// tag不为空
mTag = tag;
mPackageName = packageName;
// 创建一个Binder对象
mToken = new Binder();
mTraceName = "WakeLock (" + mTag + ")";
}
1.wakelock level(唤醒锁等级)
PARTIAL_WAKE_LOCK:确保CPU正在运行,屏幕和键盘背光灯允许熄灭;
SCREEN_DIM_WAKE_LOCK(废弃):确保屏幕打开(但可能变暗),键盘背光灯允许熄灭;如果按下Power键,还是会进入休眠状态;
SCREEN_BRIGHT_WAKE_LOCK(废弃):确保屏幕以全亮度开启,键盘背光灯允许熄灭;如果按下Power键,还是会进入休眠状态;
FULL_WAKE_LOCK(废弃):确保屏幕和键盘背光灯在全亮度;如果按下Power键,还是会进入休眠状态;
PROXIMITY_SCREEN_OFF_WAKE_LOCK:当传感器激活时,关闭屏幕;物体移开后不久,屏幕再次打开;
DOZE_WAKE_LOCK(@hide):将屏幕置于低功耗状态,并允许CPU挂起;
DRAW_WAKE_LOCK(@hide):使设备保持足够的唤醒状态以允许进行绘图;
2.wakelock flag(唤醒锁标志)
ACQUIRE_CAUSES_WAKEUP: 在获取之后锁定屏幕;
ON_AFTER_RELEASE:当锁释放之后,系统会延迟一会再黑屏;
UNIMPORTANT_FOR_LOGGING:这个锁对于记录事件并不重要,如果以后获取了重要的唤醒锁,它将被视为要记录的唤醒锁;
3.acquire(申请锁)
当创建wakelock完成后,需要申请wakelock来确保屏幕正常工作。来看源码实现:
public void acquire() {
// 加上同步锁,mToken
synchronized (mToken) {
// 调用acquireLocked函数
acquireLocked();
}
}
// 含参的acquire方法
public void acquire(long timeout) {
synchronized (mToken) {
acquireLocked();
mHandler.postDelayed(mReleaser, timeout);
}
}
可以看到无论是无参还是含参的acquire方法都会去调用acquireLocked方法,来看:
private void acquireLocked() {
mInternalCount++;
mExternalCount++;
// 唤醒锁非计数引入或计数为1
if (!mRefCounted || mInternalCount == 1) {
// Do this even if the wake lock is already thought to be held (mHeld == true)
// because non-reference counted wake locks are not always properly released.
// For example, the keyguard's wake lock might be forcibly released by the
// power manager without the keyguard knowing. A subsequent call to acquire
// should immediately acquire the wake lock once again despite never having
// been explicitly released by the keyguard.
mHandler.removeCallbacks(mReleaser);
Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
try {
// 调用PowerManagerService中的acquireWakeLock
mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
mHistoryTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mHeld = true;
}
}
接着看PowerManagerService的acquireWakeLock方法的实现:
@Override // Binder call
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
WorkSource ws, String historyTag) {
if (lock == null) {
throw new IllegalArgumentException("lock must not be null");
}
if (packageName == null) {
throw new IllegalArgumentException("packageName must not be null");
}
PowerManager.validateWakeLockParameters(flags, tag);
// 检查 WAKE_LOCK 权限
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
if ((flags & PowerManager.DOZE_WAKE_LOCK) != 0) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.DEVICE_POWER, null);
}
if (ws != null && !ws.isEmpty()) {
// 如果ws不为空,需要检查UPDATE_DEVICE_STATS 权限
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.UPDATE_DEVICE_STATS, null);
} else {
ws = null;
}
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
final long ident = Binder.clearCallingIdentity();
try {
// 调用acquireWakeLockInternal函数
acquireWakeLockInternal(lock, flags, tag, packageName, ws, historyTag, uid, pid);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
4.release(释放锁)
acquire申请锁了之后,进程一直持锁处于亮屏等状态,当结束这些状态后,我们需要将锁释放而不能一直持有。这时会去调用release方法,来看源码实现:
public void release() {
release(0);
}
public void release(int flags) {
// 同步锁,mToken
synchronized (mToken) {
if (mInternalCount > 0) {
// internal count must only be decreased if it is > 0 or state of
// the WakeLock object is broken.
mInternalCount--;
}
if ((flags & RELEASE_FLAG_TIMEOUT) == 0) {
mExternalCount--;
}
// 唤醒锁非引用计数或内部计数为0
if (!mRefCounted || mInternalCount == 0) {
mHandler.removeCallbacks(mReleaser);
if (mHeld) {
Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
try {
// 调用PowerManagerService的releaseWakeLock函数
mService.releaseWakeLock(mToken, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mHeld = false;
}
}
// 唤醒锁引用计数且计数<0,抛WakeLock under-locked异常
if (mRefCounted && mExternalCount < 0) {
throw new RuntimeException("WakeLock under-locked " + mTag);
}
}
}
5.计数/不计数锁模式
在上面讲acquire和release方法的时候有涉及到计数这个词。默认情况下,唤醒锁是引用计数,如果唤醒锁是引用计数,则每次对acquire的调用都必须通过对release的相等数量的调用来平衡。即申请一次wakeLock,需要释放对应的一次;
而如果唤醒锁是不计数的,那么一次release的调用就可以解掉wakeLock锁,而不论acquire调用了几次。
源码中是通过mRefCounted这个布尔值来控制的:true表示唤醒锁引用计数;false表示不包含唤醒锁引用。可以通过setReferenceCounted方法来对mRefCounted来赋值。
public void setReferenceCounted(boolean value) {
synchronized (mToken) {
mRefCounted = value;
}
}
// 默认为true,即引用计数
private boolean mRefCounted = true;
PS:源码是基于Android 10平台