PowerManagerService之唤醒锁的使用获取创建示例解析

作者:大胃粥 时间:2021-09-29 06:42:22 

前言

在开发中,或多或少会使用唤醒锁(wake lock),有的是为了保持屏幕长亮,有的是为了保持 CPU 运行。

唤醒锁的本质,其实是对屏幕状态的控制,以及对 CPU 挂起的控制。

屏幕状态的控制,指的是保持屏幕处于点亮的状态,或者直接唤醒屏幕,或者延长亮屏时间。

CPU 挂起的控制,指的是否阻止 CPU 挂起,如果阻止了 CPU 挂起,其实就是保持 CPU 运行。

本文重点分析唤醒锁是如何实现对屏幕状态的控制,以及对 CPU 挂起的控制。

本文仍以前面的三篇文章为基础,重复的过程不会分析,只会简要概述,因此请读者务必仔细阅读如下三篇文章

PowerManagerService之亮屏流程分析

PowerManagerService之手动灭屏

PowerManagerService之自动灭屏

使用唤醒锁

首先介绍下如何使用唤醒锁,如下

PowerManager pm = mContext.getSystemService(PowerManager.class);
// 1. 创建唤醒锁
// 保持屏幕处于点亮状态,但是允许变暗
PowerManager.WakeLock wl = pm.newWakeLock(
               PowerManager.SCREEN_DIM_WAKE_LOCK
               | PowerManager.ON_AFTER_RELEASE,
               TAG);
// 2. 获取唤醒锁
wl.acquire();
// ... 执行任务 ...
// 3. 释放唤醒锁
wl.release();

使用唤醒锁的步骤为

  • 创建唤醒锁

  • 获取唤醒锁

  • 在不需要唤醒锁的时候,释放它。

注意,使用唤醒时,还需要在 AndroidManifest.xml 中声明权限 android.Manifest.permission.WAKE_LOCK

创建唤醒锁

首先介绍下创建唤醒锁的API

// PowerManager.java
public WakeLock newWakeLock(int levelAndFlags, String tag) {
   validateWakeLockParameters(levelAndFlags, tag);
   return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(),
           Display.INVALID_DISPLAY);
}

参数 levelAndFlags 是由 level 和 flag 以按位或的方式组成,其中必须指定一个 level,但是 flag 是可选的。

第三方 app 能使用的 level 有如下几个

level描述
PARTIAL_WAKE_LOCK保证 CPU 运行,但是屏幕和键盘背光可以关闭
FULL_WAKE_LOCK保证屏幕和键盘背光处于最大亮度
SCREEN_DIM_WAKE_LOCK确保屏幕处于点亮状态,但是可以变暗,键盘背光允许关闭
SCREEN_BRIGHT_WAKE_LOCK确认屏幕处于最大亮度,但是键盘背光允许关闭
PROXIMITY_SCREEN_OFF_WAKE_LOCK当距离传感器检测到物体靠近时,灭屏,检测到物体远离时,点亮屏幕

注意,FULL_WAKE_LOCK 、SCREEN_DIM_WAKE_LOCK、SCREEN_BRIGHT_WAKE_LOCK 不仅会使屏幕处于点亮状态,同时也会保持 CPU 处于运行状态,我们将在后面的分析得到验证。

第三方 app 能使用的 flag 有如下几个

flag描述
ACQUIRE_CAUSES_WAKEUP当唤醒锁被获取时,点亮屏幕
ON_AFTER_RELEASE当唤醒锁被释放时,如果屏幕处于点亮的状态,那么延长亮屏的时间

注意,ACQUIRE_CAUSES_WAKEUP 和 ON_AFTER_RELEASE 要配合屏幕唤醒锁 FULL_WAKE_LOCK, SCREEN_BRIGHT_WAKE_LOCK, SCREEN_DIM_WAKE_LOCK一起使用。我们将在后面的分析得到验证。

这里介绍的 level 和 flag 只适用于第三方 app 使用,其实系统还定义了一些,用于完成特殊的功能。

参数 tag,名字其实可以随意,但是官方说,最好以 app:mytag 的方式命名,例如 gmail:mytag

参数介绍完了,我现在想提另外一个话题,与多屏相关。 不知从何时起,Android 把多屏进行了分组,内置的屏幕是在默认的分组中。PowerManager#newWakeLock(int levelAndFlags, String tag) 这个 API 会作用于所有的屏幕分组,但是如果我们想指定某组显示屏呢,那么需要使用下面的 API,但是它是系统 API

// PowerManager.java
/**
* @hide
*/
public WakeLock newWakeLock(int levelAndFlags, String tag, int displayId) {
   validateWakeLockParameters(levelAndFlags, tag);
   return new WakeLock(levelAndFlags, tag, mContext.getOpPackageName(), displayId);
}

参数 displayId 其实应该叫做 display group id,它表示唤醒锁作用于指定分组显示屏。

现在看下 WakeLock 的构造函数

