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

目录


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

扫描二维码关注公众号,回复: 14690540 查看本文章
  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按键事件传递流程(二)

猜你喜欢

转载自blog.csdn.net/benzhujie1245com/article/details/89559900
今日推荐