返回顶部
首页 > 资讯 > 移动开发 >PowerManagerService之手动灭屏流程示例分析
  • 656
分享到

PowerManagerService之手动灭屏流程示例分析

PowerManagerService 手动灭屏PowerManagerService 灭屏 2022-11-13 18:11:38 656人浏览 薄情痞子
摘要

目录前言1. 灭屏流程2. 设备进入打盹状态2.1 更新用户行为2.2 更新显示屏的电源状态2.3 更新梦境状态3. 启动doze dream结束前言 PowerManagerSer

前言

PowerManagerService之亮屏流程分析 分析了亮屏的流程,并归纳出了一个适用于亮屏或灭屏的通用的流程。 但是,灭屏流程还有一些独特的东西,例如 dream 这个晦涩的东西,就是在灭屏流程中启动的。

灭屏分为手动灭屏和自动灭屏,手动灭屏一般就是指 Power 键灭屏,自动灭屏就是大家常说的屏幕超时而导致的灭屏。本文以 Power 键灭屏为例来分析手动灭屏,自动灭屏留到下一篇文章分析。

本文以 PowerManagerService之亮屏流程分析 作为基础,重复内容不做过多分析,希望读者务必先打好基础,再来阅读本文。

1. 灭屏流程

Power 键灭屏会调用 PowerManagerService#goToSleep()

// PowerManagerService.java
@Override // Binder call
public void GoToSleep(long eventTime, int reason, int flags) {
    // ...
    try {
        sleepDisplayGroup(Display.DEFAULT_DISPLAY_GROUP, eventTime, reason, flags, uid);
    }
}
private void sleepDisplayGroup(int groupId, long eventTime, int reason, int flags,
        int uid) {
    synchronized (mLock) {
        // 1. 更新 wakefulness
        if (sleepDisplayGroupNoUpdateLocked(groupId, eventTime, reason, flags, uid)) {
            // 2. 更新电源状态
            updatePowerStateLocked();
        }
    }
}

很熟悉的流程,先更新 wakefulness ,再更新电源状态。

不过,Power 键灭屏的更新 wakefulness 过程,有点小插曲,如下

private boolean sleepDisplayGroupNoUpdateLocked(int groupId, long eventTime, int reason,
        int flags, int uid) {
    // ...
    try {
        // ...
        // 召唤睡梦精灵,其实就是保存一个状态
        mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, true);
        // 更新 wakefulness 为 WAKEFULNESS_DOZING
        setWakefulnessLocked(groupId, WAKEFULNESS_DOZING, eventTime, uid, reason,
                 0,  null,  null);
        // 如果 flags 带有 PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE,更新 wakefulness 为 WAKEFULNESS_ASLEEP
        if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
            reallySleepDisplayGroupNoUpdateLocked(groupId, eventTime, uid);
        }
    }
    return true;
}

默认地,wakefulness 更新为 WAKEFULNESS_DOZING,然而,如果参数 flags 带有 PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE,wakefulness 更新为 WAKEFULNESS_ASLEEP。

wakefulness 表示设备处于何种状态,其实它有四个值,如下

public abstract class PowerManagerInternal {
    
    public static final int WAKEFULNESS_ASLEEP = 0;
    
    public static final int WAKEFULNESS_AWAKE = 1;
    
    public static final int WAKEFULNESS_DREAMING = 2;
    
    public static final int WAKEFULNESS_DOZING = 3;
}    

虽然注释已经写得很详细,但是有些概念你可能还真不知道,我献丑来描述下这四种设备状态。

  • WAKEFULNESS_ASLEEP : 表示设备处于休眠状态。屏幕处于灭屏状态,或者处于灭屏的过程中。设备只能被 wakeup() 唤醒,例如 Power 键唤醒设备。
  • WAKEFULNESS_AWAKE :表示设备处于唤醒状态。屏幕处于亮屏或者暗屏的状态。当用户行为超时(屏幕超时),设备可能会开启梦境(指屏保)或者进入休眠状态。当然 goToSleep() 也能使设备进入休眠状态。
  • WAKEFULNESS_DREAMING :设备处于梦境状态,这里指的是显示屏保。可以通过 wakeup() 结束屏保,唤醒设备,例如点击屏保。 也可以通过 goToSleep() 结束屏保,使设备进入休眠,例如,屏保时按 Power 键。
  • WAKEFULNESS_DOZING : 设备处于打盹状态(dozing)。这种状态几乎是一种休眠状态,但是屏幕处于一种低功耗状态。系统会让 dream manager 启动一个 doze 组件,这个组件会绘制一些简单的信息在屏幕上,但是前提是屏幕要支持 AOD(always on display)功能。我曾经买了一款 OLED 屏的手机,当关闭屏幕时,屏幕会显示时间、通知图标,等等一些信息,当时就觉得相当炫酷,其实这就是 AOD 功能的实现。如果 doze 组件启动失败,那么设备就进入休眠状态。可以通过 wakeup() 结束这个打盹状态,使设备处于唤醒状态,例如 Power 键亮屏。