// PowerManager.java
WakeLock(int flags, String tag, String packageName, int displayId) {
   mFlags = flags;
   mTag = tag;
   mPackageName = packageName;
   mToken = new Binder();
   mTraceName = "WakeLock (" + mTag + ")";
   mDisplayId = displayId;
}

构造函数就是简单保存几个参数,但是有一点需要注意,mToken 是一个 Binder,它会传给服务端 PowerManagerService,服务端会注册它的死亡事件。那么这个 Binder 对象其实就是为了监控服务端进程的生死。这个技术大家要学会,我曾经用这个技术优化过自己写的服务端代码。

获取唤醒锁

// PowerManager.java
public void acquire() {
   synchronized (mToken) {
       acquireLocked();
   }
}
public void acquire(long timeout) {
   synchronized (mToken) {
       acquireLocked();
       // 发送一个延时消息,自动释放唤醒锁
       mHandler.postDelayed(mReleaser, timeout);
   }
}
private void acquireLocked() {
   mInternalCount++;
   mExternalCount++;
   // mRefCounted 默认为 true,它表示对唤醒锁引用计数
   if (!mRefCounted || mInternalCount == 1) {
       mHandler.removeCallbacks(mReleaser);
       Trace.asyncTraceBegin(Trace.TRACE_TAG_POWER, mTraceName, 0);
       try {
           mService.acquireWakeLock(mToken, mFlags, mTag, mPackageName, mWorkSource,
                   mHistoryTag, mDisplayId);
       } catch (RemoteException e) {
           throw e.rethrowFromSystemServer();
       }
       mHeld = true;
   }
}

获取唤醒锁时,可以指定一个超时时间,如果时间到了,唤醒锁还没有释放,那么会自动释放唤醒锁。

默认的情况下,唤醒锁是计数的。如果多次获取唤醒锁,需要进行相应次数的释放。

而如果通过 wakeLock.setReferenceCounted(false) 设置唤醒锁为不计数

// PowerManager.java
public void setReferenceCounted(boolean value) {
   synchronized (mToken) {
       mRefCounted = value;
   }
}

那么多次获取唤醒锁后,只需要释放一次。

现在让我们看下服务端 PowerManagerService 是如何获取唤醒锁的

// PowerManagerService.java
public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName,
       WorkSource ws, String historyTag, int displayId) {
   // ... 省略权限检测
   try {
       acquireWakeLockInternal(lock, displayId, flags, tag, packageName, ws, historyTag,
               uid, pid);
   } finally {
       Binder.restoreCallingIdentity(ident);
   }
}
private void acquireWakeLockInternal(IBinder lock, int displayId, int flags, String tag,
       String packageName, WorkSource ws, String historyTag, int uid, int pid) {
   synchronized (mLock) {
       // ... 省略显示屏分组的检测
       WakeLock wakeLock;
       int index = findWakeLockIndexLocked(lock);
       boolean notifyAcquire;
       if (index >= 0) { // 唤醒锁已经存在
           // ...
       } else { // 唤醒锁不存在
           // mUidState 由 ActivityManagerService 同步给 PowerManagerService
           // UidState 代表一个 app 进程的状态
           UidState state = mUidState.get(uid);
           if (state == null) {
               state = new UidState(uid);
               state.mProcState = ActivityManager.PROCESS_STATE_NONEXISTENT;
               mUidState.put(uid, state);
           }
           // 保存唤醒锁的数量
           state.mNumWakeLocks++;
           // 1. 创建唤醒锁
           wakeLock = new WakeLock(lock, displayId, flags, tag, packageName, ws, historyTag,
                   uid, pid, state);
           try {
               // 2. 监听客户端进程的死亡
               // 当客户端进程死亡时,释放它所申请的唤醒锁
               lock.linkToDeath(wakeLock, 0);
           } catch (RemoteException ex) {
               throw new IllegalArgumentException("Wake lock is already dead.");
           }
           // 3. 保存唤醒锁
           mWakeLocks.add(wakeLock);
           // 4. 更新唤醒锁 PowerManager.PARTIAL_WAKE_LOCK 的 disable 状态
           setWakeLockDisabledStateLocked(wakeLock);
           notifyAcquire = true;
       }
       // 5. 处理 PowerManager.ACQUIRE_CAUSES_WAKEUP 唤醒锁亮屏的情况
       applyWakeLockFlagsOnAcquireLocked(wakeLock, uid);
       // 6. 标记唤醒锁已经改变
       mDirty |= DIRTY_WAKE_LOCKS;
       // 7. 更新电源状态
       updatePowerStateLocked();
       if (notifyAcquire) {
           // 记录唤醒锁
           notifyWakeLockAcquiredLocked(wakeLock);
       }
   }
}

先大致了解下,首次向 PowerManagerService 申请唤醒锁的过程

  • 创建服务端的 WakeLock。

  • 监听客户端传递过来的 Binder 的死亡事件,其实就是监听客户端进程的死亡。当客户端进程死亡时,释放它所申请的唤醒锁。

  • PowerManagerService 使用 ArrayList< WakeLock > mWakeLocks 保存创建的唤醒锁。

