对于Power键的分析文章较多,本文从android7.0源码的角度大致分析下电源键的流程!参考博主连接:http://blog.csdn.net/gaugamela/article/details/52912382 和http://blog.csdn.net/kc58236582/article/details/51568506!
一 android 按键事件简介
在android系统中,每一个App所呈现的UI都是一个Activity,而UI的呈现实际上则是由Window对象来实现的,每个Window对象实际上又是PhoneWindow对象;而每个PhonewWindow对象又是一个PhoneWindowManager(PWM)对象。在将按键处理操作分发到App之前,首选会回调PWM中的dispatchUnhandledKey()方法,该方法主要是用于在执行当前App处理之前的操作,处理一些特殊按键。
PWM中有两个对按键比较重要的函数(按键预处理,针对特殊按键,如Power):intercepetKeyBeforeDispatching()和intercepetKeyBeforeQueueing()。
intercepetKeyBeforeQueueing()主要是用来处理音量和电源键,该函数中有一个重要的变量ACTION_PASS_TO_USER(变量解释:传递按键事件到用户app进行处理),该函数的返回值如果是0则表示该按键事件被消费,按键事件不会传递到用户App,返回1则表示允许按键事件传递给用户App中进行处理。
intercepetKeyBeforeDispatching()主要是用来预处理home、menu、search按键事件;该函数的返回值如果是-1则表示的是该按键事件被消费,返回0则代表允许事件的往下传递。这些处理的实现都是在PWM,其调用者是InputDispatchThread,这样处理的好处就是把平台相关的东西都放置在PWM中,而InputDispatchThread则是平台通用的,厂商要定制特殊策略只需要更改PWM,这样就很好的做到了Mechanism和Policy的分离。
二 PWM函数解析
@Override
public KeyEventdispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags) {
KeyEvent fallbackEvent = null;
//下面的逻辑主要是用于判断按键事件是否被消费;因为同一时刻可能有两个按键事件同时触发,如组合键(Power+音量下键实现截屏),则必须判断该逻辑表明一个按键还未消费
if ((event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {//按键标识符
final KeyCharacterMap kcm = event.getKeyCharacterMap(); //获得键盘映射----字符映射KeyCharacterMap
final int keyCode = event.getKeyCode(); //按键码
final int metaState = event.getMetaState();//获取功能键状态;如果没有功能键(如alt、shift)按下,则metaState为0
final boolean initialDown = event.getAction() == KeyEvent.ACTION_DOWN
&& event.getRepeatCount() == 0; //首次按键
// Check for fallback actions specified by the key character map.
final FallbackAction fallbackAction; //不太懂
if (initialDown) {
fallbackAction = kcm.getFallbackAction(keyCode, metaState);
} else {
fallbackAction = mFallbackActions.get(keyCode);
}
if (fallbackAction != null) {
final int flags = event.getFlags() | KeyEvent.FLAG_FALLBACK;
fallbackEvent = KeyEvent.obtain(
event.getDownTime(), event.getEventTime(),
event.getAction(), fallbackAction.keyCode,
event.getRepeatCount(), fallbackAction.metaState,
event.getDeviceId(), event.getScanCode(),
flags, event.getSource(), null); //按键事件
if (!interceptFallback(win, fallbackEvent, policyFlags)) {
fallbackEvent.recycle();
fallbackEvent = null;
}
if (initialDown) {
mFallbackActions.put(keyCode, fallbackAction);
} else if (event.getAction() == KeyEvent.ACTION_UP) {
mFallbackActions.remove(keyCode);
fallbackAction.recycle();
}
}
}
return fallbackEvent;
}
其中最主要分析以下的代码
private boolean interceptFallback(WindowState win, KeyEvent fallbackEvent, int policyFlags) {
int actions =interceptKeyBeforeQueueing(fallbackEvent, policyFlags);
if ((actions & ACTION_PASS_TO_USER) != 0) {
long delayMillis =interceptKeyBeforeDispatching(
win, fallbackEvent, policyFlags);
if (delayMillis == 0) {
return true;
}
}
return false;
}
三 电源键(Power)函数分析
在interceptKeyBeforeQueueing函数中对于电源键的处理部分代码如下:
case KeyEvent.KEYCODE_POWER: {
result &= ~ACTION_PASS_TO_USER;
isWakeKey = false; // wake-up will be handled separately
if (down) {
interceptPowerKeyDown(event, interactive); //power键按下
} else {
interceptPowerKeyUp(event, interactive, canceled);//power键抬起
}
break;
}
3.1 power键按下处理
private void interceptPowerKeyDown(KeyEvent event, boolean interactive) {
// 申请wakelock锁直至power键释放
if (!mPowerKeyWakeLock.isHeld()) {
mPowerKeyWakeLock.acquire();
}
// 取消power多次按下的消息,手机不支持这一方面
if (mPowerKeyPressCounter != 0) {
mHandler.removeMessages(MSG_POWER_DELAYED_PRESS);
}
// Detect user pressing the power button in panic when an application has
// taken over the whole screen.不太懂是什么情况,何时会走该函数
boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(interactive,
SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags));
if (panic) {
mHandler.post(mHiddenNavPanic);
}
//截屏函数相关
//interactive 表示屏幕是否点亮
if (interactive && !mScreenshotChordPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mScreenshotChordPowerKeyTriggered = true;
mScreenshotChordPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
}
//获取电话管理者
TelecomManager telecomManager = getTelecommService();
boolean hungUp = false;
if (telecomManager != null) {
if (telecomManager.isRinging()){//如果有电话拨入且电话铃声响起,按Power键设置电话响铃静音
telecomManager.silenceRinger(); //处于静音模式
} else if ((mIncallPowerBehavior
& Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0
&& telecomManager.isInCall() && interactive) {
//如果power键挂断电话使能,电话正处于通话状态,且处于亮屏状态
// Otherwise, if "Power button ends call" is enabled,
// the Power button will hang up any current active call.
hungUp = telecomManager.endCall(); //挂断电话
}
}
//手势相关,不太懂
GestureLauncherService gestureService = LocalServices.getService(
GestureLauncherService.class);
boolean gesturedServiceIntercepted = false;
if (gestureService != null) {
//手势对应的服务,尝试处理power键动作事件
gesturedServiceIntercepted = gestureService.interceptPowerKeyDown(event, interactive,
mTmpBoolean);
if (mTmpBoolean.value && mGoingToSleep) {
mCameraGestureTriggeredDuringGoingToSleep = true;
}
}
// If the power key has still not yet been handled, then detect short
// press, long press, or multi press and decide what to do.
//该变量主要用于判断power键事件是否消费,如果没有被消费掉则检测power是短按、长按、多次按下三种状态
mPowerKeyHandled = hungUp || mScreenshotChordVolumeDownKeyTriggered
|| mScreenshotChordVolumeUpKeyTriggered || gesturedServiceIntercepted
if (!mPowerKeyHandled) {//power键事件未被消费
if (interactive) {//处于亮屏状态
// When interactive, we're already awake.
// Wait for a long press or for the button to be released to decide what to do.
//支持长按功能主要是判断变量mLongPressOnPowerBehavior不等于0,猜测一定是支持长按功能,否则长按关机、重启等截面又如何展现呢?
if (hasLongPressOnPowerBehavior()) {//是否支持长按
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
}
} else {//灭屏状态
wakeUpFromPowerKey(event.getDownTime());//按power键唤醒系统
/*
* mSupportLongPressPowerWhenNonInteractive = mContext.getResources().getBoolean(
* com.android.internal.R.bool.config_supportLongPressPowerWhenNonInteractive);
/*
if (mSupportLongPressPowerWhenNonInteractive && hasLongPressOnPowerBehavior()) { //是否支持灭屏状态下长按Power功能
Message msg = mHandler.obtainMessage(MSG_POWER_LONG_PRESS);//android系统默认为false
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg,
ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
mBeganFromNonInteractive = true;
} else { //走这个分支
final int maxCount = getMaxMultiPressPowerCount();//getMaxMultiPressPowerCount返回1
if (maxCount <= 1) {//走该分支,即不支持灭屏状态下的power长按功能,则只是唤醒系统,之后不做任何处理,标志power键已经消费掉
mPowerKeyHandled = true;
} else {
mBeganFromNonInteractive = true;
}
}
}
}
}
mLongPressOnPowerBehavior赋值是在mLongPressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_longPressOnPowerBehavior);
Android系统的默认值为
1
表明Android的power Key是支持长按功能,找到问题点。
长按之后会发送一个
MSG_POWER_LONG_PRESS的异步消息,该消息的接收是在
case MSG_POWER_LONG_PRESS:
powerLongPress(); //Power键长按处理
break;
private void powerLongPress() {
//该函数android原生就是返回mLongPressOnPowerBehavior的变量值,默认为1
final int behavior = getResolvedLongPressOnPowerBehavior();
switch (behavior) {
case LONG_PRESS_POWER_NOTHING: //0
break;
case LONG_PRESS_POWER_GLOBAL_ACTIONS: //1,power键长按
mPowerKeyHandled = true;
//貌似是:终端对接收事件处理后,给用户一个反馈信息;
performHapticFeedbackLw主要进行震动反馈,例如按键后,终端震动一下;不同的事件,定义了不同的震动模式
if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
performAuditoryFeedbackForAccessibilityIfNeed();//如果没有震动反馈,尝试进行声音的反馈,例如响一下按键音
}
showGlobalActionsInternal(); //弹出选择关机or重启对话框
break;
case LONG_PRESS_POWER_SHUT_OFF: //2关机,直接调用mWindowManagerFuncs.shutdown()进行关机,shutdown()原型
case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM: //3
mPowerKeyHandled = true;
performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
break;
}
}
Power键长按会弹出选择界面,如:飞行模式、关机、重启等选项;在此先分析下
showGlobalActionsInternal函数:
void showGlobalActionsInternal() {
sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
if (mGlobalActions == null) {//new 一个GlobalActions实例
mGlobalActions = new GlobalActions(mContext, mWindowManagerFuncs);
}
//锁屏是否显示
final boolean keyguardShowing = isKeyguardShowingAndNotOccluded();
mGlobalActions.showDialog(keyguardShowing, isDeviceProvisioned());
if (keyguardShowing) {
// since it took two seconds of long press to bring this up,
// poke the wake lock so they have some time to see the dialog.
mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
}
}
void sendCloseSystemWindows(String reason) {
PhoneWindow.sendCloseSystemWindows(mContext, reason);
}
进入到PhoneWindow.java中
public static void sendCloseSystemWindows(Context context, String reason) {
if (ActivityManagerNative.isSystemReady()) {
try {
ActivityManagerNative.getDefault().closeSystemDialogs(reason);
} catch (RemoteException e) {
}
}
}
采用Binder机制,最终会调用到ActivityManagerService.java中的closeSystemDialogs()函数,该函数主要功能是关闭当前页面中存在的系统窗口,比如输入法、壁纸等。
进入到GlobalActions.java中
/frameworks/base/services/core/java/com/android/server/policy/GlobalActions.java
/**
* Show the global actions dialog (creating if necessary)
* @param keyguardShowing True if keyguard is showing
*/
public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) {
mKeyguardShowing = keyguardShowing; //锁屏是否显示
mDeviceProvisioned = isDeviceProvisioned; //
if (mDialog != null) {//弹框已经出现
mDialog.dismiss(); //弹框消失
mDialog = null;
// Show delayed, so that the dismiss of the previous dialog completes
mHandler.sendEmptyMessage(MESSAGE_SHOW);
} else {
handleShow();
}
}
在这个函数里面,mDialog一定为空,走到else分支里面,但是不论走那个分支,最终都会走到handleShow()函数里面来进行处理:
private void handleShow() {
awakenIfNecessary();//如果处于屏幕保护,则唤醒
mDialog = createDialog(); //创建全局弹框的Dialog
prepareDialog();
// If we only have 1 item and it's a simple press action, just do this action.
if (mAdapter.getCount() == 1
&& mAdapter.getItem(0) instanceof SinglePressAction
&& !(mAdapter.getItem(0) instanceof LongPressAction)) {
((SinglePressAction) mAdapter.getItem(0)).onPress();
} else {
WindowManager.LayoutParams attrs = mDialog.getWindow().getAttributes();
attrs.setTitle("GlobalActions");
mDialog.getWindow().setAttributes(attrs);
mDialog.show();
mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND);
}
}/**
* Create the global actions dialog.
* @return A new dialog.
*/
private GlobalActionsDialogcreateDialog() {
..................................
mAirplaneModeOn = new ToggleAction(
R.drawable.ic_lock_airplane_mode,
R.drawable.ic_lock_airplane_mode_off,
R.string.global_actions_toggle_airplane_mode,
R.string.global_actions_airplane_mode_on_status,
R.string.global_actions_airplane_mode_off_status) {
...................
onAirplaneModeChanged();
mItems = new ArrayList();
String[] defaultActions = mContext.getResources().getStringArray(
com.android.internal.R.array.config_globalActionsList);
for (int i = 0; i < defaultActions.length; i++) {
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) {
// If we already have added this, don't add it again.
continue;
}
if (GLOBAL_ACTION_KEY_POWER.equals(actionKey)) {
mItems.add(new PowerAction());
}else if(GLOBAL_ACTION_KEY_REBOOT.equals(actionKey)){
mItems.add(new RebootAction());
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
}else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
if (Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
mItems.add(new BugReportAction());
}
} else if (GLOBAL_ACTION_KEY_SILENT.equals(actionKey)) {
if (mShowSilentToggle) {
mItems.add(mSilentModeAction);
}
} else if (GLOBAL_ACTION_KEY_USERS.equals(actionKey)) {
if (SystemProperties.getBoolean("fw.power_user_switcher", false)) {
addUsersToMenu(mItems);
}
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
mItems.add(getLockdownAction());
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
} else if (GLOBAL_ACTION_KEY_ASSIST.equals(actionKey)) {
mItems.add(getAssistAction());
} else {
Log.e(TAG, "Invalid global action key " + actionKey);
}
// Add here so we don't add more than one.
addedKeys.add(actionKey);
}
mAdapter = new MyAdapter();
AlertParams params = new AlertParams(mContext);
params.mAdapter = mAdapter;
params.mOnClickListener = this;
params.mForceInverseBackground = true;
GlobalActionsDialog dialog = newGlobalActionsDialog(mContext, params);
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.getListView().setItemsCanFocus(true);
dialog.getListView().setLongClickable(true);
dialog.getListView().setOnItemLongClickListener(
new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(AdapterView> parent, View view, int position,
long id) {
final Action action = mAdapter.getItem(position); //获取点击的项
if (action instanceof LongPressAction) {
return ((LongPressAction) action).onLongPress();
}
return false;
}
});
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
dialog.setOnDismissListener(this);
return dialog;
}
对于长按Power键弹出的选项的配置是在framework/base/core/res/res/values/config.xml中
如
"config_globalActionsList">
- power
- reboot
- airplane
- bugreport
- users
获得长按所弹出的操作列表,这里可能包含关机、重启、静音等,然后将所具有的操作添加到操作列表中并保存到Dialog的适配器adapter之中并最终返回该dialog。
之后设置好窗口属性,再调用
mDialog.show()进行长按操作所弹出的界面显示:在此,对于之后按下各个列表之后的动作只需要找到对应的类进行分析即可或者参考其他博客!
3.2 Power键抬起函数
private void interceptPowerKeyUp(KeyEvent event, boolean interactive, boolean canceled) {
final boolean handled = canceled || mPowerKeyHandled; //按键是否已经处理
mScreenshotChordPowerKeyTriggered = false; //重置Power键截屏标志为false
cancelPendingScreenshotChordAction(); //退出截屏
cancelPendingPowerKeyAction(); //取消长按功能
//灭屏状态下和亮屏长按时mPowerKeyHandled最后会设置成true,即handle为true则直接执行finishPowerKeyPress
//在亮屏短按的时候,handle为false,最终会发送消息:SHORT_PRESS_POWER_GO_TO_SLEEP,使得手机进入休眠状态
if (!handled) { //如果Power键未处理
// Figure out how to handle the key now that it has been released.
mPowerKeyPressCounter += 1;//记录短按的次数
final int maxCount = getMaxMultiPressPowerCount();//返回值为1
final long eventTime = event.getDownTime();//按下的时间
if (mPowerKeyPressCounter < maxCount) {//代码不会走到这个分支
// This could be a multi-press. Wait a little bit longer to confirm.
// Continue holding the wake lock.
Message msg = mHandler.obtainMessage(MSG_POWER_DELAYED_PRESS,
interactive ? 1 : 0, mPowerKeyPressCounter, eventTime);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, ViewConfiguration.getDoubleTapTimeout());
return;
}
// No other actions. Handle it immediately.
powerPress(eventTime, interactive, mPowerKeyPressCounter);
}
// Done. Reset our state.
finishPowerKeyPress();//重置power键状态并释放wakelock锁
}
private int getMaxMultiPressPowerCount() {
if (mTriplePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
return 3;
}
if (mDoublePressOnPowerBehavior != MULTI_PRESS_POWER_NOTHING) {
return 2;
}
return 1;
}
mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_doublePressOnPowerBehavior);//默认为0
mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
com.android.internal.R.integer.config_triplePressOnPowerBehavior);//默认为0
//由上面的分析可知,powerpress的count为1
private void powerPress(long eventTime, boolean interactive, int count) {
if (mScreenOnEarly && !mScreenOnFully) {
Slog.i(TAG, "Suppressed redundant power key press while "
+ "already in the process of turning the screen on.");
return;
}
.......
else if (interactive && !mBeganFromNonInteractive) {
switch (mShortPressOnPowerBehavior) {
case SHORT_PRESS_POWER_NOTHING: //0
break;
case SHORT_PRESS_POWER_GO_TO_SLEEP: //1 走入该分支里面,执行休眠
mPowerManager.goToSleep(eventTime,
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON, 0);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP: //2
mPowerManager.goToSleep(eventTime,
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
break;
case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME: //3
mPowerManager.goToSleep(eventTime,
PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
PowerManager.GO_TO_SLEEP_FLAG_NO_DOZE);
launchHomeFromHotKey();
break;
case SHORT_PRESS_POWER_GO_HOME: //4
launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/);
break;
}
}
}
到此,整个Power的按下和抬起操作函数从源码角度分析了代码流程,能够了解到Power键的大致处理流程;可能代码片段较多,看着比较头晕,最好是对比着源码走一遍流程,基本上也就了解了Power的处理流程。
在此结尾处,分享以前遇到的一个问题:组合键调出一个应用App;该问题的处理其实比较简单,只需要按照源码中的Power+音量下键组合调用截屏功能进行下分析,就可以解决该问题;同时要记住加上上面说的按键标识的处理KeyEvent.FLAG_FALLBACK;如果不加该标识,打印log时会发现两个按键处理时只有一个按键会有时间,另一个按键不会有按下的时间;因此注意下这个标识!