好,回归正题,刚刚我们分析到,当 Power 键灭屏的时候,根据参数 flags 不同,wakefulness 可能会更新为 WAKEFULNESS_DOZING 或 WAKEFULNESS_ASLEEP,也就是设备进入打盹或者休眠状态。

设备进入休眠状态的过程与前面一篇文章分析的设备亮屏的过程是一致的,并且流程上更简单,请读者自行分析,本文分析设备进入打盹状态的流程。

2. 设备进入打盹状态

根据刚才的分析,wakefulness 更新为 WAKEFULNESS_DOZING,使设备进入打盹状态。并且根据前面一篇文章可知,此时 mDirty 设置了 DIRTY_DISPLAY_GROUP_WAKEFULNESS 和 DIRTY_WAKEFULNESS 标记位,分别表示显示屏分组的 wakefulness 改变 和 PowerManagerService 的 wakefulness 改变,其实就是表示设备状态改变。

现在根据 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;
            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.
        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!
        updateSuspendBlockerLocked();
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
}

根据前面文章可知,这里包含了很多功能,但是与 Power 键灭屏流程相关步骤如下

  • updateUserActivitySummaryLocked() 更新用户行为。屏幕请求策略的影响之一的因素就是用户行为。详见【2.1 更新用户行为
  • updateDisplayPowerStateLocked() 更新显示屏的电源状态,它会对 DisplayManagerService 发起电源请求,从而决定屏幕屏的状态,例如亮、灭、暗,等等。详见【2.2 更新显示屏的电源状态
  • updateDreamLocked() 更新梦境状态,其实就是通过 dream manager 启动 doze 组件,然后更新 PowerManagerService 的梦境状态。详见【2.3 更新梦境状态

屏保和 doze 组件都是由 dream manager 启动的,在 PowerManagerService 中,屏保被称为 dream,doze 组件称为 doze dream。

2.1 更新用户行为

// PowerManagerService.java
private void updateUserActivitySummaryLocked(long now, int dirty) {
    // ...
    // 遍历 display group id
    for (int groupId : mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked()) {
        int groupUserActivitySummary = 0;
        long groupNextTimeout = 0;
        // 注意,休眠状态是无法决定用户行为的
        if (mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != WAKEFULNESS_ASLEEP) {
            final long lastUserActivityTime =
                    mDisplayGroupPowerStateMapper.getLastUserActivityTimeLocked(groupId);
            final long lastUserActivityTimeNoChangeLights =
                    mDisplayGroupPowerStateMapper.getLastUserActivityTimeNoChangeLightsLocked(
                            groupId);
            // 1. 获取用户行为与超时时间
            // 上一次用户行为的时间 >= 上一次唤醒屏幕的时间
            if (lastUserActivityTime >= mLastWakeTime) {
                groupNextTimeout = lastUserActivityTime + screenOffTimeout - screenDimDuration;
                if (now < groupNextTimeout) { // 没有到 dim 时间
                    groupUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
                } else {
                    groupNextTimeout = lastUserActivityTime + screenOffTimeout;
                    if (now < groupNextTimeout) { // 处于 dim 时间段
                        groupUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
                    }
                }
            }
            // ...
        }
        // 2. DisplayGroupPowerStateMapper 保存用户行为
        mDisplayGroupPowerStateMapper.setUserActivitySummaryLocked(groupId,
                groupUserActivitySummary);
        }
    } // 遍历 display group id 结束
    // ...
    // 3. 定时更新电源状态
    // 这一步决定自动灭屏
    if (hasUserActivitySummary && nextTimeout >= 0) {
        scheduleUserInactivityTimeout(nextTimeout);
    }
}

