分析了PMS部分的亮屏流程。PMS模块中也提供了灭屏接口goTosleep(),方法给其他组件或应用来关闭屏幕,这里将对PMS部分涉及到的灭屏流程进行分析。
1.非自动灭屏流程
当系统其他组件发起灭屏时,将调用PowerManager#goTosleep()方法:
/**
* time: 开始灭屏时间
* reason: 灭屏原因
* flags: 灭屏可选标记值
*/
public void goToSleep(long time, int reason, int flags) {
try {
mService.goToSleep(time, reason, flags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
PowerManager中直接调用到PMS中,并在进行权限检查后,调用goToSleepInternal()进入PMS模块内部:
private void goToSleepInternal(long eventTime, int reason, int flags, int uid) {
synchronized (mLock) {
// 灭屏成功返回true
if (goToSleepNoUpdateLocked(eventTime, reason, flags, uid)) {
updatePowerStateLocked();
}
}
}
首先将会调用goToSleepNoUpdateLocked()方法,并在该方法返回true后,调用updatePowerStateLocked()开始更新全局状态,完成整个灭屏流程。
1.1.goToSleepNoUpdateLocked()更新验证和灭屏状态更新
private boolean goToSleepNoUpdateLocked(long eventTime, int reason, int flags, int uid) {
......
try {
// 表示需要召唤"睡眠精灵",即请求进入Dreamland模式
mSandmanSummoned = true;
// 表示处于Doze过程中
mDozeStartInProgress = true;
// 设置唤醒状态为Dozing
setWakefulnessLocked(WAKEFULNESS_DOZING, reason, eventTime);
// 灭屏后屏幕相关WakeLock锁将失去意义,所以统计下清除锁的个数
int numWakeLocksCleared = 0;
final int numWakeLocks = mWakeLocks.size();
for (int i = 0; i < numWakeLocks; i++) {
final WakeLock wakeLock = mWakeLocks.get(i);
switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) {
case PowerManager.FULL_WAKE_LOCK:
case PowerManager.SCREEN_BRIGHT_WAKE_LOCK:
case PowerManager.SCREEN_DIM_WAKE_LOCK:
numWakeLocksCleared += 1;
break;
}
}
// 如果带有GO_TO_SLEEP_FLAG_NO_DOZE标记,则不会经过Doze直接进入Asleep状态
if ((flags & PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE) != 0) {
reallyGoToSleepNoUpdateLocked(eventTime, uid);
}
}
return true;
}
首先,更新灭屏相关属性值,然后通过setWakefulnessLocked()方法,将唤醒状态mWakefulnessRaw值设置为Dozing;最后如果goToSleep()中还带有PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE标记,则将跳过Dozing状态,直接进入Asleep状态。
因此,如果goToSleep()不带PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE时,会先将唤醒状态设置为Dozing状态,再根据条件决定是否进入Asleep。
setWakefulnessLocked()方法在第三篇中也分析过了,会更新mWakefulnessRaw值,然后调用Notifier#onWakefulnessChangeStarted()做系统唤醒状态改变后的交互状态更新,然后执行handleEarlyInteractiveChange()方法。
1.2.Notifier#handleEarlyInteractiveChange()
看下该方法中灭屏流程相关逻辑:
private void handleEarlyInteractiveChange() {
synchronized (mLock) {
if (mInteractive) {
......
} else {
// Going to sleep...
// 通知WMS开始灭屏
final int why = translateOffReason(mInteractiveChangeReason);
mHandler.post(new Runnable() {
@Override
public void run() {
mPolicy.startedGoingToSleep(why);
}
});
}
}
}
这里会通知WMS模块开始灭屏,WMS中会进一步通知给Keyguard等进行相应操作。
1.3.updatePowerStateLocked()
当上述方法执行完毕并返回后,开始执行updatePowerStateLocked()方法,在这里我们只看其中灭屏流程相关,有如下部分:
// 向DMS中发起灭屏请求,将亮度设置为0, Display状态设置为DOZE/OFF
boolean displayBecameReady = updateDisplayPowerStateLocked(dirtyPhase2);
//更新Dreamland状态
updateDreamLocked(dirtyPhase2, displayBecameReady);
// 收尾工作
finishWakefulnessChangeIfNeededLocked();
//释放锁
updateSuspendBlockerLocked();
1.4.updateDisplayPowerStateLocked()请求DMS
1.5.updateDreamLocked()更新Dream状态
该方法用来决定灭屏后是否会进入Dream状态或Doze状态,如屏保、AOD、FOD等功能就是在此状态下运行。当完成DMS请求且DMS返回请求结果后,才会执行该方法内容,并最终在handleSandman()方法中进行处理:
private void handleSandman() { // runs on handler thread
.....
synchronized (mLock) {
mSandmanScheduled = false;
wakefulness = getWakefulnessLocked();
// 召唤睡眠精灵且Display状态准备完毕
if (mSandmanSummoned && mDisplayReady) {
// 确定是需要进入Dreamland
startDreaming = canDreamLocked() || canDozeLocked();
mSandmanSummoned = false; // 重置
} else {
startDreaming = false;
}
}
final boolean isDreaming;
if (mDreamManager != null) {
// 进入Dreamland状态
if (startDreaming) {
mDreamManager.stopDream(false /*immediate*/);
mDreamManager.startDream(wakefulness == WAKEFULNESS_DOZING);
}
// 确认是否完成Dreamland
isDreaming = mDreamManager.isDreaming();
} else {
isDreaming = false;
}
// 重置,表示完成Dozing过程
mDozeStartInProgress = false;
synchronized (mLock) {
......
if (wakefulness == WAKEFULNESS_DREAMING) {
......
} else if (wakefulness == WAKEFULNESS_DOZING) {
// 如果在进行Dreaming,则直接返回
if (isDreaming) {
return; // continue dozing
}
// 否则真正执行灭屏流程
reallyGoToSleepNoUpdateLocked(now, Process.SYSTEM_UID);
updatePowerStateLocked();
}
}
// Stop dream.
if (isDreaming) {
mDreamManager.stopDream(false /*immediate*/);
}
}
当mWakefulness变为WAKEFULNESS_DOZING后,执行到这里时,如果没有能够进入Dreamland,那么将通过reallyGoToSleepNoUpdateLocked()方法,开始进行真正的”休眠“。
1.6.reallyGoToSleepNoUpdateLocked()
该方法中,将mWakefulnessRaw值设置为WAKEFULNESS_ASLEEP,表示进入休眠状态:
private boolean reallyGoToSleepNoUpdateLocked(long eventTime, int uid) {
......
try {
// 设置唤醒状态为ASLEEP
setWakefulnessLocked(WAKEFULNESS_ASLEEP, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
eventTime);
}
return true;
}
因此在灭屏过程中,系统唤醒状态会先设置为Dozing,并通过DreamManager去启动进入Dreamland状态。没有进入Dreamland时,才会将唤醒状态设置为Asleep。如果goToSleep()带有PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE标记,则直接进Asleep。
1.7.Notifier#handleLateInteractiveChange()
更新完Dreamland相关状态后,执行finishWakefulnessChangeIfNeededLocked()方法来做唤醒状态变化完成后的任务,并执行Notifier#handleLateInteractiveChange()方法,进行交互状态变化完成后的任务,这里看下灭屏流程部分:
private void handleLateInteractiveChange() {
synchronized (mLock) {
final int interactiveChangeLatency =
(int) (SystemClock.uptimeMillis() - mInteractiveChangeStartTime);
if (mInteractive) {
// Finished waking up...
.......
} else {
......
mHandler.post(new Runnable() {
@Override
public void run() {
mPolicy.finishedGoingToSleep(why);
}
});
// 表示即将要发送广播状态为ASLEEP
mPendingInteractiveState = INTERACTIVE_STATE_ASLEEP;
// 表示即将要发送灭屏广播
mPendingGoToSleepBroadcast = true;
// 发送广播
updatePendingBroadcastLocked();
}
}
}
首先,通过mPolicy.finishedGoingToSleep()通知WMS模块已经完成灭屏。然后发送灭屏广播。
1.8.updatePendingBroadcastLocked()发送灭屏广播
updatePendingBroadcastLocked()方法在上一篇文章中已经分析过,当满足灭屏广播的发送条件时,将会进行灭屏广播的发送:
// frameworks/base/services/core/java/com/android/server/power/Notifier.java
private void sendGoToSleepBroadcast() {
if (mActivityManagerInternal.isSystemReady()) {
mContext.sendOrderedBroadcastAsUser(mScreenOffIntent, UserHandle.ALL, null,
mGoToSleepBroadcastDone, mHandler, 0, null, null);
} else {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3, 1);
sendNextBroadcast();
}
}
1.9.updateSuspendBlockerLocked()
最后执行updateSuspendBlockerLocked()方法,灭屏后会释放掉mDisplaySuspendBlocker锁,如果此时没有PARTICAL_WAKE_LOCK、DOZE_WAKE_LOCK、DRAW_WAKE_LOCK这三类锁,mWakeLockSuspendBlocker也将会释放掉,CPU将会在适当时机进行休眠。
2.自动灭屏流程
自动灭屏的影响因素主要有两个:
自动休眠时间:用户在Settings中设置的值;
用户活动时间:用户最后和设备进行交互的时间点。
当用户在某时刻和设备有交互时,会记录该时间点,并以该时间为起始时间,到达"起始时间+自动休眠时间"这个时间点后,开始执行灭屏流程。
在前面几篇文章中多次提到了userActivityNoUpdateLocked()方法,但没有分析,这里正是分析它的最佳时机。因为就是这个方法,会在每一次有用户活动时更新最近一次的交互时间。
2.1.userActivityNoUpdateLocked()更新最近一次交互时间
当用户触摸屏幕、按键时,Input模块中在处理这些Key事件后,会通知PMS来更新用户活动时间:
// frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
void NativeInputManager::pokeUserActivity(nsecs_t eventTime, int32_t eventType) {
ATRACE_CALL();
android_server_PowerManagerService_userActivity(eventTime, eventType);
}
// frameworks/base/services/core/jni/com_android_server_power_PowerManagerService.cpp
void android_server_PowerManagerService_userActivity(nsecs_t eventTime, int32_t eventType) {
if (gPowerManagerServiceObj) {
// ......
env->CallVoidMethod(gPowerManagerServiceObj,
gPowerManagerServiceClassInfo.userActivityFromNative,
nanoseconds_to_milliseconds(eventTime), eventType, 0);
}
}
input模块中通过JNI调用,进入PMS中:
// frameworks/base/services/core/java/com/android/server/power/PowerManagerService.java
private void userActivityFromNative(long eventTime, int event, int flags) {
userActivityInternal(eventTime, event, flags, Process.SYSTEM_UID);
}
private void userActivityInternal(long eventTime, int event, int flags, int uid) {
synchronized (mLock) {
// 更新用户活动时间
if (userActivityNoUpdateLocked(eventTime, event, flags, uid)) {
// 更新PMS全局状态
updatePowerStateLocked();
}
}
}
Native层代码调用userActivityFromNative()方法进入PMS中,在这个方法中,首先通过userActivityNoUpdateLocked()方法更新用户活动时间,然后updatePowerStateLocked()方法进行全局状态的更新。userActivityNoUpdateLocked()方法如下:
private boolean userActivityNoUpdateLocked(long eventTime, int event, int flags, int uid) {
// ......
// 通知Notifier更新用户活动状态
mNotifier.onUserActivity(event, uid);
mAttentionDetector.onUserActivity(eventTime, event);
// 重置,表示是否WMS覆盖了最长不交互时间阈值
if (mUserInactiveOverrideFromWindowManager) {
mUserInactiveOverrideFromWindowManager = false;
mOverriddenTimeout = -1;
}
// 已灭屏,或调用该方法带有USER_ACTIVITY_FLAG_INDIRECT,不会更新用户活动时间
if (mWakefulness == WAKEFULNESS_ASLEEP
|| mWakefulness == WAKEFULNESS_DOZING
|| (flags & PowerManager.USER_ACTIVITY_FLAG_INDIRECT) != 0) {
return false;
}
// 根据userid更新该user在PowerProfileState中记录的用户活动时间
maybeUpdateForegroundProfileLastActivityLocked(eventTime);
// 带有USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS标记时,会延长亮屏一会儿
if ((flags & PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS) != 0) {
if (eventTime > mLastUserActivityTimeNoChangeLights
&& eventTime > mLastUserActivityTime) {
// 更新最后一次活动时间给mLastUserActivityTimeNoChangeLights
mLastUserActivityTimeNoChangeLights = eventTime;
mDirty |= DIRTY_USER_ACTIVITY;
// ......
return true;
}
} else {
if (eventTime > mLastUserActivityTime) {
// 更新最后一次活动时间给mLastUserActivityTime
mLastUserActivityTime = eventTime;
mDirty |= DIRTY_USER_ACTIVITY;
// ......
return true;
}
}
} finally { }
return false;
}
以上方法中,如果调用时带有PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS标记,则将交互时间赋值给mLastUserActivityTimeNoChangeLights变量,否则将交互时间赋给mLastUserActivityTime变量。这个flag标志用于延长亮屏或Dim的时长一小会儿。在第二篇文章中说过,当在释放WakeLock锁时带有ON_AFTER_RELEASE标记,会延长超时灭屏时间,原理就是通过该Flag实现。
当执行完成后。mLastUserActivityTime或者mLastUserActivityTimeNoChangeLights的值得到了更新。接下来又会进入updatePowerStateLocked()方法,来更新PMS全局状态。
2.2.updatePowerStateLocked()更新PMS全局状态
在这个方法中,和自动灭屏相关的逻辑如下:
private void updatePowerStateLocked() {
// ......
try {
for (;;) {
int dirtyPhase1 = mDirty;
dirtyPhase2 |= dirtyPhase1;
mDirty = 0;
updateWakeLockSummaryLocked(dirtyPhase1);
updateUserActivitySummaryLocked(now, dirtyPhase1);
if (!updateWakefulnessLocked(dirtyPhase1)) {
break;
}
}
// ......
} finally { }
}
updateWakeLockSummaryLocked()方法会将当前系统所有的WakeLock统计到mWakeLock变量上。 updateUserActivitySummaryLocked()方法会更新用户活动状态,并确认多久开始自动灭屏。
2.3.updateUserActivitySummaryLocked()
该方法用来更新用户活动状态:
private void updateUserActivitySummaryLocked(long now, int dirty) {
// Update the status of the user activity timeout timer.
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY
| DIRTY_WAKEFULNESS | DIRTY_SETTINGS)) != 0) {
mHandler.removeMessages(MSG_USER_ACTIVITY_TIMEOUT);
long nextTimeout = 0;
// mWakefulnessRaw为Asleep时,不执行该流程
if (getWakefulnessLocked() == WAKEFULNESS_AWAKE
|| getWakefulnessLocked() == WAKEFULNESS_DREAMING
|| getWakefulnessLocked() == WAKEFULNESS_DOZING) {
// 获取细微模式超时时间
final long attentiveTimeout = getAttentiveTimeoutLocked();
// 获取彻底进入休眠超时时间
final long sleepTimeout = getSleepTimeoutLocked(attentiveTimeout);
// 获取自动灭屏超时时间
final long screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout,
attentiveTimeout);
// 获取进入Dim所需时间
final long screenDimDuration = getScreenDimDurationLocked(screenOffTimeout);
// 是否WindowManager中覆盖了超时时间
final boolean userInactiveOverride = mUserInactiveOverrideFromWindowManager;
// DevicePolicy是否对于单用户设置了超时时间
final long nextProfileTimeout = getNextProfileTimeoutLocked(now);
// 重置统计值
mUserActivitySummary = 0;
// 最近一次用户活动时间>=最近一次唤醒时间
if (mLastUserActivityTime >= mLastWakeTime) {
// 如果最近一次用户活动时间 + 自动休眠设置 - Dim时间大于当前时间,则此时为亮屏状态,mUserActivitySummary状态设置为USER_ACTIVITY_SCREEN_BRIGHT
nextTimeout = mLastUserActivityTime
+ screenOffTimeout - screenDimDuration;
if (now < nextTimeout) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
} else {
// 如果最近一次用户活动时间 + 自动休眠时间 > 当前时间 > 最近一次用户活动时间+自动休眠设置-Dim时间,此时为Dim状态
// mUserActivitySummary状态设置为USER_ACTIVITY_SCREEN_DIM
nextTimeout = mLastUserActivityTime + screenOffTimeout;
if (now < nextTimeout) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
}
}
}
// 如果进该if块,说明不满足mLastUserActivityTime >= mLastWakeTime,那么就需要判断mLastUserActivityTimeNoChangeLights,
// mLastUserActivityTimeNoChangeLights是带有USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS标记的最近一次用户活动时间
if (mUserActivitySummary == 0
&& mLastUserActivityTimeNoChangeLights >= mLastWakeTime) {
// 如果当前时间 < 最近一次用户活动时间 + 自动休眠时间,说明要么亮屏,要么Dim,具体根据当前已处状态确认,从而做到延长亮屏或Dim的时长
nextTimeout = mLastUserActivityTimeNoChangeLights + screenOffTimeout;
if (now < nextTimeout) {
if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_BRIGHT
|| mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_VR) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_BRIGHT;
} else if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
mUserActivitySummary = USER_ACTIVITY_SCREEN_DIM;
}
}
}
// 如果进该if块,说明当前时间 > 最近一次用户活动时间 + 自动休眠时间了,肯定将开始自动灭屏,mUserActivitySummary设置为USER_ACTIVITY_SCREEN_DREAM
if (mUserActivitySummary == 0) {
if (sleepTimeout >= 0) {
......
} else {
mUserActivitySummary = USER_ACTIVITY_SCREEN_DREAM;
nextTimeout = -1;
}
}
......
// 发送定时消息
if (mUserActivitySummary != 0 && nextTimeout >= 0) {
scheduleUserInactivityTimeout(nextTimeout);
}
} else {
// 当唤醒状态为Asleep时,设置为0
mUserActivitySummary = 0;
}
}
}
此方法中逻辑比较多且碎,其中细节在代码中都进行了注释。总而言之,进入该方法后,会根据最近一次的用户活动时间和系统设置的自动休眠时间,一步步确定每个用户活动状态变化的时间点,然后通过scheduleUserInactivityTimeout()方法设置一个Msg,并在到达时间点后会再次更新。
最终,用户活动状态经过USER_ACTIVITY_SCREEN_BRIGHT → USER_ACTIVITY_SCREEN_DIM → USER_ACTIVITY_SCREEN_DREAM变化,对应亮屏 → Dim → 灭屏。如果系统状态进入Alseep,则mUserActivitySummary最终会变为0。
经过updateUserActivitySummaryLocked()方法后,得到了mUserActivitySummary。
2.4.updateWakefulnessLocked()
现在看下循环体中的updateWakefulnessLocked()方法,这个方法作为for循环的终止条件,下面看下其逻辑:
private boolean updateWakefulnessLocked(int dirty) {
boolean changed = false;
if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_BOOT_COMPLETED
| DIRTY_WAKEFULNESS | DIRTY_STAY_ON | DIRTY_PROXIMITY_POSITIVE
| DIRTY_DOCK_STATE | DIRTY_ATTENTIVE | DIRTY_SETTINGS
| DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) {
// 当系统唤醒状态为Awake,且到达"睡觉"时间
if (getWakefulnessLocked() == WAKEFULNESS_AWAKE && isItBedTimeYetLocked()) {
final long time = mClock.uptimeMillis();
// 判断是否超过细微模式超时时间阈值
if (isAttentiveTimeoutExpired(time)) {
changed = goToSleepNoUpdateLocked(time, PowerManager.GO_TO_SLEEP_REASON_TIMEOUT,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE, Process.SYSTEM_UID);
// 进入屏保,将系统状态设置为Dreaming
} else if (shouldNapAtBedTimeLocked()) {
changed = napNoUpdateLocked(time, Process.SYSTEM_UID);
} else {
// 否则灭屏
changed = goToSleepNoUpdateLocked(time,
PowerManager.GO_TO_SLEEP_REASON_TIMEOUT, 0, Process.SYSTEM_UID);
}
}
}
return changed;
}
首先根据isItBedTimeYetLocked()和mWakefulnessRaw来决定是否执行该方法;然后根据shouldNapAtBedTimeLocked()决定是要进入屏保还是直接灭屏。
不管进入哪个状态,都说明此时系统屏幕状态发生了改变,所以该方法会返回true,因此将不会跳出for循环,再次进行一次循环。因此,只有超时灭屏时for循环才会执行两次,其他情况下都会只执行一次for循环就退出。
isItBedTimeYetLocked()方法是判断是否要自动休眠的关键:
private boolean isItBedTimeYetLocked() {
if (!mBootCompleted) {
return false;
}
long now = mClock.uptimeMillis();
// 到达细微模式时
if (isAttentiveTimeoutExpired(now)) {
return !isBeingKeptFromInattentiveSleepLocked();
} else {
return !isBeingKeptAwakeLocked();
}
}
该方法直接返回isBeingKeptAwakeLocked()方法:
private boolean isBeingKeptAwakeLocked() {
return mStayOn // 是否开启了不锁定屏幕开关
|| mProximityPositive // 是否有PSensor靠近
|| (mWakeLockSummary & WAKE_LOCK_STAY_AWAKE) != 0 // 是否持有屏幕相关WakeLock锁
|| (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT
| USER_ACTIVITY_SCREEN_DIM)) != 0 // 用户活动状态是否为Bright或Dim
|| mScreenBrightnessBoostInProgress; // 是否在进行亮度增强
}
如果isBeingKeptAwakeLocked()方法有任意一个条件为true,那么就不能进入休眠或者屏保状态,因此只有全部为false时,才会进行自动灭屏。其中条件包括mWakeLockSummary和mUserActivitySummary,所以前面流程中对这两个变量进行统计,就是用在这里起关键作用。
isItBedTimeYetLocked()返回后,将进入屏保或者灭屏,屏保这个功能已经几乎不使用了,就不分析它了。接下来将调用goToSleepNoUpdateLocked()方法,于是开始走灭屏流程,之后的逻辑和非自动灭屏流程一致。
至此,自动灭屏流程分析完毕。从以上流程可以看到,mWakeLockSummary和mUserActivitySummary对自动灭屏来说相当重要,平时如果存在到达自动灭屏时间后不灭屏问题,可以确认下这两个值的状态是否正常。
自动灭屏时序图如下:
3.PSensor灭屏
日常使用场景中,还有一个常见灭屏,就是距离传感器灭屏。打电话、听语音时都会由Psensor灭屏,避免误触。
然而,虽然都是灭屏,但内部实现却是天差地别,实际上,PSensor的灭屏,只会改变Display模块中的屏幕亮度和Display状态,PM唤醒状态依然保持Awake(即mWakefulnessRaw = AWAKE,mUserActivitySummary = BRIGHT,....... ),这部分内容,在对DisplayManager模块分析时进行。