更新唤醒锁 PowerManager.PARTIAL_WAKE_LOCK 的 disable 状态,因此有些情况下,是不允许获取这种唤醒锁的,这些特殊情况如下

private boolean setWakeLockDisabledStateLocked(WakeLock wakeLock) {
   if ((wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK)
           == PowerManager.PARTIAL_WAKE_LOCK) {
       boolean disabled = false;
       final int appid = UserHandle.getAppId(wakeLock.mOwnerUid);
       if (appid >= Process.FIRST_APPLICATION_UID) {
           // Cached inactive processes are never allowed to hold wake locks.
           // 1. 缓存的不活跃的进程的唤醒锁需要disable
           if (mConstants.NO_CACHED_WAKE_LOCKS) {
               disabled = mForceSuspendActive
                       || (!wakeLock.mUidState.mActive && wakeLock.mUidState.mProcState
                               != ActivityManager.PROCESS_STATE_NONEXISTENT &&
                       wakeLock.mUidState.mProcState > ActivityManager.PROCESS_STATE_RECEIVER);
           }
           if (mDeviceIdleMode) {
               // 2. idle 模式下,不处理白名单的进程的唤醒锁,也需要 disable
               final UidState state = wakeLock.mUidState;
               if (Arrays.binarySearch(mDeviceIdleWhitelist, appid) < 0 &&
                       Arrays.binarySearch(mDeviceIdleTempWhitelist, appid) < 0 &&
                       state.mProcState != ActivityManager.PROCESS_STATE_NONEXISTENT &&
                       state.mProcState >
                               ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
                   disabled = true;
               }
           }
       }
       // 3. 更新唤醒锁的 disable 状态
       if (wakeLock.mDisabled != disabled) {
           wakeLock.mDisabled = disabled;
           return true;
       }
   }
   return false;
}

处理 PowerManager.ACQUIRE_CAUSES_WAKEUP 唤醒锁亮屏的情况。

private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock, int uid) {
   // 注意,PowerManager.ACQUIRE_CAUSES_WAKEUP 要与如下几个屏幕锁一起使用才有效
   // PowerManager.FULL_WAKE_LOCK
   // PowerManager.SCREEN_BRIGHT_WAKE_LOCK
   // PowerManager.SCREEN_DIM_WAKE_LOCK
   if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0
           && isScreenLock(wakeLock)) {
       String opPackageName;
       int opUid;
       if (wakeLock.mWorkSource != null && !wakeLock.mWorkSource.isEmpty()) {
           // ...
       } else {
           opPackageName = wakeLock.mPackageName;
           opUid = wakeLock.mOwnerUid;
       }
       for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
           // 更新 wakefulness 为 WAKEFULNESS_AWAKE
           wakeDisplayGroupNoUpdateLocked(id, mClock.uptimeMillis(),
                   PowerManager.WAKE_REASON_APPLICATION, wakeLock.mTag,
                   opUid, opPackageName, opUid);
       }
   }
}

注意,PowerManager.ACQUIRE_CAUSES_WAKEUP 是要下屏幕锁一直使用,屏幕锁为 PowerManager.FULL_WAKE_LOCK、PowerManager.SCREEN_BRIGHT_WAKE_LOCK、PowerManager.SCREEN_DIM_WAKE_LOCK。很显然,这些屏幕锁,都是保持屏幕处于点亮的状态。

根据前面的文章,wakeDisplayGroupNoUpdateLocked() 其实就是更新 wakefulness 为 WAKEFULNESS_AWAKE。当后面更新电源状态时,会向 DisplayManagerService 发起屏幕请示,从而进行亮屏。这个过程,请读者参考前面的文章,自行分析。

  • 标记唤醒锁已经改变。

  • 更新电源状态,根据 mDirty 处理唤醒锁的改变 。

现在来看下最后一步,更新电源状态

// PowerManagerService.java
private void updatePowerStateLocked() {
   if (!mSystemReady || mDirty == 0) {
       return;
   }
   // 注意这里的技术,线程可以判断是否获取了某个锁
   if (!Thread.holdsLock(mLock)) {
       Slog.wtf(TAG, "Power manager lock was not held when calling updatePowerStateLocked");
   }
   Trace.traceBegin(Trace.TRACE_TAG_POWER, "updatePowerState");
   try {
       // Phase 0: Basic state updates.
       updateIsPoweredLocked(mDirty);
       updateStayOnLocked(mDirty);
       updateScreenBrightnessBoostLocked(mDirty);
       // Phase 1: Update wakefulness.
       // Loop because the wake lock and user activity computations are influenced
       // by changes in wakefulness.
       final long now = mClock.uptimeMillis();
       int dirtyPhase2 = 0;
       for (;;) {
           int dirtyPhase1 = mDirty;
           dirtyPhase2 |= dirtyPhase1;
           mDirty = 0;
           // 1. 归纳唤醒锁
           updateWakeLockSummaryLocked(dirtyPhase1);
           // 更新用户行为
           updateUserActivitySummaryLocked(now, dirtyPhase1);
           updateAttentiveStateLocked(now, dirtyPhase1);
           if (!updateWakefulnessLocked(dirtyPhase1)) {
               break;
           }
       }
       // Phase 2: Lock profiles that became inactive/not kept awake.
       updateProfilesLocked(now);
       // Phase 3: Update display power state.
       // 2. 更新显示屏的电源状态
       final boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
       // Phase 4: Update dream state (depends on display ready signal).
       updateDreamLocked(dirtyPhase2, displayBecameReady);
       // Phase 5: Send notifications, if needed.
       finishWakefulnessChangeIfNeededLocked();
       // Phase 6: Update suspend blocker.
       // Because we might release the last suspend blocker here, we need to make sure
       // we finished everything else first!
       // 3. 唤醒锁保持 CPU 运行
       updateSuspendBlockerLocked();
   } finally {
       Trace.traceEnd(Trace.TRACE_TAG_POWER);
   }
}

