KeyEvent事件的传递
KeyEvent事件的传递
最近解bug的时候遇到这么一个case,就是用户点击导航栏的返回键时,onBackPress并没有执行,再进一步调试发现onKeyDown也没有执行到。所以下面我会尽可能从代码层面分析KeyEvent事件的传递,而一些涉及WMS,IME的就不去深入分析了。
KeyEvent主要包括手机导航键Back、Home、Task,也有可能是连接了外置键盘的键盘键。
onBackPress和onKeyDown的关系
Activity.java
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
event.startTracking();
} else {
onBackPressed();
}
return true;
}
...
}
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (getApplicationInfo().targetSdkVersion
>= Build.VERSION_CODES.ECLAIR) {
if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
&& !event.isCanceled()) {
onBackPressed();
return true;
}
}
return false;
}
从代码中可以看到,
- Android E以下,back键只要按下就触发onBackPress
- Android E以上,当back按下后,会开始追溯tracking,当back按下一定事件会触发onLongPress,短时间弹起的话会触发onKeyUp。
KeyEvent.java
//KeyEvent 省略许多代码
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
boolean res = receiver.onKeyDown(mKeyCode, this);
if (!res) {
//一定条件下
onKeyLongPress;
}
...
case ACTION_UP: {
onKeyUp;
}
...
- 从KeyEvent的分发来看,onKeyDown和onKeyLongPress有可能都执行。
- 而onKeyUp和onKeyLongPress只会执行其中一个。
KeyEvent分发给onKeyDown
Activity最早可以处理KeyEvent的时机应该是dispatchEvent:
Activity.java
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
final int keyCode = event.getKeyCode();
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
- onUserInteraction()表示用户触发了点击事件,但该方法不能阻止事件往下传递。
- 传递流:menu处理-> window处理-> KeyEvent的dispatch处理,在dispatch里面就交给Activituy执行onKeyDown和onKeyUp。
从系统层面看
- WMS接收到KeyEvent,判断是否为系统事件,比如home,那么系统会自己处理,不会往下传递;
- WMS将KeyEvent交给ViewRootImpl,按照触摸事件的传递方式调用mView.dispatchKeyEventPreIme,如果上层ViewGroup没有处理,将交给下层的ViewGroup或View,然后执行View的onKeyPreIme;
- onKeyPreIme处理完之后,如果输入法窗口有显示,则交给输入法处理
- 最后交给Activity的diapatchKeyEvent处理了。
onKeyPreIme分析
IME全称InputMethodEditors,也就是输入法。
ViewGroup.java
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
return super.dispatchKeyEventPreIme(event);
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
//将KeyEvent传递给具有焦点的子View
return mFocused.dispatchKeyEventPreIme(event);
}
return false;
}
View.java
/**
* Handle a key event before it is processed by any input method
* associated with the view hierarchy. This can be used to intercept
* key events in special situations before the IME consumes them; a
* typical example would be handling the BACK key to update the application's
* UI instead of allowing the IME to see it and close itself.
*
* @param keyCode The value in event.getKeyCode().
* @param event Description of the key event.
* @return If you handled the event, return true. If you want to allow the
* event to be handled by the next receiver, return false.
*/
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
return false;
}
- onKeyPreIme会在交给输入法之前执行,返回true则表示消费该事件,则输入法接收不到该事件,返回false则表示传递给输入法。
- View默认返回false。
AutoCompleteTextView实现了onKeyPreIme:
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
&& !mPopup.isDropDownAlwaysVisible()) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.startTracking(event, this);
}
return true;
} else if (event.getAction() == KeyEvent.ACTION_UP) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
state.handleUpEvent(event);
}
if (event.isTracking() && !event.isCanceled()) {
dismissDropDown();
return true;
}
}
}
return super.onKeyPreIme(keyCode, event);
}
AutoCompleteTextView在输入法之前拦截KeyEvent主要是为了:当联想词浮窗展开(PopupShowing)的时候,用户按back键后收起联想词,同时return true不让输入法处理。
总结
- Activity层最早接收到KeyEvent的时机是dispatchKeyEvent,可以通过重写来达到不往下传递的作用;
- View的onPreIme是在交给输入法之前处理KeyEvent的,通过返回值决定是否交给输入法处理。