PowerManagerService之唤醒锁

1,829 阅读10分钟

前言

在开发中,或多或少会使用唤醒锁(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();

使用唤醒锁的步骤为

  1. 创建唤醒锁
  2. 获取唤醒锁
  3. 在不需要唤醒锁的时候,释放它。

注意,使用唤醒时,还需要在 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 申请唤醒锁的过程

  1. 创建服务端的 WakeLock。

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

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

  4. 更新唤醒锁 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;
    }
    
  5. 处理 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 发起屏幕请示,从而进行亮屏。这个过程,请读者参考前面的文章,自行分析。

  6. 标记唤醒锁已经改变。

  7. 更新电源状态,根据 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);
    }
}

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

  1. 归纳唤醒锁。这个过程会把所有的唤醒锁整合到一起,它会影响请求策略,也就是会影响屏幕最终状态。并用它也会决定是否阻止CPU挂起,也就是是否保持CPU运行。详见【归纳唤醒锁
  2. 更新电源状态。根据前面的文章可知,屏幕的最终状态是由请求的策略所决定的,而唤醒锁可以响应策略。详见【更新请求策略
  3. 如果有唤醒锁需要保证 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 的过程,我们要注意以下几点事情

  1. PowerManager.ON_AFTER_RELEASE 必须要与屏幕唤醒锁 PowerManager.FULL_WAKE_LOCK, PowerManager.SCREEN_BRIGHT_WAKE_LOCK, PowerManager.SCREEN_DIM_WAKE_LOCK 一起使用.
  2. PowerManager.ON_AFTER_RELEASE 更新了所有屏幕分组的用户行为时间,也就是说最终会导致所有屏幕都延长亮屏的时间。
  3. 更新用户行为时间,根据 PowerManagerService之自动灭屏 可知,用户行为时间的更新,最终会导致延长亮屏的时间

结束

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

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