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

 更新时间:2022年10月28日 09:03:37   作者:大胃粥  
这篇文章主要为大家介绍了PowerManagerService之唤醒锁的使用获取创建示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

在开发中,或多或少会使用唤醒锁(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 系列的文章,就此结束。虽然还有一些功能我并未分析,但是我写的这些文章都是基础,只要掌握基础,其它功能的分析,岂不是信手拈来。

以上就是PowerManagerService之唤醒锁的使用获取创建示例解析的详细内容,更多关于PowerManagerService 唤醒锁的资料请关注脚本之家其它相关文章!

相关文章

  • Android UI:ListView - SimpleAdapter实例详解

    Android UI:ListView - SimpleAdapter实例详解

    这篇文章主要介绍了Android UI:ListView - SimpleAdapter实例详解,SimpleAdapter是扩展性最好的适配器,可以定义各种你想要的布局,而且使用很方便,需要的朋友可以参考下
    2016-11-11
  • android的UDP编程实例

    android的UDP编程实例

    android的UDP编程实例和注意事项。
    2013-11-11
  • 查看apk签名信息方法

    查看apk签名信息方法

    用shell写了一个查看apk签名的脚本,下面与大家分享下如何查看apk签名信息,感兴趣的朋友可以了解下哈
    2013-06-06
  • 浅析Android文件管理器(项目一)

    浅析Android文件管理器(项目一)

    这篇文章主要介绍了浅析Android文件管理器(一)的相关资料,需要的朋友可以参考下
    2015-11-11
  • Android 实现右滑返回功能

    Android 实现右滑返回功能

    右滑返回功能在ios上非常实用,因为它的返回键在左上角,下面脚本之家小编给大家带来了Android 实现右滑返回功能,感兴趣的朋友一起看看吧
    2018-04-04
  • Android Fragment实现顶部、底部导航栏

    Android Fragment实现顶部、底部导航栏

    这篇文章主要为大家详细介绍了Android Fragment实现顶部、底部导航栏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • Android 添加TextView删除线(代码简单)

    Android 添加TextView删除线(代码简单)

    最近接了个项目,其中有项目需求是这样的,有这么个需求,就是一个产品下有两个价格,一个是市场价,一个是销售价,这时要把市场价添加个删除线;怎么实现呢?下面小编给大家分享一段简单的代码实现Android 添加TextView删除线
    2016-02-02
  • Android中AnimationDrawable使用的简单实例

    Android中AnimationDrawable使用的简单实例

    这篇文章介绍了Android中AnimationDrawable使用的简单实例,有需要的朋友可以参考一下
    2013-10-10
  • Android调用手机拍照功能的方法

    Android调用手机拍照功能的方法

    这篇文章主要介绍了Android调用手机拍照功能的方法,实例分析了Android调用手机拍照功能及显示图片的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • Android的Launcher启动器中添加快捷方式及小部件实例

    Android的Launcher启动器中添加快捷方式及小部件实例

    这篇文章主要介绍了在Android的Launcher启动器中添加快捷方式及窗口小部件的方法,包括在自己的应用程序中添加窗口小部件AppWidget的例子,需要的朋友可以参考下
    2016-02-02

最新评论