与唤醒锁相关的主要流程如下

  • 归纳唤醒锁。这个过程会把所有的唤醒锁整合到一起,它会影响请求策略,也就是会影响屏幕最终状态。并用它也会决定是否阻止CPU挂起,也就是是否保持CPU运行。详见【归纳唤醒锁

  • 更新电源状态。根据前面的文章可知,屏幕的最终状态是由请求的策略所决定的,而唤醒锁可以响应策略。详见【更新请求策略

  • 如果有唤醒锁需要保证 CPU 运行,那么 PMS 会向底层获取锁,保证 CPU 运行。详见【唤醒锁保持 CPU 运行

归纳唤醒锁

private void updateWakeLockSummaryLocked(int dirty) {
   if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS | DIRTY_DISPLAY_GROUP_WAKEFULNESS))
           != 0) {
       //1. wake lock summary 清 0
       mWakeLockSummary = 0;
       final int numProfiles = mProfilePowerState.size();
       for (int i = 0; i < numProfiles; i++) {
           mProfilePowerState.valueAt(i).mWakeLockSummary = 0;
       }
       for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
           mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId, 0);
       }
       // 2. 获取 wake lock summary
       int invalidGroupWakeLockSummary = 0;
       final int numWakeLocks = mWakeLocks.size();
       // 遍历所有的 WakeLock
       for (int i = 0; i < numWakeLocks; i++) {
           final WakeLock wakeLock = mWakeLocks.get(i);
           final Integer groupId = wakeLock.getDisplayGroupId();
           if (groupId == null) {
               continue;
           }
           // 把 PowerManager 定义的 WakeLock flag 转化为 PowerManagerService 定义的 WakeLock flag
           final int wakeLockFlags = getWakeLockSummaryFlags(wakeLock);
           // 更新 PMS 的 mWakeLockSummary
           mWakeLockSummary |= wakeLockFlags;
           if (groupId != Display.INVALID_DISPLAY_GROUP) {
               int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(
                       groupId);
               wakeLockSummary |= wakeLockFlags;
               mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId,
                       wakeLockSummary);
           } else {
               // 没有指定 group id 的唤醒锁,保存到 invalidGroupWakeLockSummary
               invalidGroupWakeLockSummary |= wakeLockFlags;
           }
           for (int j = 0; j < numProfiles; j++) {
               // ...
           }
       } // 遍历所有 WakeLock 结束
       // 3. 调整的 wake lock summary
       for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
           // 从这里可以看出,invalidGroupWakeLockSummary 应用到了所有的 display group 中
           // 因此,在获取 WakeLock 没有指定 group id 时,这个 WakeLock 是应用到所有的 display group 上
           final int wakeLockSummary = adjustWakeLockSummaryLocked(
                   mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId),
                   invalidGroupWakeLockSummary
                           | mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId));
           mDisplayGroupPowerStateMapper.setWakeLockSummaryLocked(groupId, wakeLockSummary);
       }
       mWakeLockSummary = adjustWakeLockSummaryLocked(getWakefulnessLocked(),
               mWakeLockSummary);
       for (int i = 0; i < numProfiles; i++) {
           // ...
       }
   }
}

这里的逻辑很清晰,其实就是遍历所有的唤醒锁,然后归纳保存到 mWakeLockSummary。当然这其中有几个重要的函数需要搞清楚

通过 getWakeLockSummaryFlags() 把 PowerManager 定义的唤醒锁转化为 PowerManagerService 定义的唤醒锁

private int getWakeLockSummaryFlags(WakeLock wakeLock) {
   switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
       // 这个唤醒锁用于保持 CPU 运行
       case PowerManager.PARTIAL_WAKE_LOCK:
           // disabled 状态的唤醒锁,是不能保证 CPU 运行的
           if (!wakeLock.mDisabled) {
               return WAKE_LOCK_CPU;
           }
           break;
       // 以下三个唤醒锁用于保持屏幕处于点亮状态
       case PowerManager.FULL_WAKE_LOCK:
           return WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_BUTTON_BRIGHT;
       case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
           return WAKE_LOCK_SCREEN_BRIGHT;
       case PowerManager.SCREEN_DIM_WAKE_LOCK:
           return WAKE_LOCK_SCREEN_DIM;
       // 用距离传感器进行灭屏、亮屏
       case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK:
           return WAKE_LOCK_PROXIMITY_SCREEN_OFF;
       // PowerManager.DOZE_WAKE_LOCK 由 DreamManagerService 获取
       // 它使设备真正进入打盹状态
       case PowerManager.DOZE_WAKE_LOCK:
           return WAKE_LOCK_DOZE;
       // 由 window manager 获取,允许应用在系统doze状态下能够绘制
       case PowerManager.DRAW_WAKE_LOCK:
           return WAKE_LOCK_DRAW;
   }
   return 0;
}