更新用户行为,其实就是 DisplayGroupPowerStateMapper 保存用户行为。现在我们分析的情况是,Power 键导致屏幕由亮到灭的过程,因此这里获取的用户行为是 USER_ACTIVITY_SCREEN_BRIGHT。如果是由暗到灭的过程,那么用户行为是 USER_ACTIVITY_SCREEN_DIM。

那么,你是否有疑惑,系统正在进入打盹状态,为何用户行为是 USER_ACTIVITY_SCREEN_BRIGHT 或 USER_ACTIVITY_SCREEN_DIM,它们并不是灭屏的用户行为。因为系统根本没有定义灭屏的用户行为。

所有的用户行为如下

private static final int USER_ACTIVITY_SCREEN_BRIGHT = 1 << 0;
private static final int USER_ACTIVITY_SCREEN_DIM = 1 << 1;
private static final int USER_ACTIVITY_SCREEN_DREAM = 1 << 2;

用户行为只有三种,屏幕变亮,变暗,或者进入屏保,根本没有打盹的用户行为。那么为何是这样呢?

虽然此时 wakefulness 为 WAKEFULNESS_DOZING,只是表示正在进入打盹状态,实际上还没有正式处于打盹状态。系统真正进入打盹状态的标志是,dream manager 成功启动 doze 组件,并且获取到唤醒 PowerManager.DOZE_WAKE_LOCK。因此,不好定义一个打盹的用户行为。

2.2 更新显示屏的电源状态

根据前篇文章可知,更新显示屏的电源状态,其实就是向 DisplayManagerService 发起请求,并且请求策略才是真正决定屏幕状态(亮、灭、暗,等等)。

请求策略的获取方式如下

// PowerManagerService.java
int getDesiredScreenPolicyLocked(int groupId) {
    final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
    final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId);
    if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
        return DisplayPowerRequest.POLICY_OFF;
    } else if (wakefulness == WAKEFULNESS_DOZING) {
        // 如下的条件表示dream manager获取了 PowerManager.DOZE_WAKE_LOCK 唤醒锁
        if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
            return DisplayPowerRequest.POLICY_DOZE;
        }
        // mDozeAfterScreenOff 默认为 false
        // 不过,可以被 SystemUI 设置为 true,表示屏幕先灭屏,再进入doze状态
        if (mDozeAfterScreenOff) {
            return DisplayPowerRequest.POLICY_OFF;
        }
    }
    if (mIsVrModeEnabled) {
        return DisplayPowerRequest.POLICY_VR;
    }
    if ((wakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
            || !mBootCompleted
            || (mDisplayGroupPowerStateMapper.getUserActivitySummaryLocked(groupId)
            & USER_ACTIVITY_SCREEN_BRIGHT) != 0
            || mScreenBrightnessBoostInProgress) {
        return DisplayPowerRequest.POLICY_BRIGHT;
    }
    return DisplayPowerRequest.POLICY_DIM;
}

虽然现在的 wakefulness 为 WAKEFULNESS_DOZING,但是此时决定请求策略的仍然是用户行为。而根据刚刚的分析,此时用户行为是 USER_ACTIVITY_SCREEN_BRIGHT 或 USER_ACTIVITY_SCREEN_DIM,因此策略为 DisplayPowerRequest.POLICY_BRIGHT 或 DisplayPowerRequest.POLICY_DIM。也就是说屏幕状态继续保持不变。

看到这里,我和我的小伙伴都惊呆了!现在的状况明明是 Power 键灭屏,为何现在的分析结果是屏幕状态继续保持不变。你没有看错,我也没有写错。刚刚我们还提到,现在设备还没有正式处于打盹状态,因为还没有启动 doze dream。当成功启动 doze dream 后,dream manager 会获取唤醒锁 PowerManager.DOZE_WAKE_LOCK,此时 PowerManagerService 再次更新电源状态时,策略就会更新为 DisplayPowerRequest.POLICY_DOZE,它会让屏幕进入一个doze状态,并开启 AOD 功能。

如果你不想在灭屏时暂时保持屏幕状态不变,可以把 mDozeAfterScreenOff 设置为 true,这会导致请求策略更新为 DisplayPowerRequest.POLICY_OFF,也就是立即让屏幕进入休眠。

2.3 更新梦境状态

我们一直在强调,设备真正进入打盹,是在启动 doze dream 之后。doze dream 的启动过程是在更新梦境状态的过程中

