目录
1.1 按键事件(KeyEvent)传入 DecorView
1.1.1 时序图
1.1.2 代码分析
1.2 DecorView 往下分发按键事件(KeyEvent)
1.2.1 流程图
1.2.2 代码分析
1.3 InputStage 介绍
1.3.1 InputStage 介绍
1.3.2 创建责任链
1.3.3 EarlyPostImeInputStage
1.4 总结
本内容主要介绍 Android 中 Java 层 按键事件(KeyEvent) 的分发机制。基于 Android 9.0 的源码进行介绍。
Android 中所有输入事件都会封装为 InputEvent 进行分发,InputEvent 又分为实体按键事件(KeyEvent)和触摸事件(MotionEvent)两种类型。这些事件流入到上层之后才会分别进行处理。
本内容主要分为两大过程进行介绍:
- 按键事件(KeyEvent)传入 DecorView。
- DecorView 往下分发按键事件(KeyEvent)。
1.1 按键事件(KeyEvent)传入 DecorView
1.1.1 时序图

1.1.2 代码分析
当 InputEvent 从 Native 层传到 Java 层时,会调用 ViewRootImpl 内部类 WindowInputEventReceiver 的 dispatchInputEvent()。由于 WindowInputEventReceiver 没有实现 dispatchInputEvent(),因此将调用其父类 InputEventReceiver 的方法。
1.1.2.1 InputEventReceiver.dispatchInputEvent()
具体代码如下:
package android.view;
/**
* Provides a low-level mechanism for an application to receive input events.
* @hide
*/
public abstract class InputEventReceiver {
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event, displayId);
}
}
在 InputEventReceiver.dispatchInputEvent() 中将调用 onInputEvent(),因为 WindowInputEventReceiver 有实现这个函数,所以将调用 WindowInputEventReceiver.onInputEvent()。
1.1.2.2 ViewRootImpl.WindowInputEventReceiver.onInputEvent()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
final class WindowInputEventReceiver extends InputEventReceiver {
@Override
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
}
}
在 WindowInputEventReceiver.onInputEvent() 中将调用 enqueueInputEvent()。
1.1.2.3 ViewRootImpl.enqueueInputEvent()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
// 01. 把输入事件(InputEvent)封装为 QueuedInputEvent 对象
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
// 02. 把输入事件(即 QueuedInputEvent 对象)入队
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT,
mPendingInputEventQueueLengthCounterName, mPendingInputEventCount);
// 03. 根据 processImmediately 的值,执行不同操作
if (processImmediately) {
// 前面传入的 processImmediately 为 true,所有将执行 doProcessInputEvents()
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
}
在 ViewRootImpl.enqueueInputEvent() 中主要执行如下操作:
- 调用 obtainQueuedInputEvent() 函数把输入事件(InputEvent)封装为 QueuedInputEvent 对象。
- 把输入事件(即 QueuedInputEvent 对象)入队。
- 根据 processImmediately 的值,执行不同操作。由前面 WindowInputEventReceiver.onInputEvent() 的内容可知,这里的 processImmediately 为 true,所以将调用 doProcessInputEvents()。
1.1.2.4 ViewRootImpl.doProcessInputEvents()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
// 循环取出队列中的所有输入事件(即 QueuedInputEvent 对象),
// 然后调用 deliverInputEvent() 传送输入事件
while (mPendingInputEventHead != null) {
// 取出队列中的所有输入事件(即 QueuedInputEvent 对象)
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT,
mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
long eventTime = q.mEvent.getEventTimeNano();
long oldestEventTime = eventTime;
if (q.mEvent instanceof MotionEvent) {
MotionEvent me = (MotionEvent)q.mEvent;
if (me.getHistorySize() > 0) {
oldestEventTime = me.getHistoricalEventTimeNano(0);
}
}
mChoreographer.mFrameInfo.updateInputEventTime(
eventTime, oldestEventTime);
// 调用 deliverInputEvent() 传送输入事件
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
}
在 ViewRootImpl.doProcessInputEvents() 中,循环将队列中的所有输入事件(即 QueuedInputEvent 对象),传递给 deliverInputEvent() 进行处理。
1.1.2.5 ViewRootImpl.deliverInputEvent()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getSequenceNumber());
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
// 01. 获取 InputStage 对象
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
// 一般情况下,将会执行这里,并返回 mFirstInputStage
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (q.mEvent instanceof KeyEvent) {
mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
}
if (stage != null) {
handleWindowFocusChanged();
// 02. 调用 deliver()
stage.deliver(q);
} else {
finishInputEvent(q);
}
}
}
在 ViewRootImpl.deliverInputEvent() 中,首先获取 InputStage 对象,一般情况下返回 mFirstInputStage;然后调用其 deliver() 函数。按照 mFirstInputStage 的责任链,InputEvent 经过一步步传递,将执行 ViewPostImeInputStage.onProcess()。(有关 InputStage 的信息,可以参照下面的 1.3 InputState 介绍。)
1.1.2.6 ViewRootImpl.ViewPostImeInputStage.onProcess()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// 处理按键事件
return processKeyEvent(q);
} else {
// 处理触摸事件,例如鼠标,轨迹球,普通触摸
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
}
}
在 ViewPostImeInputStage.onProcess() 中,会对实体按键事件(KeyEvent)和触摸事件(MotionEvent)进行不同的处理。也意味着,系统从这里开始对 KeyEvent 和 MotionEvent 进行分开处理。
1.1.2.7 ViewRootImpl.ViewPostImeInputStage.processKeyEvent()
具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
/**
* Delivers post-ime input events to the view hierarchy.
*/
final class ViewPostImeInputStage extends InputStage {
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mUnhandledKeyManager.preViewDispatch(event)) {
return FINISH_HANDLED;
}
// Deliver the key to the view hierarchy.
// 01. 传递给 DecorView 进行按键事件分发
if (mView.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// This dispatch is for windows that don't have a Window.Callback. Otherwise,
// the Window.Callback usually will have already called this (see
// DecorView.superDispatchKeyEvent) leaving this call a no-op.
if (mUnhandledKeyManager.dispatch(mView, event)) {
return FINISH_HANDLED;
}
int groupNavigationDirection = 0;
// 根据组合键(Tab 键 + 其他键)确定焦点变化方向为向上或向下
if (event.getAction() == KeyEvent.ACTION_DOWN
&& event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
if (KeyEvent.metaStateHasModifiers(
event.getMetaState(), KeyEvent.META_META_ON)) {
groupNavigationDirection = View.FOCUS_FORWARD;
} else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),
KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
groupNavigationDirection = View.FOCUS_BACKWARD;
}
}
// If a modifier is held, try to interpret the key as a shortcut.
// 02. 处理组合快捷键(modifier key + 其他键)
if (event.getAction() == KeyEvent.ACTION_DOWN
&& !KeyEvent.metaStateHasNoModifiers(event.getMetaState())
&& event.getRepeatCount() == 0
&& !KeyEvent.isModifierKey(event.getKeyCode())
&& groupNavigationDirection == 0) {
if (mView.dispatchKeyShortcutEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
}
// Apply the fallback event policy.
if (mFallbackEventHandler.dispatchKeyEvent(event)) {
return FINISH_HANDLED;
}
if (shouldDropInputEvent(q)) {
return FINISH_NOT_HANDLED;
}
// Handle automatic focus changes.
// 03. 处理 Focus 变更
if (event.getAction() == KeyEvent.ACTION_DOWN) {
if (groupNavigationDirection != 0) {
if (performKeyboardGroupNavigation(groupNavigationDirection)) {
return FINISH_HANDLED;
}
} else {
// 按下键盘的上下左右键以及 Tab 按键后,处理 Focus 变更
if (performFocusNavigation(event)) {
return FINISH_HANDLED;
}
}
}
return FORWARD;
}
}
}
在 ViewPostImeInputStage.processKeyEvent() 中,主要执行以下操作:
- 调用 DecorView.dispatchKeyEvent() 处理输入事件。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 调用 DecorView.dispatchKeyShortcutEvent() 处理组合快捷键(modifier key + 其他键)。
- 处理 Focus 变更。例如,按下 “上下左右” 导航键时,需要变更 Focus。
经过上面的代码流程分析,按键事件(KeyEvent)传入到了 DecorView 中。
1.2 DecorView 往下分发按键事件(KeyEvent)
1.2.1 流程图