通过 adjustWakeLockSummaryLocked() 调整归纳的唤醒锁

private static int adjustWakeLockSummaryLocked(int wakefulness, int wakeLockSummary) {
   // 系统处于 非doze 状态,PowerManager.DOZE_WAKE_LOCK 和 PowerManager.DRAW_WAKE_LOCK 无效
   // 看来,这两个锁只有当系统处于 doze 状态,才有效果
   if (wakefulness != WAKEFULNESS_DOZING) {
       wakeLockSummary &= ~(WAKE_LOCK_DOZE | WAKE_LOCK_DRAW);
   }
   // 系统处于休眠或者doze状态下,如下三个保持屏幕点亮状态的锁是无效的
   // PowerManager.FULL_WAKE_LOCK
   // PowerManager.SCREEN_BRIGHT_WAKE_LOCK
   // PowerManager.SCREEN_DIM_WAKE_LOCK
   if (wakefulness == WAKEFULNESS_ASLEEP
           || (wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
       wakeLockSummary &= ~(WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM
               | WAKE_LOCK_BUTTON_BRIGHT);
       // 甚至,当系统处于休眠状态,PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK 无法唤醒屏幕
       if (wakefulness == WAKEFULNESS_ASLEEP) {
           wakeLockSummary &= ~WAKE_LOCK_PROXIMITY_SCREEN_OFF;
       }
   }
   // 如下三个保持屏幕点亮状态的锁
   // PowerManager.FULL_WAKE_LOCK
   // PowerManager.SCREEN_BRIGHT_WAKE_LOCK
   // PowerManager.SCREEN_DIM_WAKE_LOCK
   // 当系统处于唤醒状态或者屏保状态,这两个其实都是亮屏状态
   // 需要保证 CPU 运行,也就是下面添加的 WAKE_LOCK_CPU
   // 并且系统处于唤醒状态时,还要屏幕长亮,这正好符合上面三个唤醒锁的定义
   if ((wakeLockSummary & (WAKE_LOCK_SCREEN_BRIGHT | WAKE_LOCK_SCREEN_DIM)) != 0) {
       // 并且如果系统处理唤醒状态,还要保持长亮
       if (wakefulness == WAKEFULNESS_AWAKE) {
           wakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_STAY_AWAKE;
       } else if (wakefulness == WAKEFULNESS_DREAMING) { // 系统处于屏保状态
           wakeLockSummary |= WAKE_LOCK_CPU;
       }
   }
   // 系统处于 doze 状态,PowerManager.DRAW_WAKE_LOCK 需要保持 CPU 运行
   if ((wakeLockSummary & WAKE_LOCK_DRAW) != 0) {
       wakeLockSummary |= WAKE_LOCK_CPU;
   }
   return wakeLockSummary;
}

调整归纳的唤醒锁,其实就是针对系统处于不同的状态,去掉一些不兼容的唤醒锁或者添加一些合适的锁。

例如,前面说过,PowerManager.FULL_WAKE_LOCKPowerManager.SCREEN_BRIGHT_WAKE_LOCKPowerManager.SCREEN_DIM_WAKE_LOCK 不仅仅要保持屏幕的亮度,而且还要保持 CPU 运行,这里就可以看出端倪。

更新请求策略

通过前面的文章可知,屏幕最终的状态是通过请求策略控制的,函数如下

int getDesiredScreenPolicyLocked(int groupId) {
   final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
   final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId);
   if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
       // 1. 系统处于休眠状态,任何唤醒锁都不起作用,屏幕会进入关闭状态
       return DisplayPowerRequest.POLICY_OFF;
   } else if (wakefulness == WAKEFULNESS_DOZING) {
       // 2. 系统处于 doze 状态,PowerManager.DRAW_WAKE_LOCK 会让屏幕进入 doze 状态
       // 当 dream manager 成功启动 doze dream,才会获取 PowerManager.DRAW_WAKE_LOCK,此时系统才真正进入 doze 状态
       if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
           return DisplayPowerRequest.POLICY_DOZE;
       }
       if (mDozeAfterScreenOff) {
           return DisplayPowerRequest.POLICY_OFF;
       }
   }
   if (mIsVrModeEnabled) {
       return DisplayPowerRequest.POLICY_VR;
   }
   // 下面处理的是系统处于唤醒和屏保状态,都是亮屏的状态
   // 3. PowerManager.FULL_WAKE_LOCK 和 PowerManager.SCREEN_BRIGHT_WAKE_LOCK 会
   // 让屏幕处于亮屏状态
   if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
           || !mBootCompleted
           || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId)
           & USER_ACTIVITY_SCREEN_BRIGHT) != 0
           || mScreenBrightnessBoostInProgress) {
       return DisplayPowerRequest.POLICY_BRIGHT;
   }
   // 4. PowerManager.SCREEN_DIM_WAKE_LOCK 允许屏幕变暗
   // 当屏幕快要超时时,会进入变暗的状态,此时持有 PowerManager.SCREEN_DIM_WAKE_LOCK 会保持屏幕
   // 一直处于 dim 状态
   return DisplayPowerRequest.POLICY_DIM;
}