// PowerManagerService.java
private void updateDreamLocked(int dirty, boolean displayBecameReady) {
    if ((dirty & (DIRTY_WAKEFULNESS
            | DIRTY_USER_ACTIVITY
            | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED
            | DIRTY_ATTENTIVE
            | DIRTY_WAKE_LOCKS
            | DIRTY_BOOT_COMPLETED
            | DIRTY_SETTINGS
            | DIRTY_IS_POWERED
            | DIRTY_STAY_ON
            | DIRTY_PROXIMITY_POSITIVE
            | DIRTY_BATTERY_STATE)) != 0 || displayBecameReady) {
        if (mDisplayGroupPowerStateMapper.areAllDisplaysReadyLocked()) {
            // 最终调用 handleSandman()
            scheduleSandmanLocked();
        }
    }
}
private void handleSandman(int groupId) { // runs on handler thread
    // Handle preconditions.
    final boolean startDreaming;
    final int wakefulness;
    synchronized (mLock) {
        mSandmanScheduled = false;
        final int[] ids = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
        if (!ArrayUtils.contains(ids, groupId)) {
            // Group has been removed.
            return;
        }
        wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
        if ((wakefulness == WAKEFULNESS_DREAMING || wakefulness == WAKEFULNESS_DOZING) &&
                mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId)
                && mDisplayGroupPowerStateMapper.isReady(groupId)) {
            // 1. 决定是否能启动梦境
            // canDreamLocked() 表示是否启动梦境中的屏保
            // canDozeLocked() 表示是否启动梦境中的doze组件,判断条件就是 wakefulness 为 WAKEFULNESS_DOZING
            startDreaming = canDreamLocked(groupId) || canDozeLocked();
            // 重置"召唤睡梦精灵"状态
            mDisplayGroupPowerStateMapper.setSandmanSummoned(groupId, false);
        } else {
            startDreaming = false;
        }
    }
    final boolean isDreaming;
    if (mDreamManager != null) {
        // Restart the dream whenever the sandman is summoned.
        if (startDreaming) {
            mDreamManager.stopDream(false );
            // 2. 启动梦境 doze 组件
            mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
        }
        // 判断是否正在启动中
        isDreaming = mDreamManager.isDreaming();
    } else {
        isDreaming = false;
    }
    mDozeStartInProgress = false;
    synchronized (mLock) {
        final int[] ids = mDisplayGroupPowerStateMapper.getDisplayGroupIdsLocked();
        if (!ArrayUtils.contains(ids, groupId)) {
            return;
        }
        if (startDreaming && isDreaming) {
            // ...
        }
        if (mDisplayGroupPowerStateMapper.isSandmanSummoned(groupId)
                || mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId) != wakefulness) {
            return; // wait for next cycle
        }
        // Determine whether the dream should continue.
        long now = mClock.uptimeMillis();
        if (wakefulness == WAKEFULNESS_DREAMING) {
            // ...
        } else if (wakefulness == WAKEFULNESS_DOZING) {
            // 3. 正在启动梦境的doze组件,那继续
            if (isDreaming) {
                return; // continue dozing
            }
            // 4. 没有启动成功,或者doze组件自己结束梦境,进入更新 wakefulness 为 WAKEFULNESS_ASLEEP 流程
            // Doze has ended or will be stopped.  Update the power state.
            reallySleepDisplayGroupNoUpdateLocked(groupId, now, Process.SYSTEM_UID);
            updatePowerStateLocked();
        }
    }
    // ...
}

更新梦境状态过程如下

  • 判断是否能进入梦境。梦境其实有两种功能,一种是屏保,另一种是 doze 组件。由于此时 wakefulness 为 WAKEFULNESS_DOZING,因此可以满足启动 doze dream 的条件。
  • 通过 dream manager 启动 dream。此时,启动的就是 doze dream,而不是屏保。
  • 如果正在启动,那就继续。继续什么呢?详见【启动doze dream
  • 如果启动失败,进入休眠的流程。

休眠的流程最简单了,首先更新 wakefulenss 为 WAKEFULNESS_ASLEEP,然后更新请求策略为 DisplayPowerRequest.POLICY_OFF, 这使屏幕直接灭屏。更新请求策略的过程如下

