分类: Android平台
2015-09-15 21:42:50
Android 电源管理
Date |
Issue |
Description |
Author |
<02/12/2010> |
<0.5> |
|
wylhistory |
Revision History
目录
TOC \o "1-3" \h \z \u 1. Abstract. PAGEREF _Toc280188107 \h 3
2. Introduction.. PAGEREF _Toc280188108 \h 3
3. 整体架构... PAGEREF _Toc280188109 \h 3
4. 锁屏过程... PAGEREF _Toc280188110 \h 4
4.1 Power键的处理流程... PAGEREF _Toc280188111 \h 4
4.2 准备睡眠... PAGEREF _Toc280188112 \h 6
4.2.1 updateLightsLocked实现... PAGEREF _Toc280188113 \h 9
4.2.2 screenOffFinishedAnimatingLocked的逻辑... PAGEREF _Toc280188114 \h 14
4.2.3 sendNotificationLocked的逻辑... PAGEREF _Toc280188115 \h 17
4.3 锁屏界面的显示... PAGEREF _Toc280188116 \h 20
4.3.1 doKeyguard的逻辑... PAGEREF _Toc280188117 \h 22
4.3.2 UI的显示... PAGEREF _Toc280188118 \h 24
5. 唤醒过程... PAGEREF _Toc280188119 \h 28
5.1 Power键唤醒的处理流程... PAGEREF _Toc280188120 \h 28
5.2 WakeLock导致的唤醒流程... PAGEREF _Toc280188121 \h 32
5.2.1 updateLightsLocked唤醒时流程... PAGEREF _Toc280188122 \h 37
5.2.2 setScreenStateLocked唤醒逻辑... PAGEREF _Toc280188123 \h 43
5.2.3 sendNotificationLocked唤醒逻辑... PAGEREF _Toc280188124 \h 44
6. 状态切换... PAGEREF _Toc280188125 \h 46
6.1 解锁过程... PAGEREF _Toc280188126 \h 49
6.2 状态的切换... PAGEREF _Toc280188127 \h 53
7. arm11驱动的实现... PAGEREF _Toc280188128 \h 57
7.1 用户层使用... PAGEREF _Toc280188129 \h 58
7.2 早期睡眠... PAGEREF _Toc280188130 \h 59
7.3 深睡眠... PAGEREF _Toc280188131 \h 61
8. 未分析 PAGEREF _Toc280188132 \h 65
9. 总结 PAGEREF _Toc280188133 \h 65
10. reference.. PAGEREF _Toc280188134 \h 65
主要是分析一下android的电源管理架构;
本文主要讨论的是android的电源管理框架,涉及一点驱动的代码,是以高通的7227平台实现为例;以用户按锁屏键导致睡眠,以及再按锁屏键唤醒,拉动屏幕解锁为序,解剖整个过程;
Android:2.2
高通修订版本:6040;
如果只从睡眠,唤醒的角度来说,使用架构其实很简单,
应用可以是各个APK也可以是framework里面的代码,比如它的典型用法为:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK
| PowerManager.ACQUIRE_CAUSES_WAKEUP
| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
mWakeLock.acquire();
…………………….
mWakeLock.release();
当持有锁的时候,是不能睡眠的,而是否需要点亮屏幕,或者键盘灯等,是在newWakeLock的时候传入的第一个参数来决定的,比如FULL_WAKE_LOCK也就意味着CPU高速运行,屏亮,而键盘灯等是否要亮是由设置里面的是否启用自动调节背光选项来决定的,如果设置了,那么键盘灯等的亮暗是由外界光来决定的,否则,这时候,就应该是要亮灯的;
好,现在我们再来看看锁屏和解锁整个架构:
红色的表示默认的传递路径,最下面右边的四个方框,只有在用户设置了不同的解锁类型才能到达;
关于按键的处理逻辑,请参考附录【1】,这里只讨论对于power键的处理;
如图所示:
这个图只展示了电源键按下的处理逻辑;
当驱动有按键事件上报的时候,这个函数preprocessEvent会被触发执行,根据惯例,这个处理流程会被传递到mPolicy,也就是PhoneWindowManager,在那里会调用interceptKeyTq来处理;
首先判断是否是一个唤醒的按键,正常情况下一个按键是否具有唤醒功能是在键盘映射的配置文件里面注明的,比如在我们的板子上是这个文件:/system/usr/keylayout/7k_ffa_keypad.kl(注意power键等三个键是在7k_handset.kl文件里面),每个按键的最后设置里面如果写明了WAKE或者WAKE_DROPPED的,都具有唤醒功能;
再判断是否是keyguardMediator激活的,正常情况下,如果如果屏暗了,或者屏亮着并且界面上有那个锁屏界面,那么此变量为真(注意,如果我们第一次开机的时候没有做过setup,此变量的值,似乎会为假);
在interceptKeyTq里面的右边,会看到一个else的判断框,这里省略了相应的if语句,表示是按键的抬起(略去的if表示按键的按下);
后面的逻辑就是红色的框标明的情况,也就是result返回了ACTION_GO_TO_SLEEP;
(如果返回ACTION_PASS_TO_USER,那么这个键值将会被传递到应用本身;
返回ACTION_POKE_USER_ACTIVITY则会唤醒屏幕;
睡眠的时候返回ACTION_GO_TO_SLEEP,表示将要睡眠,这应该是睡眠的路径;
唤醒的时候很奇怪,down事件被抛弃,但是up事件缺被继续传递了,比如黑屏的时候按下menu键,这时候down事件经过preprocess处理后返回false,但是up事件却返回true)
在从interceptKeyTq函数返回以后,会判断这个返回值决定下一步的动作,对于我们分析的情况,会调用PowerManager的goToSleep;
总结一下这个图,PhoneWindowManager会根据不同的按键,以及keyquaredActive等状态返回不同的值,有不同的处理,这里需要知道的是,按键具有唤醒功能需要在配置文件里面标明,比如音量加减键如果要在黑屏的时候起到调节音量的作用,就必须要在后面添加WAKE的flag;还需要知道的是,睡眠和唤醒的处理逻辑都是这里开始的;
好,下面开始进入到电源管理的处理流程;
这个图,主要是控制的传递,先从PowerManager.java传递到PowerManagerService.java;
函数就是经过了几个goToSleep的变种,最后调用到setPowerState,这个是一个很关键的函数,需要详细讨论;
我很不喜欢贴代码,但经验表明,遇到很关键的函数,最好还是这样;
函数比较长,我一点一点的发:
原型为:private void setPowerState(int newState, boolean noChangeLights, int reason);
可以看到三个参数,对照上面的调用,可以看到
newState=SCREEN_OFF;
noChangeLights=false;
reason=OFF_BECAUSE_OF_USER;
从命名来看,newState表示将要进入的状态,而noChangeLights表示这次状态的变化是否涉及灯的状态改变,为false表明需要涉及,也就是有可能会关闭或者打开键盘灯等;reason表示是什么原因导致这个新的状态的设置;
整个函数都是用synchronized (mLocks)来锁定的,防止竞争条件;
synchronized (mLocks) {
int err;
if (noChangeLights) {
newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK);
}
/*如果noChangeLights为true,那么新的状态里面需要去除灯相关的状态,当然需要保留当前已经设置的灯状态,这个mPowerState保存的是当前的电源管理状态*/
if (mProximitySensorActive) {
// don't turn on the screen when the proximity sensor lock is held
newState = (newState & ~SCREEN_BRIGHT);
/*如果接近传感器在起作用,那么新的状态需要去除屏幕亮的flag,因为这时候屏幕的亮暗,是有接近传感器来控制的*/
}
if (batteryIsLow()) {
newState |= BATTERY_LOW_BIT;
} else {
newState &= ~BATTERY_LOW_BIT;
}
/*加上或者减去电池电量低的flag*/
if (newState == mPowerState) {
/*如果新的状态和旧的状态没有变化,就直接返回了*/
return;
}
if (!mBootCompleted && !mUseSoftwareAutoBrightness) {
/*如果还没有启动完成,并且用户也没有设置自动调节背光,那么就全亮*/
newState |= ALL_BRIGHT;
}
…….
}
接着往下看:
if (!mBootCompleted && !mUseSoftwareAutoBrightness) {
newState |= ALL_BRIGHT;
}
boolean oldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0;
/*当前的screen是否是亮的*/
boolean newScreenOn = (newState & SCREEN_ON_BIT) != 0;
/*新的状态是否是亮的*/
if (mPowerState != newState) {
/*如果新的状态和当前的状态不一致进入,先更新灯的状态*/
updateLightsLocked(newState, 0);/*这个函数待会再看*/
mPowerState = (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK);
/*保存新的灯的状态flag*/
}
继续:
if (oldScreenOn != newScreenOn) {
if (newScreenOn) {
。。。。。。/*这里是点亮屏幕的路径,后面分析唤醒的时候再看*/
} else {
// cancel light sensor task
mHandler.removeCallbacks(mAutoBrightnessTask);/*防止再点亮*/
mScreenOffTime = SystemClock.elapsedRealtime();
long identity = Binder.clearCallingIdentity();
try {
mBatteryStats.noteScreenOff();/*通知屏幕灭的事实*/
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling noteScreenOff on BatteryStatsService", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
mPowerState &= ~SCREEN_ON_BIT;/*去掉屏幕亮的flag*/
mScreenOffReason = reason;/*保留reason*/
if (!mScreenBrightness.animating) {/*这个标志标明屏暗的动画是否已经完成,这个动画,就是我们所看到的屏幕逐渐变暗的过程,或者这里调用,或者在动画完成的时候调用下面的函数*/
err = screenOffFinishedAnimatingLocked(reason);/*这个函数后面再看*/
} else {
err = 0;
mLastTouchDown = 0;
}
}
}
至此这个函数setPowerState就完成了,我们回头看看,这个函数的工作流程,主要分成四部分:
区分灯的状态和screen的状态;
通过updateLightsLocked函数更新灯的状态;
通过screenOffFinishedAnimatingLocked传递到驱动,等待睡眠;
锁屏界面相关的处理,它或者在唤醒的时候直接通过sendNotificationLocked来实现,或者是在睡眠的时候由函数screenOffFinishedAnimatingLocked来触发(它里面会调用sendNotificationLocked);
下面开始介绍这三个函数;
原型为:private void updateLightsLocked(int newState, int forceState)
对照我们传入的参数为:
假设我们的场景中,没有接近传感器,那么这个
newState = SCREEN_OFF;
forceState = 0;
代码如下:
private void updateLightsLocked(int newState, int forceState) {
final int oldState = mPowerState;
/*先保留当前的电源状态,接着通过applyButtonState调节新的按键灯的状态,对于我们分析的情况,返回的肯定是state & ~BUTTON_BRIGHT_BIT*/
newState = applyButtonState(newState);
/*对于我们分析的场景,返回的肯定是return state & ~KEYBOARD_BRIGHT_BIT*/
newState = applyKeyboardState(newState);
/*判断新旧状态是否有差别*/
final int realDifference = (newState ^ oldState);
final int difference = realDifference | forceState;
if (difference == 0) {
/*如果没有差别就直接返回了*/
return;
}
int offMask = 0;
int dimMask = 0;
int onMask = 0;
int preferredBrightness = getPreferredBrightness();
/*取得亮度的偏好设置,以及BRIGHTNESS_DIM (= 20)的最大值*/
boolean startAnimation = false;
。。。。。。。。
}
后面开始对键盘灯,按键灯的处理,因为对于我们分析的场景,这两个很有可能是没有变化的,所以就不贴代码了,下面只分析screen亮暗的处理:
if ((difference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {
if (ANIMATE_SCREEN_LIGHTS) {/*这个变量为true,也就是默认开启的是暗屏动画*/
int nominalCurrentValue = -1;
// If there was an actual difference in the light state, then
// figure out the "ideal" current value based on the previous
// state. Otherwise, this is a change due to the brightness
// override, so we want to animate from whatever the current
// value is.
if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {
switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) {
case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT:
nominalCurrentValue = preferredBrightness;
break;
case SCREEN_ON_BIT:
nominalCurrentValue = Power.BRIGHTNESS_DIM;
break;
case 0:
nominalCurrentValue = Power.BRIGHTNESS_OFF;
break;
case SCREEN_BRIGHT_BIT:
default:
// not possible
nominalCurrentValue = (int)mScreenBrightness.curValue;
break;
}
}
/*这个switch主要是取得名义上的亮度的当前值,也就是如果当前的值是全亮的,那么这个名义的值就是刚才我们取得的那个亮度值,否则如果当前的状态仅仅是屏幕亮,那么这个值就是DIM,否则就是BRIGHTNESS_OFF了*/
int brightness = preferredBrightness;/*保存这个值*/
int steps = ANIM_STEPS; /*60/60 == 1*/
if ((newState & SCREEN_BRIGHT_BIT) == 0) {/*如果新的状态不再是全亮进入*/
// dim or turn off backlight, depending on if the screen is on
// the scale is because the brightness ramp isn't linear and this biases
// it so the later parts take longer.
final float scale =1.5f;
float ratio = (((float)Power.BRIGHTNESS_DIM)/preferredBrightness);
if (ratio >1.0f) ratio =1.0f;
if ((newState & SCREEN_ON_BIT) == 0) {
if ((oldState & SCREEN_BRIGHT_BIT) != 0) {
// was bright
steps = ANIM_STEPS;
} else {
// was dim
steps = (int)(ANIM_STEPS*ratio*scale);
}
brightness = Power.BRIGHTNESS_OFF;
} else {
if ((oldState & SCREEN_ON_BIT) != 0) {
// was bright
steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale);
} else {
// was dim
steps = (int)(ANIM_STEPS*ratio);
}
if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) {
// If the "stay on while plugged in" option is
// turned on, then the screen will often not
// automatically turn off while plugged in. To
// still have a sense of when it is inactive, we
// will then count going dim as turning off.
mScreenOffTime = SystemClock.elapsedRealtime();
}
brightness = Power.BRIGHTNESS_DIM;
}
}
/*以上的操作本质就是决定在屏暗的过程中应该以什么样的速率来变*/
long identity = Binder.clearCallingIdentity();/*清除Binder调用者痕迹*/
try {
mBatteryStats.noteScreenBrightness(brightness);/*通知新的屏幕亮度事件*/
} catch (RemoteException e) {
// Nothing interesting to do.
} finally {
Binder.restoreCallingIdentity(identity);/*恢复调用者身份*/
}
if (mScreenBrightness.setTargetLocked(brightness,
steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue)) {
/*开启暗屏的动画,也就是逐步暗屏的过程*/
startAnimation = true;
}
} else {
if ((newState & SCREEN_BRIGHT_BIT) == 0) {/*不走这条路线,因为这个世界需要动画*/
// dim or turn off backlight, depending on if the screen is on
if ((newState & SCREEN_ON_BIT) == 0) {
offMask |= SCREEN_BRIGHT_BIT;
} else {
dimMask |= SCREEN_BRIGHT_BIT;
}
} else {
onMask |= SCREEN_BRIGHT_BIT;
}
}
}
if (startAnimation) {
mHandler.removeCallbacks(mLightAnimator); /*移除上次设置的回调函数*/
mHandler.post(mLightAnimator);/*这里触发暗屏的动画,这是另外一个线程执行的逻辑,在那里开始了逐渐暗屏的过程,而此线程将会继续睡眠的过程*/
}
本函数最后一段:
if (offMask != 0) {
if (mSpew) Slog.i(TAG, "Setting brightess off: " + offMask);
setLightBrightness(offMask, Power.BRIGHTNESS_OFF);/*OFF*/
}
if (dimMask != 0) {
int brightness = Power.BRIGHTNESS_DIM;
if ((newState & BATTERY_LOW_BIT) != 0 &&
brightness > Power.BRIGHTNESS_LOW_BATTERY) {
brightness = Power.BRIGHTNESS_LOW_BATTERY;
}
if (mSpew) Slog.i(TAG, "Setting brightess dim " + brightness + ": " + dimMask);
setLightBrightness(dimMask, brightness); /*DIM*/
}
if (onMask != 0) {
int brightness = getPreferredBrightness();
if ((newState & BATTERY_LOW_BIT) != 0 &&
brightness > Power.BRIGHTNESS_LOW_BATTERY) {
brightness = Power.BRIGHTNESS_LOW_BATTERY;
}
if (mSpew) Slog.i(TAG, "Setting brightess on " + brightness + ": " + onMask);
setLightBrightness(onMask, brightness); /*设置背光亮度*/
}
接着看这里面的这个函数setLightBrightness
private void setLightBrightness(int mask, int value) {
int brightnessMode = (mAutoBrightessEnabled
? LightsService.BRIGHTNESS_MODE_SENSOR
: LightsService.BRIGHTNESS_MODE_USER);
if ((mask & SCREEN_BRIGHT_BIT) != 0) {
mLcdLight.setBrightness(value, brightnessMode);
}
if ((mask & BUTTON_BRIGHT_BIT) != 0) {
mButtonLight.setBrightness(value);
}
if ((mask & KEYBOARD_BRIGHT_BIT) != 0) {
mKeyboardLight.setBrightness(value);
}
}
后面的逻辑就比较简单了,就是不同的灯的设置函数设置对应的亮度,我就不仔细分析了,贴两个图:
这里可以看出一个细节,这里的颜色是按照RGB的方式传递的,只不过RGB都是相同的;
经过层层传递,从JNI层再到HAL层,也就是到了lights.c里面;
可以看到,最后的实现,都是类似的通过从sys文件系统export出来的接口直接写值就可以了;
OK到此,这个updateLightsLocked函数的执行逻辑,基本就分析完了;
总结一下:
基本上就是控制键盘灯,按键灯,以及背光的亮度,最后通过/sys/文件系统设置值,以达到设置背光,键盘灯等的亮度的作用;
它的代码很少,如下:
private int screenOffFinishedAnimatingLocked(int reason) {
// I don't think we need to check the current state here because all of these
// Power.setScreenState and sendNotificationLocked can both handle being
// called multiple times in the same state. -joeo
EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 0, reason, mTotalTouchDownTime, mTouchCycles);
mLastTouchDown = 0;
int err = setScreenStateLocked(false);
if (err == 0) {
mScreenOffReason = reason;
sendNotificationLocked(false, reason);
}
return err;
}
这里最重要的就是这两个函数:
setScreenStateLocked,以及sendNotificationLocked;
sendNotificationLocked后面会讨论,这里只讨论前面这个函数;
因为比较简单,所以就贴个图吧;
最后的实现就是在/sys/power/state里面echo一个mem(对于睡眠)或者on(对于唤醒);
这里需要注意的是,写mem不代表它就会睡眠,驱动还会处理策略的东西,决定当前是睡眠还是不睡眠;
这个函数比较重要,也是需要详细分析的,简单说就是和锁屏界面相关的,通知屏暗/亮的消息;
原型为:private void sendNotificationLocked(boolean on, int why);
第一个参数表示是屏暗,还是屏亮;
第二个参数表示屏暗的原因,目前有这些可能:
public final int OFF_BECAUSE_OF_ADMIN = 1;
/** Screen turned off because of power button */
public final int OFF_BECAUSE_OF_USER = 2;
/** Screen turned off because of timeout */
public final int OFF_BECAUSE_OF_TIMEOUT = 3;
/** Screen turned off because of proximity sensor */
public final int OFF_BECAUSE_OF_PROX_SENSOR = 4;
也就是说在设备管理,用户按power键,或者屏亮的时间到,或者是接近传感器的原因都会导致屏暗(注意这里有优先级别的,应该是越小越高);
讨论这个函数之前,必须要了解它的调用情况,否则无法理解里面的逻辑;
当屏暗的时候会调用一次这个函数,
sendNotificationLocked(false, reason);
当屏亮的时候会调用两次,
sendNotificationLocked(false, WindowManagerPolicy.OFF_BECAUSE_OF_USER);
……
sendNotificationLocked(true, -1);
从这个调用次序来看,这个函数第一次调用传递的参数一定是false,第二次调用,是可能为false也可能为true,第三次调用也是可能为false,也可能为true,好,有了这个理解,我们再看看这个函数;
private void sendNotificationLocked(boolean on, int why)
{
if (!on) {
mStillNeedSleepNotification = false;
}
// Add to the queue.
int index = 0;
while (mBroadcastQueue[index] != -1) {
index++;
}
mBroadcastQueue[index] = on ? 1 : 0;
mBroadcastWhy[index] = why;
……
}
这里比较关键的就是这两个数组mBroadcastQueue,以及mBroadcastWhy;前一个保存的是on或者off,后一个保存的是原因,注意它们定义如下:
private final int[] mBroadcastQueue = new int[] { -1, -1, -1 };
private final int[] mBroadcastWhy = new int[3];
也就是说当mBroadcastQueue里面的元素为-1的时候,表示里面的值是无效的;
继续往下看:
if (index == 2) {/*这个index为2,标明这个数组里面已经有两个数据了,根据下面index为1的时候的情况,就可以知道前面的两个一定是一个为false,一个为true,所以可以去掉,只保留第三个数据*/
// While we're collapsing them, if it's going off, and the new reason
// is more significant than the first, then use the new one.
if (!on && mBroadcastWhy[0] > why) {
mBroadcastWhy[0] = why;/*从这里可以看出,这里的reason保存的是值小的那个原因*/
}
mBroadcastQueue[0] = on ? 1 : 0; /*如果为on保存1,否则为0*/
mBroadcastQueue[1] = -1;
mBroadcastQueue[2] = -1;
mBroadcastWakeLock.release();/*释放前面已经获取的两次锁*/
mBroadcastWakeLock.release();
index = 0;
}
if (index == 1 && !on) {/*如果index为1,表明第一次肯定为false,如果第二次还是为false,则没有必要加入,直接过滤掉,并且第一次的进入肯定会导致keyguard的出现,因此,直接把第一次的值也去掉,并且需要释放掉锁*/
mBroadcastQueue[0] = -1;
mBroadcastQueue[1] = -1;
index = -1;
// The wake lock was being held, but we're not actually going to do any
// broadcasts, so release the wake lock.
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 1, mBroadcastWakeLock.mCount);
mBroadcastWakeLock.release();
}
// Now send the message.
if (index >= 0) {/*如果有新的状态加入,则发送消息,等待被处理*/
// Acquire the broadcast wake lock before changing the power
// state. It will be release after the broadcast is sent.
// We always increment the ref count for each notification in the queue
// and always decrement when that notification is handled.
mBroadcastWakeLock.acquire();/*进来一次,就获得一次*/
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_SEND, mBroadcastWakeLock.mCount);
mHandler.post(mNotificationTask);
}
这个函数到此就已经结束了,一定需要记住的是,它的调用都是有锁mLocks的;而且,我想正常情况下进来这个index都应该是0;
好,我们继续看这个消息导致的处理;
private Runnable mNotificationTask = new Runnable()
{
public void run()
{
while (true) {
int value;
int why;
WindowManagerPolicy policy;
synchronized (mLocks) { /*获得锁,防止里面操作的这些mBroadcastQueue发送竞争*/
value = mBroadcastQueue[0];
why = mBroadcastWhy[0];
for (int i=0; i<2; i++) {/*取得第一个数据后,把后面的数据往前移*/
mBroadcastQueue[i] = mBroadcastQueue[i+1];
mBroadcastWhy[i] = mBroadcastWhy[i+1];
}
policy = getPolicyLocked();/*这里可以理解为PhoneWindowManager*/
}
if (value == 1) {/*这里是需要点亮的过程*/
mScreenOnStart = SystemClock.uptimeMillis();
policy.screenTurnedOn();/*调用PhoneWindowManager开启操作流程*/
try {
ActivityManagerNative.getDefault().wakingUp();/*activityManagerService唤醒,开启事件处理*/
} catch (RemoteException e) {
// ignore it
}
if (mSpew) {
Slog.d(TAG, "mBroadcastWakeLock=" + mBroadcastWakeLock);
}
if (mContext != null && ActivityManagerNative.isSystemReady()) {
mContext.sendOrderedBroadcast(mScreenOnIntent, null,
mScreenOnBroadcastDone, mHandler, 0, null, null);/*发送带有先后次序的广播*/
} else {
synchronized (mLocks) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2,
mBroadcastWakeLock.mCount);
mBroadcastWakeLock.release();
}
}
}
else if (value == 0) {/*如果需要暗屏*/
mScreenOffStart = SystemClock.uptimeMillis();
policy.screenTurnedOff(why);/*调用PhoneWindowManager来关闭屏幕*/
try {
ActivityManagerNative.getDefault().goingToSleep();/*activityManagerService进入睡眠,基本上就是停止事件分发,暂停当前的activity*/
} catch (RemoteException e) {
// ignore it.
}
if (mContext != null && ActivityManagerNative.isSystemReady()) {
mContext.sendOrderedBroadcast(mScreenOffIntent, null,
mScreenOffBroadcastDone, mHandler, 0, null, null);/*发送屏暗的广播*/
} else {
synchronized (mLocks) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 3,
mBroadcastWakeLock.mCount);
mBroadcastWakeLock.release();
}
}
}
else {
// If we're in this case, then this handler is running for a previous
// paired transaction. mBroadcastWakeLock will already have been released.
break;/*到这里通常意味着mBroadcastQueue里面的有效数据都处理完了,现在是-1的垃圾值了*/
}
}
}
}
这里剩下了两个很关键的函数policy.screenTurnedOn/ policy.screenTurnedOff(why),分别对应着亮和暗屏;
因为它很重要,所以我们需要单独的章节讨论;
来个图先:
基本上没什么好说的,我们只关注最左边的红色框,最下面这个红色框在屏亮的时候才会触发到,也就是说在屏点亮的时候启动一个timer,时候一到就进入锁屏状态,这在屏亮的过程我们再分析;这里走的是else的流程,也就是删除这个timer,因为前面会直接进入这个锁屏状态,好现在看看这个函数,mKeyguardMediator.onScreenTurnedOff;
代码如下:
public void onScreenTurnedOff(int why) {
synchronized (this) {
mScreenOn = false;
if (mExitSecureCallback != null) {/*这个变量没看到有地方设置*/
if (DEBUG) Log.d(TAG, "pending exit secure callback cancelled");
mExitSecureCallback.onKeyguardExitResult(false);
mExitSecureCallback = null;
if (!mExternallyEnabled) {/*当其它客户端明确的disable才为false,默认为true*/
hideLocked();
}
} else if (mShowing) {/*如果这个锁屏界面显示的时候就为true*/
notifyScreenOffLocked();
resetStateLocked();
} else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT) {/*自动的黑屏过程会走到这里来*/
// if the screen turned off because of timeout, set an alarm
// to enable it a little bit later (i.e, give the user a chance
// to turn the screen back on within a certain window without
// having to unlock the screen)
long when = SystemClock.elapsedRealtime() + KEYGUARD_DELAY_MS;
Intent intent = new Intent(DELAYED_KEYGUARD_ACTION);
intent.putExtra("seq", mDelayedShowingSequence);
PendingIntent sender = PendingIntent.getBroadcast(mContext,
0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
sender);
if (DEBUG) Log.d(TAG, "setting alarm to turn off keyguard, seq = "
+ mDelayedShowingSequence);
} else if (why == WindowManagerPolicy.OFF_BECAUSE_OF_PROX_SENSOR) {
// Do not enable the keyguard if the prox sensor forced the screen off.
} else {/*这里才应该是我们目前跟踪的路线*/
doKeyguard();
}
}
}
这个函数很重要,所以单独分析;
private void doKeyguard() {
synchronized (this) {
// if another app is disabling us, don't show
if (!mExternallyEnabled) {/*一般不走*/
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because externally disabled");
// note: we *should* set mNeedToReshowWhenReenabled=true here, but that makes
// for an occasional ugly flicker in this situation:
// 1) receive a call with the screen on (no keyguard) or make a call
// 2) screen times out
// 3) user hits key to turn screen back on
// instead, we reenable the keyguard when we know the screen is off and the call
// ends (see the broadcast receiver below)
// TODO: clean this up when we have better support at the window manager level
// for apps that wish to be on top of the keyguard
return;
}
// if the keyguard is already showing, don't bother
if (mKeyguardViewManager.isShowing()) {/*如果这个锁屏界面已经在显示就走这条路线*/
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because it is already showing");
return;
}
// if the setup wizard hasn't run yet, don't show
final boolean requireSim = !SystemProperties.getBoolean("keyguard.no_require_sim",
false);/* 判断是否需要SIM卡*/
final boolean provisioned = mUpdateMonitor.isDeviceProvisioned();
/*通常在第一次开机的时候会弹出一个设置的程序,那个程序执行完毕后,这个值将会为true,否则为false
*/
final IccCard.State state = mUpdateMonitor.getSimState();/*返回sim卡的状态*/
final boolean lockedOrMissing = state.isPinLocked()
|| ((state == IccCard.State.ABSENT) && requireSim);
/*public boolean isPinLocked() {
return ((this == PIN_REQUIRED) || (this == PUK_REQUIRED));
}
要求输入PIN码或者PUK码(也就是解锁PIN码的码只有十次输入机会)或者是系统要求SIM卡,但是SIM又不存在,那么这个lockedOrMissing将会为true
*/
if (!lockedOrMissing && !provisioned) {
if (DEBUG) Log.d(TAG, "doKeyguard: not showing because device isn't provisioned"
+ " and the sim is not locked or missing");
return;
}
/*如果没有通过provision的设置,并且没有要求输入PIN码或者PUK码,也不是(SIM卡不存在并且要求SIM的情况);
从简单一点的情况来看,如果你没有通过provision,如果你没有插入SIM卡,那么是会有解锁界面的,如果插入了,返而看不到解锁界面
*/
if (DEBUG) Log.d(TAG, "doKeyguard: showing the lock screen");
showLocked();/*触发显示锁屏界面,这里是发送消息来触发的,这里最后会触发handleShow的执行*/
}
}
好,现在看看这个handleShow的逻辑:
private void handleShow() {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleShow");
if (!mSystemReady) return;/*如果系统没有启动好,就返回*/
playSounds(true);/*通过Ringtone播放锁屏或者解锁的声音,当然前提是用户打开了这个选项*/
mKeyguardViewManager.show();/*这里开始显示了*/
mShowing = true;/*设置标志位*/
adjustUserActivityLocked();/*这里的逻辑,我觉得是为了调整电源管理内部本身的状态机,后面讲到状态切换的时候再看*/
adjustStatusBarLocked();/*如果可能就更新状态栏的显示,表示需要添加一个新的已经锁屏的图标,如果想知道如何在状态栏上显示新的图标就可以看这里的实现了*/
try {
ActivityManagerNative.getDefault().closeSystemDialogs("lock");/*关闭系统类的对话框*/
} catch (RemoteException e) {
}
mShowKeyguardWakeLock.release();/*释放锁*/
}
}
好,到这里的时候,我们再理一下,当前的状态;
从用户按下电源键开始后,首先是灯的状态发生了变化,包括键盘灯,背光灯等;然后开始显示屏逐渐暗下去的动画,接着也向驱动传递了mem的信息,也就是告诉驱动,准备进入睡眠了;最后通过sendNotificationLocked来传递给锁屏界面,可以显示出来了;
分析道这里的时候,基本都分析完了,只剩下最后一点:锁屏界面的显示了;进行讨论之前,需要说明的是,我们平常解锁的时候看到的画面,如果不经过特殊设置的话,其实就是我们的锁屏界面,但这个界面其实是在睡眠之前就画出来的,这样可以有效的防止在唤醒的时候的闪烁;如果我们进行了特殊的解锁类型的设置,那么这个锁屏界面,通常我们是看不到的;
好,现在可以看看这个锁屏界面的show了;
这里面的细节很多,我就不一一展开了,贴个图;
可以看到整个显示架构还是挺复杂的;
先有一个KeyguardViewHost,再有一个KeyGuardView,再把这个keyGuardView加入到这个KeyguardHost,这个KeyguardHost有被加入到ViewManager,最后设置KeyguardHost的visbility为true,这样就完成了显示了;
这里神奇的地方是显示的内容到底是什么?这个是由new LockPatternKeyguardView来实现的;
现在看看它的代码:
public LockPatternKeyguardView(
Context context,
KeyguardUpdateMonitor updateMonitor,
LockPatternUtils lockPatternUtils,
KeyguardWindowController controller) {
super(context);
mConfiguration = context.getResources().getConfiguration();
mEnableFallback = false;
mRequiresSim =
TextUtils.isEmpty(SystemProperties.get("keyguard.no_require_sim"));
mUpdateMonitor = updateMonitor;
mLockPatternUtils = lockPatternUtils;
mWindowController = controller;
mMode = getInitialMode();/*默认是LockScreen模式*/
……………..
/**
* We'll get key events the current screen doesn't use. see
* {@link KeyguardViewBase#onKeyDown(int, android.view.KeyEvent)}
*/
setFocusableInTouchMode(true);/*是否可以接收focus*/
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);/*设置谁先获得focus*/
// create both the lock and unlock screen so they are quickly available
// when the screen turns on
mLockScreen = createLockScreen();/*创建锁屏界面*/
addView(mLockScreen);
final UnlockMode unlockMode = getUnlockMode();/*取得解锁的模式,这个是由用户再settings里面设置的*/
if (DEBUG) Log.d(TAG,
"LockPatternKeyguardView ctor: about to createUnlockScreenFor; mEnableFallback="
+ mEnableFallback);
mUnlockScreen = createUnlockScreenFor(unlockMode);/*创建解锁界面*/
mUnlockScreenMode = unlockMode;
maybeEnableFallback(context);
addView(mUnlockScreen);
updateScreen(mMode);/*更新界面,如果是LockScreen的模式,那么需要去掉解锁的界面*/
}
这里的重点是两个界面的创建:createLockScreen以及createUnlockScreenFor,下面看看框架图:
这就是LockScreen的创建了;
界面的分析,就留到有需要的时候再看,这里就略过了;
这个就是解锁界面的创建过程,这里分成了几种情况,分别创建不同的类来显示,涉及UI的内容,我这里就不分析了;
至此,整个锁屏触发的睡眠过程就结束了,当然,驱动部分,什么时候睡眠还没有讨论,这个留到后面分析;稍微总结一下就是,更新各种lights,然后通知驱动,最后通知锁屏界面;
当睡下去的时候,用户按power键或者其它可以唤醒的键,就会触发唤醒流程,我们现在假定用户是按power键触发了唤醒流程,对应的处理逻辑如下:
这个唤醒的流程,比较隐晦,并不是我们理所当然的通过userActivity来触发的,而是通过这个逻辑进来的;这里的处理时在PhoneWindowManager.java里面,如果是wake键,将会触发wakeWheneadyLocked
的执行,这里构造消息并发送等待被处理;
接着看这个消息的处理:
正常情况下都是最左边的分支来处理,这个图比较简单,不说了;
如果是MENU键走左边的分支,对我们分析的场景来说,走右边的分支,这里根据键盘是否打开传入不同的时间参数给pokeWakelock;
可以看到,通过acquire获得锁,这里会触发一系列的动作,我们后面再讨论,这里先看右边的分支,这里经过先前设定好的时间,或者为5s或者为10s后,就释放锁,于是系统又进入睡眠了,除非这时候用户进行解锁,或者在不停的和屏幕交互;
至此,整个过程就是这样,简单说如果按power键但不解锁,那么经过5s或者10s将会进入黑屏的状态;不过,下面需要仔细分析这个mWakeLock.acquire的处理流程,也就是说,到现在还没有看到解锁画面的显示,我们下面分析的就是这个流程;
这里是acquire的逻辑,可以看到,基本上就是传递到powermanagerservice.java里面去;
mHeld用来判断此锁释放已经释放,当这个锁释放掉的时候会触发releaseWakeLock的调用,这样可以有效防止用户的错误使用锁而导致系统无法睡眠;注意这里的mRefCounted是用来区分两种不同类型的锁;可以通过setReferenceCounted函数改变mRefCounted的值,当它为假的时候,也就是说不需要考虑mCount的值,只要调用acquire就开始调用mService的acquireWakeLock,只要release就出发service的调用;如果为真,就需要匹配mCount的值,当它为0的时候才真正出发sercice的release操作;
继续看,故事才刚刚开始;
这个图的目的就是创建新锁,或者取得先前已经创建好的锁,根据传入的参数,决定屏是全亮,还是屏亮,还是屏及button灯亮;
AcquireWakeLock最右边的分支还没有结束,因为图比较大,所以分成两部分来展示,现在看后半部分:
图
图撒地方
图5.2.0
这个图看起来比较复杂,但实际上,对于我们这个场景重要的就是setPowerState这个函数,并不会走到右边的分支,那里只有在PARTICAL_WAKE_LOCK或者是接近传感器的时候才会走的路线;好,现在我们看看setPowerState的逻辑,这个函数前面已经分析过了,只是没有分析唤醒的流程;
private void setPowerState(int state)
{
setPowerState(state, false, WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT);
}
传递到另一个同名的函数,这个函数我去掉了和唤醒流程无关的代码,如下:
private void setPowerState(int newState, boolean noChangeLights, int reason)
{
synchronized (mLocks) {
int err;
if (noChangeLights) {/*这里为false,表示需要改变灯状态*/
newState = (newState & ~LIGHTS_MASK) | (mPowerState & LIGHTS_MASK);
}
if (mProximitySensorActive) {/*如果接近传感器在起作用才进入*/
// don't turn on the screen when the proximity sensor lock is held
newState = (newState & ~SCREEN_BRIGHT);
}
if (batteryIsLow()) {
newState |= BATTERY_LOW_BIT;
} else {
newState &= ~BATTERY_LOW_BIT;
}
if (newState == mPowerState) {/
return;
}
if (!mBootCompleted && !mUseSoftwareAutoBrightness) {
newState |= ALL_BRIGHT;
}
boolean oldScreenOn = (mPowerState & SCREEN_ON_BIT) != 0;
boolean newScreenOn = (newState & SCREEN_ON_BIT) != 0;
if (mPowerState != newState) {
updateLightsLocked(newState, 0);/*这个是重点函数,后面讨论*/
mPowerState = (mPowerState & ~LIGHTS_MASK) | (newState & LIGHTS_MASK);
}
if (oldScreenOn != newScreenOn) {
if (newScreenOn) {/*如果是点亮,就需要进入,这正是我们的路径*/
// When the user presses the power button, we need to always send out the
// notification that it's going to sleep so the keyguard goes on. But
// we can't do that until the screen fades out, so we don't show the keyguard
// too early.
if (mStillNeedSleepNotification) {/*此时为假,当即将进入睡眠的时候这里为true*/
sendNotificationLocked(false, WindowManagerPolicy.OFF_BECAUSE_OF_USER);
}
// Turn on the screen UNLESS there was a prior
// preventScreenOn(true) request. (Note that the lifetime
// of a single preventScreenOn() request is limited to 5
// seconds to prevent a buggy app from disabling the
// screen forever; see forceReenableScreen().)
boolean reallyTurnScreenOn = true;
if (mSpew) {
Slog.d(TAG, "- turning screen on... mPreventScreenOn = "
+ mPreventScreenOn);
}
if (mPreventScreenOn) {/*这里通常都为假,只有用户明确的调用特定的API才会设置为true*/
if (mSpew) {
Slog.d(TAG, "- PREVENTING screen from really turning on!");
}
reallyTurnScreenOn = false;
}
if (reallyTurnScreenOn) {/*通常为true,所以会进入*/
err = setScreenStateLocked(true);/*重点函数,需要详细讨论*/
long identity = Binder.clearCallingIdentity();
try {
mBatteryStats.noteScreenBrightness(getPreferredBrightness());
mBatteryStats.noteScreenOn();
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException calling noteScreenOn on BatteryStatsService", e);
} finally {
Binder.restoreCallingIdentity(identity);
}
} else {/*不进入*/
setScreenStateLocked(false);
// But continue as if we really did turn the screen on...
err = 0;
}
mLastTouchDown = 0;
mTotalTouchDownTime = 0;
mTouchCycles = 0;
EventLog.writeEvent(EventLogTags.POWER_SCREEN_STATE, 1, reason,
mTotalTouchDownTime, mTouchCycles);
if (err == 0) {
mPowerState |= SCREEN_ON_BIT;
sendNotificationLocked(true, -1);/*重点函数,需要详细讨论,通知解锁画面show出来*/
}
} else {
。。。。。。/*这里是暗屏的过程*/
}
}
}
}
函数里面有注释,和暗屏的时候一样,这里有几个重点的函数需要说明:
updateLightsLocked(newState, 0);这个函数主要是更新灯的状态,比如背光灯,这样我们才能看得见我们想要看到的解锁画面;
setScreenStateLocked(true);/*传递on信息到/sys/power/state*/
sendNotificationLocked(true, -1);/*通知显示解锁画面*/
下面一个个看;
假设传入的这个newState为SCREEN_BRIGHT,因为我们的平台mUseSoftwareAutoBrightness为true;
forceState为0,代码如下;
private void updateLightsLocked(int newState, int forceState) {
final int oldState = mPowerState;
newState = applyButtonState(newState);/*依据环境光决定是否要更新button灯,这两个值的设置请看framework/base/core/res/res/values/config.xml */
newState = applyKeyboardState(newState);/*依据环境光决定是否要更新键盘灯*/
final int realDifference = (newState ^ oldState);/*判断新旧状态是否有差别*/
final int difference = realDifference | forceState;/*forceState为0,也就是如果force则不管有没有变化都要进行*/
if (difference == 0) {/*没有改变就返回*/
return;
}
int offMask = 0;
int dimMask = 0;
int onMask = 0;
int preferredBrightness = getPreferredBrightness();/*取得背光亮度的偏好设置*/
boolean startAnimation = false;
if ((difference & KEYBOARD_BRIGHT_BIT) != 0) {/*如果需要改变键盘灯*/
if (ANIMATE_KEYBOARD_LIGHTS) {/*这个默认为false*/
if ((newState & KEYBOARD_BRIGHT_BIT) == 0) {
mKeyboardBrightness.setTargetLocked(Power.BRIGHTNESS_OFF,
ANIM_STEPS, INITIAL_KEYBOARD_BRIGHTNESS,
Power.BRIGHTNESS_ON);
} else {
mKeyboardBrightness.setTargetLocked(Power.BRIGHTNESS_ON,
ANIM_STEPS, INITIAL_KEYBOARD_BRIGHTNESS,
Power.BRIGHTNESS_OFF);
}
startAnimation = true;
} else {
if ((newState & KEYBOARD_BRIGHT_BIT) == 0) {
/*如果为0表示需要关闭键盘灯*/
offMask |= KEYBOARD_BRIGHT_BIT;
} else {
/*如果不为0表示需要打开键盘灯*/
onMask |= KEYBOARD_BRIGHT_BIT;
}
}
}
if ((difference & BUTTON_BRIGHT_BIT) != 0) {/*如果按键灯状态有改变*/
if (ANIMATE_BUTTON_LIGHTS) {/*默认为false,不进入*/
if ((newState & BUTTON_BRIGHT_BIT) == 0) {
mButtonBrightness.setTargetLocked(Power.BRIGHTNESS_OFF,
ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS,
Power.BRIGHTNESS_ON);
} else {
mButtonBrightness.setTargetLocked(Power.BRIGHTNESS_ON,
ANIM_STEPS, INITIAL_BUTTON_BRIGHTNESS,
Power.BRIGHTNESS_OFF);
}
startAnimation = true;
} else {
if ((newState & BUTTON_BRIGHT_BIT) == 0) {
/*如果按键灯状态变为0,则需要关闭按键灯*/
offMask |= BUTTON_BRIGHT_BIT;
} else {
/*如果按键灯边状态变为1,需要打开按键灯*/
onMask |= BUTTON_BRIGHT_BIT;
}
}
}
if ((difference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {/*背光灯状态有变化则进入,我们分析的场景肯定会进入的*/
if (ANIMATE_SCREEN_LIGHTS) {/*默认为true,所以要进入的*/
int nominalCurrentValue = -1;
// If there was an actual difference in the light state, then
// figure out the "ideal" current value based on the previous
// state. Otherwise, this is a change due to the brightness
// override, so we want to animate from whatever the current
// value is.
if ((realDifference & (SCREEN_ON_BIT | SCREEN_BRIGHT_BIT)) != 0) {
switch (oldState & (SCREEN_BRIGHT_BIT|SCREEN_ON_BIT)) {/*如果旧的状态屏是亮的,我们分析的场景不会进入*/
case SCREEN_BRIGHT_BIT | SCREEN_ON_BIT:
nominalCurrentValue = preferredBrightness;
break;
case SCREEN_ON_BIT:
nominalCurrentValue = Power.BRIGHTNESS_DIM;
break;
case 0:
nominalCurrentValue = Power.BRIGHTNESS_OFF;
break;
case SCREEN_BRIGHT_BIT:
default:
// not possible
nominalCurrentValue = (int)mScreenBrightness.curValue;
break;
}
}
int brightness = preferredBrightness;/*对于我们分析的场景,就是用户设置的偏好值,或者就是由光感传感器决定的一个值*/
int steps = ANIM_STEPS;
if ((newState & SCREEN_BRIGHT_BIT) == 0) {/*这个场景不进入,这里表示新的状态需要关闭背光灯*/
// dim or turn off backlight, depending on if the screen is on
// the scale is because the brightness ramp isn't linear and this biases
// it so the later parts take longer.
final float scale =1.5f;
float ratio = (((float)Power.BRIGHTNESS_DIM)/preferredBrightness);
if (ratio >1.0f) ratio =1.0f;
if ((newState & SCREEN_ON_BIT) == 0) {
if ((oldState & SCREEN_BRIGHT_BIT) != 0) {
// was bright
steps = ANIM_STEPS;
} else {
// was dim
steps = (int)(ANIM_STEPS*ratio*scale);
}
brightness = Power.BRIGHTNESS_OFF;
} else {
if ((oldState & SCREEN_ON_BIT) != 0) {
// was bright
steps = (int)(ANIM_STEPS*(1.0f-ratio)*scale);
} else {
// was dim
steps = (int)(ANIM_STEPS*ratio);
}
if (mStayOnConditions != 0 && mBatteryService.isPowered(mStayOnConditions)) {
// If the "stay on while plugged in" option is
// turned on, then the screen will often not
// automatically turn off while plugged in. To
// still have a sense of when it is inactive, we
// will then count going dim as turning off.
mScreenOffTime = SystemClock.elapsedRealtime();
}
brightness = Power.BRIGHTNESS_DIM;
}
}
long identity = Binder.clearCallingIdentity();
try {
mBatteryStats.noteScreenBrightness(brightness);
} catch (RemoteException e) {
// Nothing interesting to do.
} finally {
Binder.restoreCallingIdentity(identity);
}
if (mScreenBrightness.setTargetLocked(brightness,
steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue)) {/*重点函数需要分析*/
startAnimation = true;
}
} else {
if ((newState & SCREEN_BRIGHT_BIT) == 0) {
// dim or turn off backlight, depending on if the screen is on
if ((newState & SCREEN_ON_BIT) == 0) {
offMask |= SCREEN_BRIGHT_BIT;
} else {
dimMask |= SCREEN_BRIGHT_BIT;
}
} else {
onMask |= SCREEN_BRIGHT_BIT;
}
}
}
if (startAnimation) {/*如果需要启动动画的显示 */
if (mSpew) {
Slog.i(TAG, "Scheduling light animator!");
}
mHandler.removeCallbacks(mLightAnimator);
mHandler.post(mLightAnimator); /*开始进行亮屏的动画*/
}
if (offMask != 0) {
if (mSpew) Slog.i(TAG, "Setting brightess off: " + offMask);
/*如果需要关闭灯*/
setLightBrightness(offMask, Power.BRIGHTNESS_OFF);
}
if (dimMask != 0) {
int brightness = Power.BRIGHTNESS_DIM;
if ((newState & BATTERY_LOW_BIT) != 0 &&
brightness > Power.BRIGHTNESS_LOW_BATTERY) {
brightness = Power.BRIGHTNESS_LOW_BATTERY;
}
if (mSpew) Slog.i(TAG, "Setting brightess dim " + brightness + ": " + dimMask);
setLightBrightness(dimMask, brightness);
}
if (onMask != 0) {/*如果需要点亮灯*/
int brightness = getPreferredBrightness();
if ((newState & BATTERY_LOW_BIT) != 0 &&
brightness > Power.BRIGHTNESS_LOW_BATTERY) {
brightness = Power.BRIGHTNESS_LOW_BATTERY;
}
if (mSpew) Slog.i(TAG, "Setting brightess on " + brightness + ": " + onMask);
setLightBrightness(onMask, brightness);
}
}
setLightBrightness函数的分析请看4.2.1;其它需要说明的都有注释了,简单说就是更新灯的状态,有需要的话,比如背光灯的显示是通过动画来完成的;这里还需要看看这个动画的实现:
先看调用
mScreenBrightness.setTargetLocked(brightness,
steps, INITIAL_SCREEN_BRIGHTNESS, nominalCurrentValue)
也就是说这里的目标亮度就是我们的偏好设置,或者是由光感决定的一个亮度值;
InitialValue就是255;
nominalCurrentValue就是-1;
boolean setTargetLocked(int target, int stepsToTarget, int initialValue,
int nominalCurrentValue) {
if (!initialized) {/*如果是第一次调用就进入*/
initialized = true;
curValue = (float)initialValue;
} else if (targetValue == target) {/*如果相等,也就是将要设置的值和当前的值一样就什么都不用做了*/
return false;
}
targetValue = target;/*设定目标*/
/*求得每次需要改变的值,也就是目标值,减去当前值,或者是传入的初始值,除以需要多少个step达到目标*/
delta = (targetValue -
(nominalCurrentValue >= 0 ? nominalCurrentValue : curValue))
/ stepsToTarget;
if (mSpew) {
String noticeMe = nominalCurrentValue == curValue ? "" : " ******************";
Slog.i(TAG, "Setting target " + mask + ": cur=" + curValue
+ " target=" + targetValue + " delta=" + delta
+ " nominalCurrentValue=" + nominalCurrentValue
+ noticeMe);
}
animating = true;
return true;
}
后面通过mHandler.post(mLightAnimator)函数触发一个类线程的东西来执行,注意并不是线程,只不过是在一个消息循环里面得到一次调用,调用完了以后又通过mHandler.postAtTime(mLightAnimator, now+(1000/60))函数安排下一次被调用的时间;
如下:
private class LightAnimator implements Runnable {
public void run() {
synchronized (mLocks) {
long now = SystemClock.uptimeMillis();
boolean more = mScreenBrightness.stepLocked();/*主要是它会执行,下面两个默认都不会有什么结果*/
if (mKeyboardBrightness.stepLocked()) {
more = true;
}
if (mButtonBrightness.stepLocked()) {
more = true;
}
if (more) {/*如果还需要执行就安排下一次调用*/
mHandler.postAtTime(mLightAnimator, now+(1000/60));
}
}
}
}
其中mScreenBrightness.stepLocked的执行逻辑如下:
boolean stepLocked() {
if (!animating) return false;/*这在设置目标的时候,也就是setTargetLocked会被设置*/
if (false && mSpew) {
Slog.i(TAG, "Step target " + mask + ": cur=" + curValue
+ " target=" + targetValue + " delta=" + delta);
}
curValue += delta;
int curIntValue = (int)curValue;
boolean more = true;
if (delta == 0) {/*如果delta为0,那么将会是个死循环,肯定是要防止的*/
curValue = curIntValue = targetValue;
more = false;
} else if (delta > 0) {/*正常的路径*/
if (curIntValue >= targetValue) {/*如果当前的值,已经到目标值了,那么就停止动画了*/
curValue = curIntValue = targetValue;
more = false;
}
} else {
if (curIntValue <= targetValue) {/*如果这个delta是减的,那么说明当前值比目标值要大,实现的原理是不断的减小当前值,那么当当前的值已经小于等于目标值的时候就该停止了*/
curValue = curIntValue = targetValue;
more = false;
}
}
//Slog.i(TAG, "Animating brightess " + curIntValue + ": " + mask);
setLightBrightness(mask, curIntValue);
animating = more;
if (!more) {
if (mask == SCREEN_BRIGHT_BIT && curIntValue == Power.BRIGHTNESS_OFF) {
/*这里的调用会通知到锁屏界面那边,这里是在屏灭的时候才会调用的*/
screenOffFinishedAnimatingLocked(mScreenOffReason);
}
}
return more;
}
这个函数前面已经讨论过了,而且比较简单;
参考4.2.2.1,唯一不同的就是传递到驱动的参数,这里是on > /sys/power/state
请参阅4.2.3;
差异的地方如下:
private Runnable mNotificationTask = new Runnable()
{
public void run()
{
while (true) {
int value;
int why;
WindowManagerPolicy policy;
synchronized (mLocks) {
value = mBroadcastQueue[0];
why = mBroadcastWhy[0];
for (int i=0; i<2; i++) {
mBroadcastQueue[i] = mBroadcastQueue[i+1];
mBroadcastWhy[i] = mBroadcastWhy[i+1];
}
policy = getPolicyLocked();
}
if (value == 1) {/*屏亮的时候走的路线,正是我们分析的场景*/
mScreenOnStart = SystemClock.uptimeMillis();
policy.screenTurnedOn();/*这里非常重要*/
try {
ActivityManagerNative.getDefault().wakingUp();/*开启事件处理流程*/
} catch (RemoteException e) {
// ignore it
}
if (mSpew) {
Slog.d(TAG, "mBroadcastWakeLock=" + mBroadcastWakeLock);
}
if (mContext != null && ActivityManagerNative.isSystemReady()) {
/*发送ACTION_SCREEN_ON的消息,如果其它应用想要知道这个事件就可以收听*/
mContext.sendOrderedBroadcast(mScreenOnIntent, null,
mScreenOnBroadcastDone, mHandler, 0, null, null);
} else {
synchronized (mLocks) {
EventLog.writeEvent(EventLogTags.POWER_SCREEN_BROADCAST_STOP, 2,
mBroadcastWakeLock.mCount);
mBroadcastWakeLock.release();
}
}
}
else if (value == 0) {/*这里不走*/
。。。。。。
}
else {
// If we're in this case, then this handler is running for a previous
// paired transaction. mBroadcastWakeLock will already have been released.
break;
}
}
}
}
policy.screenTurnedOn的调用是最关键的,需要详细讨论,其它的都是一样的;
贴个图线:
最左边是通知解锁画面的显示;
updateLockScreenTimeout是启动一个定时器用来显示锁屏界面,后面再讲,这里主要讲解锁画面的显示;
这里发送了一个消息NOTIFY_SCREEN_ON,看看是如何处理的;
最左边从KeyguardViewMediator传递控制到KeyguardViewManager,在传递到负责UI构建和现实的LockPatternKeyguardView,这里根据不同的模式显示不同的UI,如果是LockScreen(这是默认的情况),就显示LockScreen,否则就显示对应于不同的解锁模式的UI;至于这些UI的创建在锁屏那个阶段我们就讨论过了,这里就不罗嗦;
OK,解锁界面的显示这条路径就结束了;
总结一下,就是通过power键进入到KeyguardMediator,在哪里进入到powermanagerservice,在那里更新灯的状态,告知驱动,并回过来通知解锁画面的显示;
现在还有一个很关键的没有分析,那就是屏亮了以后是如何暗屏的,这里涉及了一个状态的变化再下一章讲解;
我们接着上面的分析,在screenTurnOn调用的时候,会调用到函数updateLockScreenTimeout,下面看看这个函数的实现;
private void updateLockScreenTimeout() {
synchronized (mScreenLockTimeout) {
boolean enable = (mAllowLockscreenWhenOn && mScreenOn && mKeyguardMediator.isSecure());
if (mLockScreenTimerActive != enable) {
if (enable) {/*这里是我们的路径*/
if (localLOGV) Log.v(TAG, "setting lockscreen timer");
mHandler.postDelayed(mScreenLockTimeout, mLockScreenTimeout);/*传递消息*/
} else {
if (localLOGV) Log.v(TAG, "clearing lockscreen timer");
mHandler.removeCallbacks(mScreenLockTimeout);
}
mLockScreenTimerActive = enable;
}
}
}
我们分析的这个场景最重要的就是这个mHandler.postDelayed函数触发的回调函数;
触发的逻辑如下:
也就是说控制从PhoneWindowManager传递到了KeyguardViewMediator,这里构造了一个KEYGUARD_TIMEOUT的消息,并传递,注意这个时间就是我们在设置里面设置的那个屏幕暗屏时间,通常是15s或者30s,注意这个时间是大于我们先前说过的,默认的按power键但不解锁的时间(5或者10s)的,
后面的逻辑,我们很熟悉,但需要记住的是,它不一定会执行,因为这里需要15或者更多的时间,很有可能因为别的原因导致了doKeyGuard的更早的执行;
调用doKeyguard,这个函数我们已经分析过,它会构造SHOW消息,并触发HandleShow的执行,代码如下:
private void handleShow() {
synchronized (KeyguardViewMediator.this) {
if (DEBUG) Log.d(TAG, "handleShow");
if (!mSystemReady) return;
playSounds(true);
mKeyguardViewManager.show();
mShowing = true;
adjustUserActivityLocked();
adjustStatusBarLocked();
try {
ActivityManagerNative.getDefault().closeSystemDialogs("lock");
} catch (RemoteException e) {
}
mShowKeyguardWakeLock.release();
}
}
其它的逻辑,我们都看到过了,这里着重分析这个函数adjustUserActivityLocked;
代码如下:
private void adjustUserActivityLocked() {
// disable user activity if we are shown and not hidden
if (DEBUG) Log.d(TAG, "adjustUserActivityLocked mShowing: " + mShowing + " mHidden: " + mHidden);
boolean enabled = !mShowing || mHidden;/*如果这时候已经被解锁,那么这里为true,否则这里为false,因为只要有Keyguard,那么电源管理的状态由它来负责,否则由powermanagerservice来管理*/
mRealPowerManager.enableUserActivity(enabled);/*这里会设置一个变量,以允许
if (!enabled && mScreenOn) {
// reinstate our short screen timeout policy
pokeWakelock();
}
}
坦白说,这个路径,如果我们是按照按power键进来,再等到屏暗,那么是不会走到这里来的;
或者说,如果我们解锁后,那么执行这里的目的,则是告诉powermanagerservice,以后的电源管理状态的管理,由我来负责,通常意味着即将黑屏,或者已经黑屏,从应用程序的角度来说,也就是调用userActivity将不会有什么作用;
但从这个函数本身来说,则是会在解锁的时候调用的,那时候的目的,则是告诉powermanagerservice,从此后,电源管理的状态必须有你来负责;
下面我们看看,这是这样一个过程;
先来个图:
当用户拉着滚动条从左往右拉的时候,触发这个逻辑的执行;
接着看:
层层调用,根据我们分析的默认场景,这里是通用的解锁画面,本质上是锁屏画面,它将会调用到keyguardDone函数里面去;这里表示解锁完毕了;
继续:
这个图重要的就是最左边的消息的发送,最右边的分支,没怎么看到调用,先不看了;
这个函数会触发handleKeyguardDone函数的执行:
为了节省一点空间,图就这样了;首先调用handleHide,这个函数需要分析;接着是通过userActivity传递到了powermanagerservice,这里开启了状态的切换;先看handleHide;
这里先播放一个解锁的音乐,当然,如果在电话状态也就算了;
接着把锁屏界面给隐藏掉了,再设置标志位,表示隐藏;
再调用adjustUserActivityLocked,最后调用adjustStatusBarLocked更新状态条的UI;
这里重点需要关注的是adjustUserActivityLocked,注意只有这个函数执行后,后面的userActivity的执行才有效果;
adjustUserActivityLocked函数已经分析过了,这里将会传递到powermanagerservice里面去,我们跟过去瞧瞧;
public void enableUserActivity(boolean enabled) {
synchronized (mLocks) {
mUserActivityAllowed = enabled;
if (!enabled) {
// cancel timeout and clear mUserState so the keyguard can set a short timeout
setTimeoutLocked(SystemClock.uptimeMillis(), 0);
}
}
}
这个函数的作用就是设置了mUserActivityAllowed为true;其它的基本上就什么都没做;
再回头看看mPm.userActivity函数的调用,这个需要新的一节讨论;
public void userActivity(long when, boolean noChangeLights)
{
try {
mService.userActivity(when, noChangeLights);
} catch (RemoteException e) {
}
}
这个函数通过Binder进入到PowerManagerService;
逻辑如下:
这里面的逻辑,我们看起来挺熟悉的;
重点函数是setPowerState以及setTImeoutLocked;
前一个函数已经说过了,这里只讨论和状态切换有关的setTimeoutLocked;
讨论这个函数之前,需要先看看这个函数,以取得一些参数,假设用户设置的待机时间为15s;
private void setScreenOffTimeoutsLocked() {
if ((mPokey & POKE_LOCK_SHORT_TIMEOUT) != 0) {/*关于pokey,通常都不会使用到,我只看到phoneApp有相关的使用,不是我们分析的场景*/
mKeylightDelay = mShortKeylightDelay; // Configurable via secure settings
mDimDelay = -1;
mScreenOffDelay = 0;
} else if ((mPokey & POKE_LOCK_MEDIUM_TIMEOUT) != 0) {
mKeylightDelay = MEDIUM_KEYLIGHT_DELAY;
mDimDelay = -1;
mScreenOffDelay = 0;
} else {
int totalDelay = mScreenOffTimeoutSetting;/*这个值就是屏幕暗的那个设置,在设置应用里面通过声音和现实——>屏幕待机*/
if (totalDelay > mMaximumScreenOffTimeout) {
totalDelay = mMaximumScreenOffTimeout;/*取最小值*/
}
mKeylightDelay = LONG_KEYLIGHT_DELAY;/* 6000 ms*/
if (totalDelay < 0) {
mScreenOffDelay = Integer.MAX_VALUE;
} else if (mKeylightDelay < totalDelay) {/*这个一般都要小于这个的6 < 15*/
// subtract the time that the keylight delay. This will give us the
// remainder of the time that we need to sleep to get the accurate
// screen off timeout.
mScreenOffDelay = totalDelay - mKeylightDelay;/*取得屏幕暗的delay,15-6=9s*/
} else {
mScreenOffDelay = 0;
}
if (mDimScreen && totalDelay >= (LONG_KEYLIGHT_DELAY + LONG_DIM_TIME)) {
/*对于我们的场景,是满足这个条件的15 > 6+7,mDimScreen是为true的*/
mDimDelay = mScreenOffDelay - LONG_DIM_TIME;/*这个值就是9-7也就是2s*/
mScreenOffDelay = LONG_DIM_TIME;/*这个值就是7s*/
} else {
mDimDelay = -1;
}
}
if (mSpew) {
Slog.d(TAG, "setScreenOffTimeouts mKeylightDelay=" + mKeylightDelay
+ " mDimDelay=" + mDimDelay + " mScreenOffDelay=" + mScreenOffDelay
+ " mDimScreen=" + mDimScreen);
}
}
所以我们这个例子中:
mKeylightDelay:6s
mDimDelay:2s
mScreenOffDelay:7s
好有了这个认识,我们看看这个函数setTimeoutLocked:
传入的now为当前时间,这个nextState为SCREEN_BRIGHT;
private void setTimeoutLocked(long now, int nextState)
{
if (mBootCompleted) {/*系统就绪后才能进入*/
mHandler.removeCallbacks(mTimeoutTask);/*移除掉上次设置的状态机timer*/
mTimeoutTask.nextState = nextState;/*设置timer任务的下一个状态为这里的参数也就是SCREEN_BRIGHT*/
long when = now;
switch (nextState)
{
case SCREEN_BRIGHT:
when += mKeylightDelay;/*这个when将是当前+6s,键盘灯灭掉*/
break;
case SCREEN_DIM:
if (mDimDelay >= 0) {
when += mDimDelay;/*如果mDimDelay为2s,也就是当前+2s的时候进入Dim状态*/
break;
} else {
Slog.w(TAG, "mDimDelay=" + mDimDelay + " while trying to dim");
}
case SCREEN_OFF:
synchronized (mLocks) {
when += mScreenOffDelay;/*当前加7s的时候屏幕灭掉*/
}
break;
}
if (mSpew) {
Slog.d(TAG, "setTimeoutLocked now=" + now + " nextState=" + nextState
+ " when=" + when);
}
mHandler.postAtTime(mTimeoutTask, when);/*这里延迟特定的时间传递到timer任务,在那里进行状态的切换,并再一次调用这个函数,进行状态的更新*/
mNextTimeout = when; // for debugging
}
}
下面看看这个timer任务:
private class TimeoutTask implements Runnable
{
int nextState; // access should be synchronized on mLocks
public void run()/*这是在消息处理的时候会被触发的*/
{
synchronized (mLocks) {
if (mSpew) {
Slog.d(TAG, "user activity timeout timed out nextState=" + this.nextState);
}
if (nextState == -1) {/*出错性检查*/
return;
}
mUserState = this.nextState;/*这个状态在setTimeoutLocked的时候会被设置,是即将要进入的状态*/
setPowerState(this.nextState | mWakeLockState);/*设置当前的状态,这个函数我们已经见识过了,我们这里就可以理解为把各种灯控制在我们想要的亮度内,比如或者全亮,或者按键,键盘灯灭,或者到Dim状态,或者全灭*/
long now = SystemClock.uptimeMillis();
switch (this.nextState)/*根据当前的状态决定进入的下一个状态,这就是状态机的由来*/
{
case SCREEN_BRIGHT:/*如果当前是BRIGHT状态,就进入到DIM状态*/
if (mDimDelay >= 0) {
setTimeoutLocked(now, SCREEN_DIM);/*注意这里就传入了下一个状态的参数DIM*/
break;
}
case SCREEN_DIM:/*如果当前是DIM状态,就进入到OFF状态*/
setTimeoutLocked(now, SCREEN_OFF);/*传递即将要进入的状态过去*/
break;
}
}
}
}
OK,自此,这个状态的转化就分析完了;
基本上比我想象中简单,也就是一个timer,控制好时间,到时候就进入到下一个状态就好了;
前面说到过,应用层的睡眠和唤醒和驱动的接口在于:
Echo “on” >/sys/power/state (唤醒)
Echo “mem” >/sys/power/state(睡眠)
下面看看这两句话到底都做了些什么;
讨论之前,需要先想想,驱动需要实现的目的;
1,在适当的时候进入睡眠,让各设备进入到睡眠状态;
2,在不能睡眠的时候不能睡眠;
3,在醒来的时候一切恢复正常;
简单总结一下就是该睡则睡,该醒则醒,醒后如常;
文档已经挺长了,所以后面的讨论,我就本着点到为止的原则;
在进入驱动之前,先看看上层是如何通知驱动,什么时候该睡,什么时候该醒的;
看图5.2.0,当用户获取的锁是PARTIAL_WAKE_LOCK类型的时候,将会触发在/sys/power/wake_lock写入一个字符串;当释放这样的锁的时候也会在/sys/power/wake_unlock里面写入一个相同的字符串,对于我们分析的场景,就是PowerManagerService;
于是分别会触发驱动函数wake_lock_store以及wake_unlock_store,无图无真相:
左边这个图在acquireWakeLock的时候会触发,最后调用到驱动的wake_lock_store,根据是否设置了timeout参数,调用到wake_lock_internal,这个函数有三个参数,第一个是驱动里面对于这个wakelock的代码,第二个表示timeout值,第三个表示是否有timeout的设置;
右边的分支为释放这个锁的时候调用,当写入的数据和获得的时候放入的数据一样,将会触发wake_unlock的调用,那里判断是否当前还有活跃的锁,决定是否触发suspend_work来实施睡眠;
先了解个大概,后面再细讲,先回到本章开头的两句话;
Echo “on” >/sys/power/state (唤醒)
Echo “mem” >/sys/power/state(睡眠)
它们到底意味着什么呢?
有图有真相:
简单说就是判断写入到/sys/power/state的值是否是on或者mem,如果是就调用request_suspend_state;
如果传递进来的是mem(SUSPEND_REQUEST),将会触发early_suspend_work的执行;
如果传递进来的是on,将会触发late_resume_work的执行;
这两个函数是成对的出现的,其逻辑如下:
这里的pos->suspend以及pos->resume都是设备自己提供的,比如
dhd->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 20;
dhd->early_suspend.suspend = dhd_early_suspend;
dhd->early_suspend.resume = dhd_late_resume;
register_early_suspend(&dhd->early_suspend);
也就是说最后会触发这两个函数
dhd_early_suspend以及dhd_late_resume的调用;
也就是说,如果你的设备需要在黑屏的时候就执行一定的省电操作,就调用这个接口就可以了;
如果你看得足够仔细的话,你会发现在睡眠过程中,也就是echo mem >/sys/power/state触发的逻辑中,需要在early_suspend执行完设备的早期随眠后,才会触发wake_unlock;
而在唤醒的过程中直接在调度late_resume_work之前就已经执行了wake_lock了,注意这里的调用是内核直接的调用,但和用户层调用最后走的逻辑都是一样的,回头看看图7.1,我们需要再仔细分析函数wake_lock_internal,以及suspend函数;
简单的理一下,首先这个函数的功能,肯定是不让系统睡眠的;其次根据它的参数,分为有时间限制的和没有时间限制的;如果有,那么就把这个锁加入到活跃锁列表中去;如果没有就把时间限制设为无穷大,再加入到活跃列表中去;最右边那个分支,我觉得这里触发suspend_work的可能性非常低就不分析了,留到unlock的时候再看类似的逻辑;
下面看看一个详细版本的wake_unlock:
系统有一个默认的main_wake_lock,当我们睡眠的时候会调用wake_unlock(&main_wake_lock),而这个调用不一定会触发系统睡眠,比如用户态拥有partical lock就不会导致睡眠,也就是说这里的has_wake_lock_locked将会返回一个时间,而不是0,所以不会导致suspend_work的执行,但是,相反,如果我们睡眠的时候没有partial lock或者说在黑屏的时候用户的partial lock到期了,那么将会触发这个suspend_work的执行;
先看看这个has_wake_lock_locked是如何实现的:
基本上就是检查活跃锁列表,如果这个锁的类型不是WAKE_LOCK_AUTO_EXPIRE直接返回-1,也就是不会触发睡眠;否则就一个个判断,当前的活跃的锁的寿命是不是到期了,到期了就把这个锁移除,并加入到不活跃列表里面;如果还没有到期,就保存当前到期的最长的时间,并返回;
现在假设我们的锁都到期了,于是触发了函数suspend的执行;
通过函数pm_suspend开启了睡眠的流程;
后面的流程我就不详细分析了,简单的说就是先suspend console(这里可以通过传递no_console_suspend=0开启睡眠时候的打印信息),在suspend具体的设备,设备的suspend,一般分为先prepare,再suspend;等这里执行完毕后就执行msm_pm_enter(suspend_state_t state),这里根据不同的电源管理的模式,执行不同的操作,再通知arm9,睡眠的信息,等待arm9的答复,答复完毕后通过idle-v6.S文件里面的msm_pm_collapse真正让CPU停止运行;
1,arm11的设备进入睡眠的过程没有分析;
2,arm9的睡眠过程没有分析;
3,其它;
总结:
分析下来,才发现电源管理其实挺复杂的(似乎就没有不复杂的);
Android的实现架构是提供一个Manager给客户端进入,一个Service用于上接Manager下接JNI以及更底层的HAL层,HAL层和驱动交互;
电源管理的核心是这个Service,它需要实现对客户端的不同类型的锁的管理,以及系统其它部分的协调和通知,比如锁屏解锁画面的显示,外部事件的处理;并控制系统的睡眠和唤醒,电源管理控制的实际上只是前期睡眠,深度睡眠时由驱动来控制的(前期睡眠可以理解为从屏亮到屏暗的过程;后期睡眠是屏暗到CPU停止运行);前期睡眠需要完成的是对各种灯的状态的控制、对驱动的信息的传递,以及对系统其它部分的信息传递,这时候通常也就是锁屏界面显示的时机;后期睡眠主要是驱动通过定时查看是否有活跃的锁来实现,如果没有,就触发睡眠流程;解锁的过程相反,是从驱动醒过来,传递唤醒信息,由上层决定,是否继续睡眠还是唤醒,这时候涉及的也有解锁画面的显示,对驱动的传递,以及各种灯的控制;
Service里面还有一个状态机的控制,也就是控制屏幕如何从屏亮到屏灭的过程,一旦屏幕灭掉,就会通知内核开始进入到早期睡眠了;
【1】 Android 按键事件分发原理分析.doc …………………..wylhistory
作者:wylhistory
联系方式:wylhistory@gmail.com