1.2.2 代码分析
1.2.2.1 DecorView.dispatchKeyEvent()
具体代码如下:
package com.android.internal.policy;
public class DecorView extends FrameLayout
implements RootViewSurfaceTaker, WindowCallbacks {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
final int action = event.getAction();
final boolean isDown = action == KeyEvent.ACTION_DOWN;
// 01. 处理快捷按键
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 ((mWindow.mPanelChordingKey > 0)
&& (mWindow.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 ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
if (mWindow.performPanelShortcut(
mWindow.mPreparedPanel, keyCode, event, 0)) {
return true;
}
}
}
// 02. 调用 Activity.dispatchKeyEvent()
if (!mWindow.isDestroyed()) {
// cb 实际上是一个 Activity 或 Dialog 对象
final Window.Callback cb = mWindow.getCallback();
// mFeatureId 代表应用程序的特征标识或者整个屏幕的标识。如果是应用程序,其值为 -1
// 所以会执行 cb.dispatchKeyEvent()
final boolean handled = cb != null && mFeatureId < 0
? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event);
if (handled) {
return true;
}
}
// 03. 调用 PhoneWindow 的 onKeyDown() 或 onKeyUp()
return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
: mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
}
在 DecorView.dispatchKeyEvent() 中主要执行以下操作:
- 处理快捷键。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 获取到的 cb 实际上是一个 Activity 或 Dialog 对象,我们这里以 Activity 为例进行说明;并且 mFeatureId 的值为 -1,所以将调用 Activity.dispatchKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 根据 isDown 的值,分别调用 PhoneWindow 的 onKeyDown() 或 onKeyUp()(详见 PhoneWindow 的 onKeyDown() 和 onKeyUp()),并返回执行结果。
1.2.2.2 Activity.dispatchKeyEvent()
具体代码如下:
package android.app;
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
/**
* Called to process key events. You can override this to intercept all
* key events before they are dispatched to the window. Be sure to call
* this implementation for key events that should be handled normally.
*
* @param event The key event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchKeyEvent(KeyEvent event) {
onUserInteraction();
// Let action bars open menus in response to the menu key prioritized over
// the window handling it
final int keyCode = event.getKeyCode();
// 01. 如果是 menu 键,则将先调用 ActionBar.onMenuKeyEvent()。
// 如果 ActionBar 没有消费这个事件,才继续往下执行
if (keyCode == KeyEvent.KEYCODE_MENU &&
mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
return true;
}
// 通过 getWindow() 获取到的实际上是一个 PhoneWindow 对象
Window win = getWindow();
// 02. 调用 PhoneWindow.superDispatchKeyEvent()
if (win.superDispatchKeyEvent(event)) {
return true;
}
View decor = mDecor;
if (decor == null) decor = win.getDecorView();
// 03. 如果在上一步仍然没有消费这个事件,将调用 KeyEvent.dispatch()
return event.dispatch(this, decor != null
? decor.getKeyDispatcherState() : null, this);
}
}
在 dispatchKeyEvent() 中主要执行以下操作:
- 如果是 menu 键,则将调用 ActionBar.onMenuKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。通常按 menu 键时,会启动 Recent App。因此,如果是 menu 键的话,一般不会传递到这里,就已经被消费了。所以这里一般情况下不会消费输入事件。
- 通过 getWindow() 获取到的实际上是一个 PhoneWindow 对象,因此将调用 PhoneWindow.superDispatchKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 调用 KeyEvent.dispatch(),最终将调用 Activity 的 onKeyDown() 和 onKeyUp()(详见 Activity 的 onKeyDown() 和 onKeyUp()),并返回执行结果。
1.2.2.3 PhoneWindow.superDispatchKeyEvent()
具体代码如下:
package com.android.internal.policy;
public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public boolean superDispatchKeyEvent(KeyEvent event) {
// 这里的 mDecor 是 DecorView 对象
return mDecor.superDispatchKeyEvent(event);
}
}
在 superDispatchKeyEvent() 中将直接调用 DecorView.superDispatchKeyEvent(),重新回到 DecorView 中。
1.2.2.4 DecorView.superDispatchKeyEvent()
具体代码如下:
package com.android.internal.policy;
public class DecorView extends FrameLayout
implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchKeyEvent(KeyEvent event) {
// Give priority to closing action modes if applicable.
// 01. 如果是 Back 键,并且当前为 Action Mode,将消费输入事件。
// 在松开按键时,将退出 Action Mode。
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
final int action = event.getAction();
// Back cancels action modes first.
if (mPrimaryActionMode != null) {
if (action == KeyEvent.ACTION_UP) {
mPrimaryActionMode.finish();
}
return true;
}
}
// 02. 调用父类的 dispatchKeyEvent()
if (super.dispatchKeyEvent(event)) {
return true;
}
// 03. 调用 ViewRootImpl.dispatchUnhandledKeyEvent()
return (getViewRootImpl() != null)
&& getViewRootImpl().dispatchUnhandledKeyEvent(event);
}
}
在 DecorView.superDispatchKeyEvent() 中主要执行以下操作:
- 如果是 Back 键,并且当前处于 Action Mode,将消费输入事件,并返回 true;否则,将继续往下执行。
- 调用父类的 dispatchKeyEvent(),其直接父类 FrameLayout 没有实现 dispatchKeyEvent() 方法,所以将调用 ViewGroup 的 dispatchKeyEvent() 方法。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 调用 ViewRootImpl.dispatchUnhandledKeyEvent(),并返回执行结果。
1.2.2.5 ViewGroup.dispatchKeyEvent()
具体代码如下:
package android.view;
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onKeyEvent(event, 1);
}
if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
== (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
// 如果其自身拥有焦点并且边界确定,则调用其父类 View 的 dispatchKeyEvent()
if (super.dispatchKeyEvent(event)) {
return true;
}
} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
== PFLAG_HAS_BOUNDS) {
// 将事件分发给拥有或包含焦点的子 View
if (mFocused.dispatchKeyEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
}
return false;
}
}
在 ViewGroup.dispatchKeyEvent() 中,根据不同情况进行处理:
- 如果其自身拥有焦点并且边界确定,则调用其父类 View 的 dispatchKeyEvent() 进行处理。
- 否则,将输入事件交由其拥有或包含焦点的子 View 处理。
在实际过程中,上面的过程是一个循环过程,从 DecorView 开始往下遍历查找,直到找到拥有焦点的 View 或 ViewGroup,然后调用 View.dispatchKeyEvent()。如果这个过程最终有 View 消费了输入事件,将返回 true;否则,返回 false。
注意:自定义的 View 和 ViewGroup 可以重载 dispatchKeyEvent(),从而实现自定义处理按键事件(KeyEvent)的效果。
1.2.2.6 View.dispatchKeyEvent()
具体代码如下:
package android.view;
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
/**
* Dispatch a key event to the next view on the focus path. This path runs
* from the top of the view tree down to the currently focused view. If this
* view has focus, it will dispatch to itself. Otherwise it will dispatch
* the next node down the focus path. This method also fires any key
* listeners.
*
* @param event The key event to be dispatched.
* @return True if the event was handled, false otherwise.
*/
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;
// 01. 如果对当前 View 设置了 OnKeyListener,并且处于 enabled 状态,
// 则将回调 OnKeyListener.onKey()。
if (li != null && li.mOnKeyListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
// 02. 如果在上一步仍然没有消费这个事件,将调用 KeyEvent.dispatch()
if (event.dispatch(this, mAttachInfo != null
? mAttachInfo.mKeyDispatchState : null, this)) {
return true;
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
}
在 View.dispatchKeyEvent() 中主要执行以下操作:
- 如果当前 View 处于 enabled 状态,并且对其设置了 OnKeyListener,则将回调 OnKeyListener.onKey()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
- 调用 KeyEvent.dispatch(),最终将调用 View 的 onKeyDown() 和 onKeyUp()(详见 View 的 onKeyDown() 和 onKeyUp())。如果此步消费了输入事件,将返回 true;否则,将返回 false。
1.2.2.7 KeyEvent.dispatch()
具体代码如下:
package android.view;
public class KeyEvent extends InputEvent implements Parcelable {
/**
* Deliver this key event to a {@link Callback} interface. If this is
* an ACTION_MULTIPLE event and it is not handled, then an attempt will
* be made to deliver a single normal event.
*
* @param receiver The Callback that will be given the event.
* @param state State information retained across events.
* @param target The target of the dispatch, for use in tracking.
*
* @return The return value from the Callback method that was called.
*/
public final boolean dispatch(Callback receiver, DispatcherState state,
Object target) {
switch (mAction) {
case ACTION_DOWN: {
// 清除 FLAG_START_TRACKING 标记
mFlags &= ~FLAG_START_TRACKING;
if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
+ ": " + this);
// 调用 Activity 或 View 的 onKeyDown()
boolean res = receiver.onKeyDown(mKeyCode, this);
if (state != null) {
// 因为需要存在 FLAG_START_TRACKING 标记
// 只能通过 KeyEvent.startTracking() 设置这个标记
// 当按下 back 键时,在 Activity 或 Dialog 的 onKeyDown() 中被调用
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 {
// 注意,不是在这里执行长按操作
// 源码中,Activity 和 Dialog 的 onKeyLongPress() 返回false
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);
}
// 调用 Activity 或 View 的 onKeyUp()
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;
}
}
对 KeyEvent.dispatch() 中的参数说明如下:
- receiver:在 “1.2.2 Activity.dispatchKeyEvent()” 中传入的对象为 Activity;在 “1.2.6 View.dispatchKeyEvent()” 中传入的对象为 View。
- state:该对象用来进行高级别的按键事件处理,一般情况下不为空。不用太关注这个。
- target:和参数 receiver 一样,是 Activity 或 View 对象。
如果是 ACTION_DOWN,将调用 Activity 或 View 的 onKeyDown();如果是 ACTION_UP,将调用 Activity 或 View 的 onKeyUp()。如果没有消费输入事件,将返回 false。
1.2.2.8 onKeyDown() 和 onKeyUp()
(1)View 的 onKeyDown() 和 onKeyUp()
View.onKeyDown() 具体代码如下:
package android.view;
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 01. 判断当前是否为可触发点击的按键
if (KeyEvent.isConfirmKey(keyCode)) {
// 02. 如果当前 View 为 disabled 状态,直接返回 true
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
if (event.getRepeatCount() == 0) {
// Long clickable items don't necessarily have to be clickable.
// 是否为 可点击或长按 的
final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
|| (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
// 03. 判断是否可点击 或 可 Tooltip 的
if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
// For the purposes of menu anchoring and drawable hotspots,
// key events are considered to be at the center of the view.
final float x = getWidth() / 2f;
final float y = getHeight() / 2f;
if (clickable) {
// 将该 View 设置为 PRESSED 状态
setPressed(true, x, y);
}
// 默认 500ms 后,启动一个 Runnable 来执行长按操作。
// 如果 500ms 内,放开按键,将取消这个 Runnable
checkForLongClick(0, x, y);
return true;
}
}
}
return false;
}
}
在 View.onKeyDown() 中主要执行以下操作:
- 判断当前是否为可触发点击的按键,如果不是,直接返回 false;否则,继续往下执行。
- 判断当前 View 是否为 disabled 状态,如果是,直接返回 true;否则,继续往下执行。
- 判断是否可点击 或 可 Tooltip 的,如果不是,直接返回 false;否则,继续往下执行。
- 如果是可点击的,调用 View.setPressed() 将该 View 设置为 pressed 状态,同时显示按压效果。然后,启动一个延迟 500ms 执行的 Runnable,用于执行长按操作。最后,返回 true。
View.onKeyUp() 具体代码如下:
package android.view;
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean onKeyUp(int keyCode, KeyEvent event) {
// 01. 判断当前是否为可触发点击的按键
if (KeyEvent.isConfirmKey(keyCode)) {
// 02. 如果当前 View 为 disabled 状态,直接返回 true
if ((mViewFlags & ENABLED_MASK) == DISABLED) {
return true;
}
// 03. 判断当前 View 是否可点击的,并且处于 pressed 状态
if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
// 04. 去除 View 的 pressed 状态以及显示效果
setPressed(false);
// 05. 判断是否已经执行长按操作
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
// 移除执行长按操作的 Runnable,其在 View.onKeyDown() 中延迟 500ms 启动
removeLongPressCallback();
if (!event.isCanceled()) {
// 执行点击操作
return performClickInternal();
}
}
}
}
return false;
}
}
在 View.onKeyUp() 中主要执行以下操作:
- 判断当前是否为可触发点击的按键,如果不是,直接返回 false;否则,继续往下执行。
- 判断当前 View 是否为 disabled 状态,如果是,直接返回 true;否则,继续往下执行。
- 判断当前 View 是否可点击的,并且处于 pressed 状态,如果不是,直接返回 false;否则,继续往下执行。
- 去除 View 的 pressed 状态以及显示效果。
- 判断是否已经执行长按操作,如果是,直接返回 false;否则,继续往下执行。
- 移除执行长按操作的 Runnable,并调用 performClickInternal() 执行点击操作,并返回执行结果。
(2)Activity 的 onKeyDown() 和 onKeyUp()
具体代码如下:
package android.app;
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
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;
}
}
如果是 Back 键,在 Activity.onKeyUp() 中将将调用 onBackPressed(),其默认会 finish 当前 Activity。不过自定义的 Activity 可以重载它,进行自定义操作。
(3)PhoneWindow 的 onKeyDown() 和 onKeyUp()
PhoneWindow 的 onKeyDown() 和 onKeyUp() 主要用于当前获得焦点的窗口对一些特殊按键进行处理,比如音量加减键,进行音量调节等。
1.3 InputStage 介绍
1.3.1 InputStage 介绍
InputStage 是一个 abstract 类,它存在一系列子类。系统会将所有 InputEvent(包括 TouchEvent 和 KeyEvent) 交给这些 InputState 子类对象进行处理,这个处理过程采用了 责任链模式。
其子类具体的作用如下:
- NativePreImeInputStage:分发早于 IME 的 InputEvent 事件到 NativeActivity 中去处理,NativeActivity和普通 Acitivty 的功能区别不大,只是很多代码都在 native 层去实现,这样执行效率更高,并且NativeActivity 在游戏开发中很实用。 不支持触摸事件。
- ViewPreImeInputStage:分发早于 IME 的 InputEvent 到 View 框架处理,会调用 Acitivity 的所有 View 的onkeyPreIme() 方法,这样就给 View 在输入法处理 Key 事件之前先得到消息并处理的机会。 不支持触摸事件。
- ImeInputStage:分发 InputEvent 到 IME 处理。ImeInputStage 的 onProcess() 方法会调用InputMethodManager 的 dispatchInputEvent() 方法处理消息。
- EarlyPostImeInputStage:输入法之后输入事件就会流到该阶段,此时屏幕上有焦点的 View 会高亮显示,用来提示用户焦点所在。支持触摸事件。
- NativePostImeInputStage:分发 InputEvent 事件到 NativeActivity,为了让 IME 处理完消息后能先于普通的 Activity 处理消息。支持触摸事件。
- ViewPostImeInputStage:分发 InputEvent 事件到 View 框架。支持触摸事件。
- SyntheticInputStage:未处理的 InputEvent 都会传到这个阶段,例如手机上的虚拟按键消息。
按照责任链顺序传递 InputEvent 过程中,每个阶段都可以消费输入事件,从而拦截事件。
1.3.2 创建责任链
下面我们来看一下具体的责任链,其在 ViewRootImpl.setView() 中进行创建,具体代码如下:
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
public void setView(View view,
WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage
= new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(
viewPostImeStage, "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage
= new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(
viewPreImeStage, "aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName
= "aq:pending:" + counterSuffix;
}
}
}
}
从 ViewRootImpl.setView() 的代码可知:
- 变量 mFirstInputStage 的责任链:NativePreImeInputStage -> ViewPreImeInputStage -> ImeInputStage -> EarlyPostImeInputStage -> NativePostImeInputStage -> ViewPostImeInputStage -> SyntheticInputStage。
- 变量 mFirstPostImeInputStage 的责任链:EarlyPostImeInputStage -> NativePostImeInputStage -> ViewPostImeInputStage -> SyntheticInputStage。
1.3.3 EarlyPostImeInputStage
package android.view;
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
/**
* Performs early processing of post-ime input events.
*/
final class EarlyPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// 按键事件,将调用 processKeyEvent()
return processKeyEvent(q);
} else {
...
}
return FORWARD;
}
private int processKeyEvent(QueuedInputEvent q) {
final KeyEvent event = (KeyEvent)q.mEvent;
if (mAttachInfo.mTooltipHost != null) {
mAttachInfo.mTooltipHost.handleTooltipKey(event);
}
// If the key's purpose is to exit touch mode then we consume it
// and consider it handled.
// 从触摸模式进入按键模式
if (checkForLeavingTouchModeAndConsume(event)) {
return FINISH_HANDLED;
}
// Make sure the fallback event policy sees all keys that will be
// delivered to the view hierarchy.
mFallbackEventHandler.preDispatchKeyEvent(event);
return FORWARD;
}
}
}
在 EarlyPostImeInputStage.processKeyEvent() 中将通过调用 ViewRootImpl.checkForLeavingTouchModeAndConsume() 从触摸模式进入按键模式。
1.4 总结
经过上面的代码分析,我们已经大致能够了解在 Java 层按键事件(KeyEvent)的处理流程了。进行总结如下:(大体上按照执行顺序进行描述,因为任一阶段都有可能消费输入事件,所以并不是所有操作都会执行到。)
- 从触摸模式进入按键模式:在 EarlyPostImeInputStage.processKeyEvent() 将通过调用 ViewRootImpl.checkForLeavingTouchModeAndConsume() 实现。
- 分开处理 MotionEvent 和 KeyEvent:在 ViewPostImeInputStage.onProcess() 中开始分开处理。
- 按 Back 键退出 Action Mode:
- 回调 OnKeyListener.onKey():在 View.dispatchKeyEvent() 中回调 OnKeyListener.onKey()。可以通过 View.setOnKeyListener() 设置 OnKeyListener,从而实现自定义操作。
- 显示和去除 View 的 pressed 效果:在 View.onKeyDown() 和 View.onKeyUp() 中调用 setPressed() 实现。
- 执行长按操作(即回调 OnLongClickListener.onLongClick()):按下可触发点击的按键时,在 View.onKeyDown() 中将调用 View.checkForLongClick() 来启动一个默认延时 500ms 执行的 Runnable 对象(CheckForLongPress),最终将会回调 OnLongClickListener.onLongClick()。
- 播放点击操作音效:松开可触发点击的按键时,在 View.onKeyUp() 中将调用 View.performClickInternal(),一步步往下执行,最终将在 View.performClick() 中调用 View.playSoundEffect()。
- 执行点击操作(即回调 OnClickListener.onClick()):松开可触发点击的按键时,在 View.onKeyUp() 中将调用 View.performClickInternal(),一步步往下执行,最终将在 View.performClick() 中回调 OnClickListener.onClick()。
- 按返回键 finish 当前 Activity:在 Activity 的 onKeyDown() 和 onKeyUp() 中处理。不过自定义 Activity 可以重载 onBackPressed() 来实现自定义操作。
- 音量加减键调节音量处理:在 PhoneWindow 的 onKeyDown() 和 onKeyUp() 中处理。
- 按导航键变更 Focus:在 ViewPostImeInputStage.processKeyEvent() 中调用 performFocusNavigation() 进行处理。
参考:
[1] Android8.0 按键事件处理流程
[2] Android按键事件传递流程(二)
附录:
如果您对 native 层的输入事件的分发过程有兴趣,可以参照以下链接:
[1] Android按键事件传递流程(一)
[2] Android按键事件传递流程(二)