int getDesiredScreenPolicyLocked(int groupId) {
    final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
    final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId);
    if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
        return DisplayPowerRequest.POLICY_OFF;
    } else if (wakefulness == WAKEFULNESS_DOZING) {
        // ...
    }
    // ...
}

可以看到,wakefulness 为 WAKEFULNESS_ASLEEP 时,也就是设备处于休眠状态时,请求策略不受任何因素影响,直接为DisplayPowerRequest.POLICY_OFF,它会使屏幕休眠。

3. 启动doze dream

现在我们离设备正式进入打盹状态,只有一步之遥,而这一步就是调用 DreamManagerService#startDreamInternal() 启动 doze dream

// DreamManagerService.java
private void startDreamInternal(boolean doze) {
    final int userId = ActivityManager.getCurrentUser();
    // 获取 doze dream 或 screensaver
    final ComponentName dream = chooseDreamForUser(doze, userId);
    if (dream != null) {
        synchronized (mLock) {
            // 启动 dream,此时启动的 doze dream,
            startDreamLocked(dream, false , doze, userId);
        }
    }
}
private ComponentName chooseDreamForUser(boolean doze, int userId) {
    if (doze) {
        ComponentName dozeComponent = getDozeComponent(userId);
        return validateDream(dozeComponent) ? dozeComponent : null;
    }
    // ...
}
private ComponentName getDozeComponent(int userId) {
    if (mForceAmbientDisplayEnabled || mDozeConfig.enabled(userId)) {
        // 从配置文件 config.xml 中获取 config_dozeComponent 
        return ComponentName.unflattenFromString(mDozeConfig.ambientDisplayComponent());
    } else {
        return null;
    }
}

系统没有配置 doze dream 组件,但是没关系,经过研究源码中的一些已经实现的 doze 组件,我们可以发现, doze 组件都是继承自一个名为 DreamService 的 Service,这个 Service 就是四大组件的 Service。

现在继续看看启动 doze 组件的过程

// DreamManagerService.java
private void startDreamLocked(final ComponentName name,
        final boolean isTest, final boolean canDoze, final int userId) {
    // ...
    final Binder newToken = new Binder();
    mCurrentDreamToken = newToken;
    mCurrentDreamName = name;
    mCurrentDreamIsTest = isTest;
    mCurrentDreamCanDoze = canDoze;
    mCurrentDreamUserId = userId;
    PowerManager.WakeLock wakeLock = mPowerManager
            .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
    // WakeLock#wrap() 方法,是保证在执行 Runnbale 之前获取锁,并且执行完 Runnable 后释放锁。
    // 也就是说,保持 CPU 运行,直到启动 doze dream 完毕。
    mHandler.post(wakeLock.wrap(() -> {
        mAtmInternal.notifyDreamStateChanged(true);
        if (!mCurrentDreamName.equals(mAmbientDisplayComponent)) {
            mUiEventLogger.log(DreamManagerEvent.DREAM_START);
        }
        // 开启 dream
        mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock);
    }));
}

通过 DreamController#startDream() 启动 doze dream

// DreamController.java
public void startDream(Binder token, ComponentName name,
        boolean isTest, boolean canDoze, int userId, PowerManager.WakeLock wakeLock) {
    stopDream(true , "starting new dream");
    try {
        // 其实是发送 Intent.ACTION_CLOSE_SYSTEM_DIALOGS 广播
        // Close the notification shade. No need to send to all, but better to be explicit.
        mContext.sendBroadcastAsUser(mCloseNotificationShadeIntent, UserHandle.ALL);
        // 1. 创建一条记录 dream 记录
        // 注意,DreamRecord 实现了 ServiceConnection
        mCurrentDream = new DreamRecord(token, name, isTest, canDoze, userId, wakeLock);
        mDreamStartTime = SystemClock.elapsedRealtime();
        MetricsLogger.visible(mContext,
                mCurrentDream.mCanDoze ? MetricsEvent.DOZING : MetricsEvent.DREAMING);
        Intent intent = new Intent(DreamService.SERVICE_INTERFACE);
        intent.setComponent(name);
        intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        try {
            // 2. 连接Service
            if (!mContext.bindServiceAsUser(intent, mCurrentDream,
                    Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
                    new UserHandle(userId))) {
                Slog.e(TAG, "Unable to bind dream service: " + intent);
                stopDream(true , "bindService failed");
                return;
            }
        } catch (SecurityException ex) {
            Slog.e(TAG, "Unable to bind dream service: " + intent, ex);
            stopDream(true , "unable to bind service: SecExp.");
            return;
        }
        mCurrentDream.mBound = true;
        mHandler.postDelayed(mStopUnconnectedDreamRunnable, DREAM_CONNECTION_TIMEOUT);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_POWER);
    }
}

