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

您所在的位置:网站首页 android事件分发流程的理解和认识是什么意思呀 Android 按键事件(KeyEvent)的分发机制

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

2024-07-15 00:31| 来源: 网络整理| 查看: 265

目录

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

调用 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 (KeyEventaStateHasModifiers( event.getMetaState(), KeyEvent.META_META_ON)) { groupNavigationDirection = View.FOCUS_FORWARD; } else if (KeyEventaStateHasModifiers(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 && !KeyEventaStateHasNoModifiers(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 流程图 图-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


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3