从获取请求策略的过程,我们可以看到,当系统处于不同的状态,不同的唤醒锁,是如何影响屏幕状态的。

例如,PowerManager.FULL_WAKE_LOCK 和 PowerManager.SCREEN_BRIGHT_WAKE_LOCK 会保证屏幕一直处于亮屏状态,而 PowerManager.SCREEN_DIM_WAKE_LOCK 会保证屏幕也处于亮屏状态,但是允许变暗。

唤醒锁保持 CPU 运行

private void updateSuspendBlockerLocked() {
   // 1. 检测是否有唤醒锁需要保持 CPU 运行
   final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0);
   // 2. 检测屏幕的某些状态是否需要保持 CPU 运行
   final boolean needDisplaySuspendBlocker = needDisplaySuspendBlockerLocked();
   final boolean autoSuspend = !needDisplaySuspendBlocker;
   final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
   boolean interactive = false;
   for (int id : groupIds) {
       interactive |= mDisplayGroupPowerStateMapper.getPowerRequestLocked(id).isBrightOrDim();
   }
   // mDecoupleHalAutoSuspendModeFromDisplayConfig 默认为 false    
   if (!autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
       setHalAutoSuspendModeLocked(false);
   }
   // 3. 向底层获取锁,保证 CPU 运行
   // First acquire suspend blockers if needed.
   if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) {
       mWakeLockSuspendBlocker.acquire();
       mHoldingWakeLockSuspendBlocker = true;
   }
   if (needDisplaySuspendBlocker && !mHoldingDisplaySuspendBlocker) {
       mDisplaySuspendBlocker.acquire();
       mHoldingDisplaySuspendBlocker = true;
   }
   // mDecoupleHalInteractiveModeFromDisplayConfig 默认为 false
   if (mDecoupleHalInteractiveModeFromDisplayConfig) {
       // ...
   }
   // 下面表示没有对应的唤醒锁,就需要向底层释放锁
   if (!needWakeLockSuspendBlocker && mHoldingWakeLockSuspendBlocker) {
       mWakeLockSuspendBlocker.release();
       mHoldingWakeLockSuspendBlocker = false;
   }
   if (!needDisplaySuspendBlocker && mHoldingDisplaySuspendBlocker) {
       mDisplaySuspendBlocker.release();
       mHoldingDisplaySuspendBlocker = false;
   }
   // mDecoupleHalAutoSuspendModeFromDisplayConfig 默认为 flase
   if (autoSuspend && mDecoupleHalAutoSuspendModeFromDisplayConfig) {
       setHalAutoSuspendModeLocked(true);
   }
}

如下几个锁会保持 CPU 运行

  • PowerManager.PARTIAL_WAKE_LOCK(缓存的后台进程 或 idle模式下,不处于白名单的进程,唤醒锁无效)

  • PowerManager.FULL_WAKE_LOCK(系统处于唤醒或屏保状态)

  • PowerManager.SCREEN_BRIGHT_WAKE_LOCK(系统处于唤醒或屏保状态)

  • PowerManager.SCREEN_DIM_WAKE_LOCK(系统处于唤醒或屏保状态)

  • PowerManager.DOZE_WAKE_LOCK(系统处于doze状态)

  • PowerManager.DRAW_WAKE_LOCK(系统处于doze状态)

屏幕的几种状态也需要保持 CPU 运行,请看下面代码所展示的所有情况