既然 doze dream 是一个 Service,那么启动它的过程就是绑定 Service 的过程。

由于 DreamRecord 实现了 ServiceConnection,因此当服务成功连接上,会调用 DreamRecord#onServiceConnected()

// DreamRecord.java
public void onServiceConnected(ComponentName name, final IBinder service) {
    mHandler.post(() -> {
        mConnected = true;
        if (mCurrentDream == DreamRecord.this && mService == null) {
            attach(IDreamService.Stub.asInterface(service));
            // Wake lock will be released once dreaming starts.
        } else {
            releaseWakeLockIfNeeded();
        }
    });
}
private void attach(IDreamService service) {
    try {
        // DreamRecord 监听 Service 生死
        service.asBinder().linkToDeath(mCurrentDream, 0);
        // 第三个参数是一个binder回调,用于接收结果
        service.attach(mCurrentDream.mToken, mCurrentDream.mCanDoze,
                mCurrentDream.mDreamingStartedCallback);
    } catch (RemoteException ex) {
        Slog.e(TAG, "The dream service died unexpectedly.", ex);
        stopDream(true , "attach failed");
        return;
    }
    mCurrentDream.mService = service;
    // 发送 Intent.ACTION_DREAMING_STARTED 广播
    if (!mCurrentDream.mIsTest) {
        mContext.sendBroadcastAsUser(mDreamingStartedIntent, UserHandle.ALL);
        mCurrentDream.mSentStartBroadcast = true;
    }
}

可以看到,成功启动 Service 后,会调用 DreamService#attach() ,然后发送 dream 启动的广播 Intent.ACTION_DREAMING_STARTED

这里要结合具体的 doze dream Service 来分析,既然系统没有配置,那么以 SystemUI 中的 DozeService 为例进行分析,首先看看它的 onCreate() 过程

public class DozeService extends DreamService
        implements DozeMachine.Service, RequestDoze, PluginListener<DozeServicePlugin> {
    @Override
    public void onCreate() {
        super.onCreate();
        // 设置为无窗口模式
        setWindowless(true);
        mPluginManager.addPluginListener(this, DozeServicePlugin.class, false );
        DozeComponent dozeComponent = mDozeComponentBuilder.build(this);
        mDozeMachine = dozeComponent.getDozeMachine();
    }
}    

注意,它设置了一个无窗口模式,后面会用到。

刚才分析过,DreamService 绑定成功后,会调用 DreamService#attach()

