1. ViewRootImpl.deliverKeyEvent(QueuedInputEvent q)
1. 如果mView为空或者mAdded为false,就直接调用finishInputEvent。
2. mView.dispatchKeyEventPreIme(event), 在传递给IME之前做一些预处理。因为对于View来说,如果有输入窗口存在的话,会先将按键消息派发到输入窗口,只有当输入窗口没有处理这个事件,才会派发到真正的视图。因此如果想要在输入法截取事件前处理该消息,则可以重载这个方法去处理一些特定的按键消息。
3. 如果有IME窗口存在,就把这个传递给IME进行处理。imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
4. deliverKeyEventPostIme(q); 将消息派发给真正的视图。
private void deliverKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
if ((q.mFlags & QueuedInputEvent.FLAG_DELIVER_POST_IME) == 0) {
// If there is no view, then the event will not be handled.
if (mView == null || !mAdded) {
finishInputEvent(q, false);
return;
}
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);
}
1. mView.dispatchKeyEvent(event); 将事件派发到View视图。因为mView就是一个DecorView,所以就是调用DecorView的dispatchKeyEvent的方法。
private void deliverKeyEventPostIme(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
... ...
// If the key's purpose is to exit touch mode then we consume it and consider it handled.
if (checkForLeavingTouchModeAndConsume(event)) {
finishInputEvent(q, true);
return;
}
// Make sure the fallback event policy sees all keys that will be delivered to the
// view hierarchy.
mFallbackEventHandler.preDispatchKeyEvent(event);
// Deliver the key to the view hierarchy.
if (mView.dispatchKeyEvent(event)) {
finishInputEvent(q, true);
return;
}
// If the Control modifier is held, try to interpret the key as a shortcut.
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.isCtrlPressed()
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())) {
if (mView.dispatchKeyShortcutEvent(event)) {
finishInputEvent(q, true);
return;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
finishInputEvent(q, true);
return;
}
// Handle automatic focus changes.
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int direction = 0;
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_LEFT;
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (event.hasNoModifiers()) {
direction = View.FOCUS_RIGHT;
}
break;
case KeyEvent.KEYCODE_DPAD_UP:
if (event.hasNoModifiers()) {
direction = View.FOCUS_UP;
}
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
if (event.hasNoModifiers()) {
direction = View.FOCUS_DOWN;
}
break;
case KeyEvent.KEYCODE_TAB:
if (event.hasNoModifiers()) {
direction = View.FOCUS_FORWARD;
} else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
direction = View.FOCUS_BACKWARD;
}
break;
}
if (direction != 0) {
View focused = mView.findFocus();
if (focused != null) {
View v = focused.focusSearch(direction);
if (v != null && v != focused) {
// do the math the get the interesting rect
// of previous focused into the coord system of
// newly focused view
focused.getFocusedRect(mTempRect);
if (mView instanceof ViewGroup) {
((ViewGroup) mView).offsetDescendantRectToMyCoords(
focused, mTempRect);
((ViewGroup) mView).offsetRectIntoDescendantCoords(
v, mTempRect);
}
if (v.requestFocus(direction, mTempRect)) {
playSoundEffect(SoundEffectConstants
.getContantForFocusDirection(direction));
finishInputEvent(q, true);
return;
}
}
// Give the focused view a last chance to handle the dpad key.
if (mView.dispatchUnhandledMove(focused, direction)) {
finishInputEvent(q, true);
return;
}
}
}
}
// Key was unhandled.
finishInputEvent(q, false);
}
1. 如果是Down事件, 首先会判断是不是Shortcut事件也就是快捷键按钮或者特殊事件,如果是就会调用dispatchKeyShortcutEvent和performPanelShortcut进行一些处理然后返回。
2. 如果不是快捷键按钮,就调用Callback Activity的cb.dispatchKeyEvent(event)去处理。Activity的dispatchKeyEvent中其实也还是去调用PhoneWindow中的superDispatchKeyEvent, PhoneWindow最终还是调用mDecor.superDispatchKeyEvent(event);
@Override
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);
}
1. 调用父类的dispatchKeyEvent。
public boolean superDispatchKeyEvent(KeyEvent event) {
if (super.dispatchKeyEvent(event)) {
return true;
}
// Not handled by the view hierarchy, does the action bar want it
// to cancel out of something special?
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mActionMode.finish();
}
return true;
}
// Next collapse any expanded action views.
if (mActionBar != null && mActionBar.hasExpandedActionView()) {
if (action == KeyEvent.ACTION_UP) {
mActionBar.collapseActionView();
}
return true;
}
}
return false;
}
1.2.1.2.1 ViewGroup.dispatchKeyEvent(KeyEvent event)
1. mPrivateFlags 是在View中定义的,标志当前View的属性。 这里吧的FOCUSED 和HAS_BOUNDS表示当前的View是否是focused和有bounds的。一般的View和ViewGroup都会有HAS_BOUNDS, 而mFocused记录这当前ViewGroup中处于focus的View。
例如:如果A包含B,B又包含C,如果C是一个View,C的mPrivateFlags就是 FOCUSED | HAS_BOUNDS那么B的mPrivateFlags就是FOCUSED,mFocused就是C,同理A的mPrivateFlags也是FOCUSED,mFocused就是B。
2. 所以一般情况下,ViewGroup通过mFocused.dispatchKeyEvent()递归把Event传到View。
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
如果mFocused是ViewGroup那么还是调用1.2.1.2.1;如果是View,那么就调用View的dispatchKeyEvent。
1. 如果有注册Listener,就直接调用Listener的onKey去相应,然后返回。
2. 如果没有注册Listener,就调用KeyEvent.dispatch去做最后的处理
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 0);
}
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
1. KeyEvent.dispatch会根据Action的类型调用receiver的对应的onXXX函数去处理。(receiver通常都是View)
2. 如果所有的View都没有处理这个KeyEvent,那么会最终通过KeyEvent调用Activity的onXXX函数。
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
if (DEBUG) Log.v(TAG, " Start tracking!");
state.startTracking(this, target);
} else if (isLongPress() && state.isTracking(this)) {
try {
if (receiver.onKeyLongPress(mKeyCode, this)) {
if (DEBUG) Log.v(TAG, " Clear from long press!");
state.performedLongPress(this);
res = true;
}
} catch (AbstractMethodError e) {
}
}
}
return res;
}
case ACTION_UP:
if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
+ ": " + this);
if (state != null) {
state.handleUpEvent(this);
}
return receiver.onKeyUp(mKeyCode, this);
case ACTION_MULTIPLE:
final int count = mRepeatCount;
final int code = mKeyCode;
if (receiver.onKeyMultiple(code, count, this)) {
return true;
}
if (code != KeyEvent.KEYCODE_UNKNOWN) {
mAction = ACTION_DOWN;
mRepeatCount = 0;
boolean handled = receiver.onKeyDown(code, this);
if (handled) {
mAction = ACTION_UP;
receiver.onKeyUp(code, this);
}
mAction = ACTION_MULTIPLE;
mRepeatCount = count;
return handled;
}
return false;
}
return false;
}