private boolean needDisplaySuspendBlockerLocked() {
   // 1. DisplayManagerService 正在处理请求, 需要保持 CPU 运行
   if (!mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
       return true;
   }
   // 2. 屏幕亮度正在增强中,需要保持 CPU 运行
   if (mScreenBrightnessBoostInProgress) {
       return true;
   }
   // When we transition to DOZING, we have to keep the display suspend blocker
   // up until the Doze service has a change to acquire the DOZE wakelock.
   // Here we wait for mWakefulnessChanging to become false since the wakefulness
   // transition to DOZING isn't considered "changed" until the doze wake lock is
   // acquired.
   // 3. doze状态的转换中,需要保持 CPU 运行
   if (getWakefulnessLocked() == WAKEFULNESS_DOZING && mDozeStartInProgress) {
       return true;
   }
   final int[] groupIds = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
   for (int id : groupIds) {
       final DisplayPowerRequest displayPowerRequest =
               mDisplayGroupPowerStateMapper.getPowerRequestLocked(id);
       // 3. 亮屏状态下,需要保持 CPU 运行
       // 屏幕处于点亮或者变暗的状态,都是亮屏的状态
       if (displayPowerRequest.isBrightOrDim()) {
           // If we asked for the screen to be on but it is off due to the proximity
           // sensor then we may suspend but only if the configuration allows it.
           // On some hardware it may not be safe to suspend because the proximity
           // sensor may not be correctly configured as a wake-up source.
           if (!displayPowerRequest.useProximitySensor || !mProximityPositive
                   || !mSuspendWhenScreenOffDueToProximityConfig) {
               return true;
           }
       }
       // 4. 系统真正处于 doze 状态,也需要保持 CPU 运行
       // 因此需要在屏幕绘制一些东西,例如时间
       if (displayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE
               && displayPowerRequest.dozeScreenState == Display.STATE_ON) {
           // Although we are in DOZE and would normally allow the device to suspend,
           // the doze service has explicitly requested the display to remain in the ON
           // state which means we should hold the display suspend blocker.
           return true;
       }
   }
   // Let the system suspend if the screen is off or dozing.
   return false;
}

为何屏幕的状态有时候也需要 CPU 保持运行?举个最简单的例子,如果处于亮屏状态,CPU 允许挂起的话,app 进程就无法运行了。

释放锁

// PowerManager.java
public void release() {
   release(0);
}
/**
* Releases the wake lock with flags to modify the release behavior.
* <p>
* This method releases your claim to the CPU or screen being on.
* The screen may turn off shortly after you release the wake lock, or it may
* not if there are other wake locks still held.
* </p>
*
* @param flags Combination of flag values to modify the release behavior.
* Currently only {@link #RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY} is supported.
* Passing 0 is equivalent to calling {@link #release()}.
*/
public void release(int flags) {
   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--;
       }
       if (!mRefCounted || mInternalCount == 0) {
           mHandler.removeCallbacks(mReleaser);
           if (mHeld) {
               Trace.asyncTraceEnd(Trace.TRACE_TAG_POWER, mTraceName, 0);
               try {
                   mService.releaseWakeLock(mToken, flags);
               } catch (RemoteException e) {
                   throw e.rethrowFromSystemServer();
               }
               mHeld = false;
           }
       }
       if (mRefCounted && mExternalCount < 0) {
           throw new RuntimeException("WakeLock under-locked " + mTag);
       }
   }
}

从这里我们可以验证前面说的一个结论,mRefCounted 默认为 true,表示唤醒锁是引用计数的,如果多少获取唤醒锁,需要释放相应次数的唤醒锁。如果不计数,那么只需要释放一次。

现在看下服务端 PowerManagerService 是如何释放锁的

public void releaseWakeLock(IBinder lock, int flags) {
   if (lock == null) {
       throw new IllegalArgumentException("lock must not be null");
   }
   // 需要 android.Manifest.permission.WAKE_LOCK 权限
   mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null);
   final long ident = Binder.clearCallingIdentity();
   try {
       releaseWakeLockInternal(lock, flags);
   } finally {
       Binder.restoreCallingIdentity(ident);
   }
}
private void releaseWakeLockInternal(IBinder lock, int flags) {
   synchronized (mLock) {
       // 1. 找到服务端保存的唤醒锁
       int index = findWakeLockIndexLocked(lock);
       if (index < 0) {
           return;
       }
       WakeLock wakeLock = mWakeLocks.get(index);
       // 延迟释放 PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK
       // 直到距离传感器检测到物体远离
       if ((flags & PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY) != 0) {
           mRequestWaitForNegativeProximity = true;
       }
       // 不再监听客户端进程的死亡
       wakeLock.mLock.unlinkToDeath(wakeLock, 0);
       // 移除唤醒锁
       removeWakeLockLocked(wakeLock, index);
   }
}
private void removeWakeLockLocked(WakeLock wakeLock, int index) {
   // 2. 从数据结构中移除唤醒锁
   mWakeLocks.remove(index);
   // 进程状态中减少唤醒锁的数量
   UidState state = wakeLock.mUidState;
   state.mNumWakeLocks--;
   if (state.mNumWakeLocks <= 0 &&
           state.mProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
       mUidState.remove(state.mUid);
   }
   // 记录唤醒锁被释放的数据
   notifyWakeLockReleasedLocked(wakeLock);
   // 3. 处理 PowerManager.ON_AFTER_RELEASE
   // 带有这个 flag 的锁,在释放的时候,会更新用户行为时间,从而可以延长亮屏的时间
   applyWakeLockFlagsOnReleaseLocked(wakeLock);
   // 4.标记唤醒已经改变,并更新电源状态
   mDirty |= DIRTY_WAKE_LOCKS;
   updatePowerStateLocked();
}

PowerManagerService 移除唤醒锁的过程一般如下

  • 从数据结构中移除。

  • 处于带有 PowerManager.ON_AFTER_RELEASE 这个 flag 的唤醒锁。在释放带有这个 flag 的唤醒锁的时候,会更新用户行为时间,从而可以延长亮屏的时间。

  • 标记唤醒锁已经改变,更新电源状态。

