Android 按键事件(KeyEvent)的分发机制

本文深入解析Android中按键事件(KeyEvent)的处理流程,从按键事件传入DecorView开始,到DecorView向下分发按键事件的过程,包括责任链模式下各InputStage的作用与创建。文章还详细介绍了按键事件在View、Activity及PhoneWindow中的处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录


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)两种类型。这些事件流入到上层之后才会分别进行处理。

  本内容主要分为两大过程进行介绍:

  1. 按键事件(KeyEvent)传入 DecorView。
  2. DecorView 往下分发按键事件(KeyEvent)。

1.1 按键事件(KeyEvent)传入 DecorView

1.1.1 时序图
图-1 按键事件(KeyEvent)传入 DecorView 时序图

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() 中主要执行如下操作:

  1. 调用 obtainQueuedInputEvent() 函数把输入事件(InputEvent)封装为 QueuedInputEvent 对象。
  2. 把输入事件(即 QueuedInputEvent 对象)入队。
  3. 根据 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() 中,主要执行以下操作:

  1. 调用 DecorView.dispatchKeyEvent() 处理输入事件。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  2. 调用 DecorView.dispatchKeyShortcutEvent() 处理组合快捷键(modifier key + 其他键)。
  3. 处理 Focus 变更。例如,按下 “上下左右” 导航键时,需要变更 Focus。

  经过上面的代码流程分析,按键事件(KeyEvent)传入到了 DecorView 中

1.2 DecorView 往下分发按键事件(KeyEvent)

1.2.1 流程图
图-2 DecorView 分发按键事件(KeyEvent)流程图

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() 中主要执行以下操作:

  1. 处理快捷键。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  2. 获取到的 cb 实际上是一个 Activity 或 Dialog 对象,我们这里以 Activity 为例进行说明;并且 mFeatureId 的值为 -1,所以将调用 Activity.dispatchKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  3. 根据 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() 中主要执行以下操作:

  1. 如果是 menu 键,则将调用 ActionBar.onMenuKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。通常按 menu 键时,会启动 Recent App。因此,如果是 menu 键的话,一般不会传递到这里,就已经被消费了。所以这里一般情况下不会消费输入事件。
  2. 通过 getWindow() 获取到的实际上是一个 PhoneWindow 对象,因此将调用 PhoneWindow.superDispatchKeyEvent()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  3. 调用 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() 中主要执行以下操作:

  1. 如果是 Back 键,并且当前处于 Action Mode,将消费输入事件,并返回 true;否则,将继续往下执行。
  2. 调用父类的 dispatchKeyEvent(),其直接父类 FrameLayout 没有实现 dispatchKeyEvent() 方法,所以将调用 ViewGroup 的 dispatchKeyEvent() 方法。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  3. 调用 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() 中主要执行以下操作:

  1. 如果当前 View 处于 enabled 状态,并且对其设置了 OnKeyListener,则将回调 OnKeyListener.onKey()。如果此步消费了输入事件,将返回 true;否则,将继续往下执行。
  2. 调用 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() 中主要执行以下操作:

  1. 判断当前是否为可触发点击的按键,如果不是,直接返回 false;否则,继续往下执行。
  2. 判断当前 View 是否为 disabled 状态,如果是,直接返回 true;否则,继续往下执行。
  3. 判断是否可点击 或 可 Tooltip 的,如果不是,直接返回 false;否则,继续往下执行。
  4. 如果是可点击的,调用 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() 中主要执行以下操作:

  1. 判断当前是否为可触发点击的按键,如果不是,直接返回 false;否则,继续往下执行。
  2. 判断当前 View 是否为 disabled 状态,如果是,直接返回 true;否则,继续往下执行。
  3. 判断当前 View 是否可点击的,并且处于 pressed 状态,如果不是,直接返回 false;否则,继续往下执行。
  4. 去除 View 的 pressed 状态以及显示效果。
  5. 判断是否已经执行长按操作,如果是,直接返回 false;否则,继续往下执行。
  6. 移除执行长按操作的 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)的处理流程了。进行总结如下:(大体上按照执行顺序进行描述,因为任一阶段都有可能消费输入事件,所以并不是所有操作都会执行到。)

  1. 从触摸模式进入按键模式:在 EarlyPostImeInputStage.processKeyEvent() 将通过调用 ViewRootImpl.checkForLeavingTouchModeAndConsume() 实现。
  2. 分开处理 MotionEvent 和 KeyEvent:在 ViewPostImeInputStage.onProcess() 中开始分开处理。
  3. 按 Back 键退出 Action Mode
  4. 回调 OnKeyListener.onKey():在 View.dispatchKeyEvent() 中回调 OnKeyListener.onKey()。可以通过 View.setOnKeyListener() 设置 OnKeyListener,从而实现自定义操作。
  5. 显示和去除 View 的 pressed 效果:在 View.onKeyDown() 和 View.onKeyUp() 中调用 setPressed() 实现。
  6. 执行长按操作(即回调 OnLongClickListener.onLongClick()):按下可触发点击的按键时,在 View.onKeyDown() 中将调用 View.checkForLongClick() 来启动一个默认延时 500ms 执行的 Runnable 对象(CheckForLongPress),最终将会回调 OnLongClickListener.onLongClick()。
  7. 播放点击操作音效:松开可触发点击的按键时,在 View.onKeyUp() 中将调用 View.performClickInternal(),一步步往下执行,最终将在 View.performClick() 中调用 View.playSoundEffect()。
  8. 执行点击操作(即回调 OnClickListener.onClick()):松开可触发点击的按键时,在 View.onKeyUp() 中将调用 View.performClickInternal(),一步步往下执行,最终将在 View.performClick() 中回调 OnClickListener.onClick()。
  9. 按返回键 finish 当前 Activity:在 Activity 的 onKeyDown() 和 onKeyUp() 中处理。不过自定义 Activity 可以重载 onBackPressed() 来实现自定义操作。
  10. 音量加减键调节音量处理:在 PhoneWindow 的 onKeyDown() 和 onKeyUp() 中处理。
  11. 按导航键变更 Focus:在 ViewPostImeInputStage.processKeyEvent() 中调用 performFocusNavigation() 进行处理。

参考:

[1] Android8.0 按键事件处理流程
[2] Android按键事件传递流程(二)


附录:

  如果您对 native 层的输入事件的分发过程有兴趣,可以参照以下链接:

[1] Android按键事件传递流程(一)
[2] Android按键事件传递流程(二)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值