按鍵事件在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()函數。以上邏輯都比較簡單了,不在詳細分析。