距离传感器锁的原理,在看完本文后,大家可以自行分析。

现在来看下,释放带有 PowerManager.ON_AFTER_RELEASE 的唤醒锁,是如何延长亮屏的时间的

private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) {
   // PowerManager.ON_AFTER_RELEASE 必须与如下的屏幕锁一起使用
   // PowerManager.FULL_WAKE_LOCK
   // PowerManager.SCREEN_BRIGHT_WAKE_LOCK
   // PowerManager.SCREEN_DIM_WAKE_LOCK
   if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0
           && isScreenLock(wakeLock)) {
       userActivityNoUpdateLocked(mClock.uptimeMillis(),
               PowerManager.USER_ACTIVITY_EVENT_OTHER,
               PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS,
               wakeLock.mOwnerUid);
   }
}
private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
   boolean updatePowerState = false;
   // 注意,PowerManager.ON_AFTER_RELEASE 影响了所有的 display group 的 用户行为时间
   // 那么也说明,它会导致所有的屏幕延长亮屏的时间
   for (int id : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
       if (userActivityNoUpdateLocked(id, eventTime, event, flags, uid)) {
           updatePowerState = true;
       }
   }
   return updatePowerState;
}
// PowerManagerService.java
private boolean userActivityNoUpdateLocked(int groupId, long eventTime, int event, int flags,
       int uid) {
   // ...
   try {
       // ...
       if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
           // ...
       } else {
           if (eventTime > mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(
                   groupId)) {
               // 记录用户行为的时间
               mDisplayGroupPowerStateMapper.setLastUserActivityTimeLocked(groupId, eventTime);
               // 标记用户活动有改变
               mDirty |= DIRTY_USER_ACTIVITY;
               if (event == PowerManager.USER_ACTIVITY_EVENT_BUTTON) {
                   mDirty |= DIRTY_QUIESCENT;
               }
               return true;
           }
       }
   } finally {
       Trace.traceEnd(Trace.TRACE_TAG_POWER);
   }
   return false;
}

处理 PowerManager.ON_AFTER_RELEASE 的过程,我们要注意以下几点事情

  • PowerManager.ON_AFTER_RELEASE 必须要与屏幕唤醒锁 PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK 一起使用.

  • PowerManager.ON_AFTER_RELEASE 更新了所有屏幕分组的用户行为时间,也就是说最终会导致所有屏幕都延长亮屏的时间。

  • 更新用户行为时间,根据 PowerManagerService之自动灭屏 可知,用户行为时间的更新,最终会导致延长亮屏的时间

结束

通过本文的分析,我们可以看到唤醒锁是如何控制屏幕状态,以及如何保持CPU运行。但是本文写的比较简洁,是因为很多东西已经在前文分析过了,如果读者看本文的时候,有点压力,不妨再回头看看前面的文章。

PowerManagerService 系列的文章,就此结束。虽然还有一些功能我并未分析,但是我写的这些文章都是基础,只要掌握基础,其它功能的分析,岂不是信手拈来。

来源:https://juejin.cn/post/7159143785283190814

标签:PowerManagerService,唤醒锁,获取,创建
0
投稿

猜你喜欢

  • 通过spring boot 设置tomcat解决 post参数限制问题

    2022-09-26 23:38:31
  • Android 连接Wifi和创建Wifi热点的实例

    2022-07-16 06:00:52
  • @valid 无法触发BindingResult的解决

    2023-08-10 09:16:12
  • Android RecyclerView自定义上拉和下拉刷新效果

    2022-03-17 23:06:00
  • Java编程泛型限定代码分享

    2023-11-09 17:46:32
  • WinForm子窗体访问父窗体控件的实现方法

    2021-10-12 17:32:21
  • Java中线程休眠编程实例

    2021-09-06 11:42:55
  • Android使用Sensor感应器实现线程中刷新UI创建android测力计的功能

    2023-03-05 07:51:48
  • Java应用服务器之tomcat会话复制集群配置的示例详解

    2022-08-24 07:28:27
  • SpringBoot持久化层操作支持技巧

    2023-11-24 06:40:32
  • Android实现录音静音降噪

    2023-01-06 23:46:25
  • Java 二分查找的实现及图例解析

    2023-10-28 11:41:57
  • 深入理解C#之继承

    2022-02-25 07:25:44
  • Springboot整合Netty实现RPC服务器的示例代码

    2023-07-14 11:35:35
  • Java如何实现字符串每隔4位加空格

    2023-11-27 06:00:09
  • Android实现二维码扫描和生成的简单方法

    2022-06-18 18:15:52
  • Android内容提供者ContentProvider用法实例分析

    2021-06-25 09:33:04
  • C#实现按照指定长度在数字前补0方法小结

    2023-02-23 09:42:32
  • java基础之包装类的介绍及使用

    2023-01-10 17:45:29
  • C#获取日期的星期名称实例代码

    2022-10-22 13:25:53
  • asp之家 软件编程 m.aspxhome.com