private void attach(IBinder dreamToken, boolean canDoze, IRemoteCallback started) {
    // ...
    mDreamToken = dreamToken;
    mCanDoze = canDoze;
    // 只有 doze dream 才能无窗口
    if (mWindowless && !mCanDoze) {
        throw new IllegalStateException("Only doze dreams can be windowless");
    }
    mDispatchAfterOnAttachedToWindow = () -> {
        if (mWindow != null || mWindowless) {
            mStarted = true;
            try {
                // 由子类实现
                onDreamingStarted();
            } finally {
                try {
                    started.sendResult(null);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
        }
    };
    if (!mWindowless) {
        // ...
    } else {
        // 无窗口下,直接调用 onDreamingStarted()
        mDispatchAfterOnAttachedToWindow.run();
    }
}

对于 SystemUI 的 DozeService,它是无窗口的,因此直接调用了它的 onDreamingStarted() 方法

// DozeService.java
public void onDreamingStarted() {
    super.onDreamingStarted();
    mDozeMachine.requestState(DozeMachine.State.INITIALIZED);
    // 调用父类的方法
    startDozing();
    if (mDozePlugin != null) {
        mDozePlugin.onDreamingStarted();
    }
}

再次进入 DreamService#startDozing()

public void startDozing() {
    if (mCanDoze && !mDozing) {
        mDozing = true;
        updateDoze();
    }
}
private void updateDoze() {
    if (mDreamToken == null) {
        Slog.w(TAG, "Updating doze without a dream token.");
        return;
    }
    if (mDozing) {
        try {
            // 通知 dream mananger,service 正在启动 doze 功能
            mDreamManager.startDozing(mDreamToken, mDozeScreenState, mDozeScreenBrightness);
        } catch (RemoteException ex) {
            // system server died
        }
    }
}

最终,把启动 doze 的信息返回给 DreamManagerService

// DreamManagerService.java
private void startDozingInternal(IBinder token, int screenState,
        int screenBrightness) {
    synchronized (mLock) {
        if (mCurrentDreamToken == token && mCurrentDreamCanDoze) {
            mCurrentDreamDozeScreenState = screenState;
            mCurrentDreamDozeScreenBrightness = screenBrightness;
            // 1. 通知PowerManagerService,保存doze状态下的屏幕状态和屏幕亮度
            mPowerManagerInternal.setDozeOverrideFromDreamManager(
                    screenState, screenBrightness);
            if (!mCurrentDreamIsDozing) {
                mCurrentDreamIsDozing = true;
                //2. 获取 PowerManager.DOZE_WAKE_LOCK 唤醒锁
                mDozeWakeLock.acquire();
            }
        }
    }
}

DreamManagerService 又把信息传递给了 PowerManagerService,这些信息包括 doze 下的屏幕状态,以及屏幕亮度,而PowerManagerService 也只是简单保存了这些信息。

然后获取了一个 PowerManager.DOZE_WAKE_LOCK,这就是设备进入打盹状态的最重要的一步。它会影响屏幕请求策略,如下

// PowerManagerService.java
int getDesiredScreenPolicyLocked(int groupId) {
    final int wakefulness = mDisplayGroupPowerStateMapper.getWakefulnessLocked(groupId);
    final int wakeLockSummary = mDisplayGroupPowerStateMapper.getWakeLockSummaryLocked(groupId);
    if (wakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
        return DisplayPowerRequest.POLICY_OFF;
    } else if (wakefulness == WAKEFULNESS_DOZING) {
        // 如下的条件表示dream manager获取了 PowerManager.DOZE_WAKE_LOCK 唤醒锁
        if ((wakeLockSummary & WAKE_LOCK_DOZE) != 0) {
            return DisplayPowerRequest.POLICY_DOZE;
        }
        if (mDozeAfterScreenOff) {
            return DisplayPowerRequest.POLICY_OFF;
        }
    }
    // ...
}

可以看到 DreamManagerService 成功获取到 PowerManager.DOZE_WAKE_LOCK 唤醒锁后,请求策略现在为 DisplayPowerRequest.POLICY_DOZE,这会导致屏幕进入 doze 状态,这是一种几乎休眠的状态,也是一种低功耗状态,并且开启了 AOD 功能,当然前提是屏幕支持这个功能,据说只有 OLED 屏支持 AOD 功能。

此时,设备才真正的处于了打盹状态。而 PowerManagerService 更新电源状态的后续过程,我觉得就不重要了,留给读者自行分析。

结束

本文分析了灭屏过程,设备如何进入打盹状态,可谓是波澜起伏。但是注意,这只是手动灭屏。而我们平常看到了屏幕超时导致的自动灭屏,下一篇文章继续分析。

在本文中,我们还提到设备有一种状态,梦境状态,其实指的就是屏保。你说它没用吧,有时候还是有点作用的,你说它有用吧,现在几乎看不到用它的地方。对于这种食之无味,弃之可惜的东西,要不要分析呢,我还在犹豫。

以上就是PowerManagerService之手动灭屏的详细内容,更多关于PowerManagerService 灭屏的资料请关注编程网其它相关文章!

--结束END--

本文标题: PowerManagerService之手动灭屏流程示例分析

本文链接: https://lsjlt.com/news/170310.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • PowerManagerService之手动灭屏流程示例分析
    目录前言1. 灭屏流程2. 设备进入打盹状态2.1 更新用户行为2.2 更新显示屏的电源状态2.3 更新梦境状态3. 启动doze dream结束前言 PowerManagerSer...
    99+
    2022-11-13
    PowerManagerService 手动灭屏 PowerManagerService 灭屏
  • PowerManagerService之自动灭屏流程解析
    目录前言自动灭屏自动灭屏小结延长亮屏时间结束前言 PowerManagerService之亮屏流程分析 归纳了亮屏/灭屏的通用流程,PowerManagerService之手动灭屏 ...
    99+
    2022-11-13
    PowerManagerService 自动灭屏 PowerManagerService 灭屏
  • PowerManagerService之亮屏流程示例分析
    目录前言Power键亮屏1. 更新 wakefulness1.1 更新 PMS 的 wakefulness1.2 保存用户行为时间1.3 更新 wakefulness 小结2. 更新...
    99+
    2022-11-13
    PowerManagerService 亮屏 PowerManager Service
  • linux启动流程的示例分析
    这篇文章将为大家详细讲解有关linux启动流程的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。linux启动流程简介我们都知道,由于linux的稳定性,通常被作为服务器系统,要想称为一个PHP的高...
    99+
    2023-06-09
  • ContentProvider启动流程示例解析
    目录正文App启动installContentProviders正文 ContentProvider是内容提供者,可以跨进程提供数据。 大家都知道,ContentProvider的...
    99+
    2023-03-02
    ContentProvider启动流程 ContentProvider启动
  • koa之中间件流程控制的示例分析
    这篇文章主要为大家展示了“koa之中间件流程控制的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“koa之中间件流程控制的示例分析”这篇文章吧。1. ko...
    99+
    2024-04-02
  • HDFS读流程的示例分析
    这篇文章给大家分享的是有关HDFS读流程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。客户端或者用户通过调用FileSystem对象的Open()方法打开需要读取的文件,这时就是HDSF分布式系统所获取...
    99+
    2023-06-02
  • Java流程控制之选择结构的示例分析
    这篇文章主要介绍Java流程控制之选择结构的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!布尔表达式:布尔表达式(Boolean expression)是一段代码声明,它最终只有true(真)和false(假...
    99+
    2023-06-22
  • Java流程控制之顺序结构的示例分析
    这篇文章主要介绍了Java流程控制之顺序结构的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Java中的流程控制语句可以这样分类:顺序结构,选择结构,循环结构。1.关...
    99+
    2023-06-22
  • web开发之网站制作流程的示例分析
    这篇文章主要介绍了web开发之网站制作流程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。  1、关于网站的页面设计  对于网站的页面设计这一部分主要是由设计师完成,...
    99+
    2023-06-10
  • AngularJS执行流程的示例分析
    这篇文章主要介绍了AngularJS执行流程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一.启动阶段浏览器解析HTML页面,读取...
    99+
    2024-04-02
  • http访问解析流程的示例分析
    这篇文章给大家分享的是有关http访问解析流程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。http访问网址域名解析流程:1、在浏览器中输入www.qq.com域名,操...
    99+
    2024-04-02
  • Javascript柯里化流程的示例分析
    这篇文章将为大家详细讲解有关Javascript柯里化流程的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。函数式编程是一种如今比较流行的编程范式,它主张将函数作为...
    99+
    2024-04-02
  • php中PHPCMS流程图的示例分析
    小编给大家分享一下php中PHPCMS流程图的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!首先要知道PHPCMS这个系...
    99+
    2024-04-02
  • HDFS文件写流程的示例分析
    小编给大家分享一下HDFS文件写流程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!图理解:详解HDFS写的过程:1.Client客户端发出请求open到...
    99+
    2023-06-03
  • HDFS文件读流程的示例分析
    这篇文章主要介绍了HDFS文件读流程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。用图来理解:详解HDFS读的过程:1.Client客户端发出请求open到Dist...
    99+
    2023-06-03
  • 网站开发流程的示例分析
    这篇文章主要介绍了网站开发流程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。一、必不可少的准备工作开发网站的目的在于吸引大量的客流量,所以以提高网站的访问量为网站开...
    99+
    2023-06-10
  • requestAnimationFrame定时动画屏幕刷新率节流示例浅析
    目录前言早期定时动画屏幕刷新率requestAnimationFramecancelAnimationFrame通过 requestAnimationFrame 节流前言 很长时间...
    99+
    2023-02-23
    requestAnimationFrame刷新节流 requestAnimationFrame定时动画 requestAnimationFrame屏幕刷新率
  • android性能优化之启动过程的示例分析
    小编给大家分享一下android性能优化之启动过程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、应用的启动方式通常来说,启动方式分为两种:冷启动和热...
    99+
    2023-05-30
    android
  • SOCKET之多线程的示例分析
    这篇文章主要介绍了SOCKET之多线程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。多线程的原理图:可以理解成成10086的多个客...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作