按键事件在activity中的流程和按键事件在native和jni中的流程两篇文章主要探讨了事件在activity中的处理流程和事件在native层的处理流程。本文则主要探讨事件如何进入activity,以及如果activity未处理事件时,事件在framework中的处理。
事件如何进入activity
前面的文章已经讲到了事件经过native和jni的处理之后,最终通过InputChannel进入到了ViewRootImpl。这个ViewRootImpl实现了ViewParent接口,是任何一个Window内的view层级的最顶级ViewParent。在ViewRootImpl中有一个WindowInputEventReceiver的内部类,它继承于InputEventReceiver(可以认为是InputChannel的回调,事件会进入其onInputEvent()函数中)。在WindowInputEventReceiver中,按键事件最终会送到ViewRootImpl的deliverKeyEvent()中(touch、trackball等事件也都有类似方法)。
private void deliverKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
if (mView != null && mAdded && (q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);
// Perform predispatching before the IME.
if (mView.dispatchKeyEventPreIme(event)) {
finishInputEvent(q, true);
return;
}
// Dispatch to the IME before propagating down the view hierarchy.
// The IME will eventually call back into handleImeFinishedEvent.
if (mLastWasImTarget) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
final int seq = event.getSequenceNumber();
if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
+ seq + " event=" + event);
imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
return;
}
}
}
// Not dispatching to IME, continue with post IME actions.
deliverKeyEventPostIme(q);
}
函数中的mView实际上就是PhoneWindow中的DecorView。在deliverKeyEventPostIme()函数中,会先后调用mView的dispatchKeyEvent()和dispatchKeyShortcutEvent()方法,如果事件还是未被处理,并且按键是方向键时,则会做寻找焦点的逻辑。所以,综合起来,大概逻辑就是先后调用了DecorView的dispatchKeyEventPreIme()、dispatchKeyEvent()、dispatchKeyShortcutEvent(),最后是找焦点。dispatchKeyEventPreIme()函数,在TextView中会有一些逻辑,其它地方基本都直接返回false。
注意,中间会有和IMM的交互。而IMM会将事件通过InputMethodSession接口,将事件送入InputMethodService的onKeyDown()和onKeyUp()函数,处理一些输入文件选中的一些逻辑,不再详叙。
dispatchKeyEvent()函数的代码如下:
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
if (isDown && (event.getRepeatCount() == 0)) {
// First handle chording of panel key: if a panel key is held
// but not released, try to execute a shortcut in it.
if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
boolean handled = dispatchKeyShortcutEvent(event);
if (handled) {
return true;
}
}
// If a panel is open, perform a shortcut on it without the
// chorded panel key
if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
if (!isDestroyed()) {
final Callback cb = getCallback();
final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
: super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
核心逻辑是将事件送给了Window.callback.dispatchKeyEvent(),然后会进入PhoneWindow的onKeyDown()和onKeyUp()中。而activity正实现了Window.Callback。也就是说事件会先进入activity的dispatchKeyEvent()中,这也是我们在按键事件在activity中的流程一文中所说的事件在activity中的起点。
事件在PhoneWindow中的处理
PhoneWindow的onKeyDown()和onKeyUp()函数都很简单,主要会处理vol、menu、search相关的按键。vol将传递给AudioManager,显示音量调节。menu则主要和ActionBar交互,显示/隐藏菜单之类的。search则会通过Window.Callback进入activity,并最终调用SearchManager.startSearch()函数。以上逻辑都比较简单了,不在详细分析。