前言
此篇博客会讲解基于Android10.0系统的按键事件(KeyEvent)分发流程,按键事件包括了设备物理按钮、遥控器、输入法、USB-OTG外接键盘等等。请注意!屏幕上的触控事件不属于按键事件。另外此篇博客不涉及Linux层。
大致架构流程
在说详解源代码的执行流程前,我们先用最大致的了解下按键事件的流程与设计抽象思维。
首先要搞清楚,按键事件(KeyEvent)有谁消费。按键事件的消费者其实分2大块,一个是framework层的系统操作消费,一个是View的视图操作消费。
framework层的系统操作消费一般有:
- power键
- 音量键
- 导航键(back、home等等)
- 定制设备的物理键(比如音乐播放/暂停键,快进,倒退,下一步)
- 外接键盘上的一些按键
View的视图操作消费一般有:
- 上下左右方向键(包含遥控器与键盘),用来TV设备上切换焦点
- 回车键
- 外接键盘与输入法等等相关其他按键,字母与数字键等等
它们都有对应的关键处理类,framework层的系统操作消费对应着PhoneWindowManager(属于系统进程),View的视图操作消费对应着ViewRootImpl(属于应用进程)。
或许还是有一些人不明白,为什么要分2部分来处理按键事件呢?原因其实很简单,这里一一解释:
1.应用与系统的进程不一样,无法将两者结合在一起。 进程隔离是Android系统基础中的基础。
2.系统是需要消费按键实现一些系统功能的,比如音量调节、屏幕亮灭、power+音量键截图等等。
3.在应用层上,按键功能是需要传递到应用的UI层进行逻辑处理的。比如方向键与回车键,方向键其实是执行了焦点选择的事件消费,而回车键是执行了点击事件的代码onClickListener。这些都是属于应用层上的事件消费。
顺序流程
PhoneWindowManager与ViewRootImpl都消费按键事件,那肯定有一个先后顺序。在代码里PhoneWindowManager是先执行的ViewRootImpl是后执行的。但是有一部分人会认为PhoneWindowManager处理完后会把按键事件转给ViewRootImpl。这是错误的,疏忽一个关键,它们不是一个进程无法直接传递信息,它们也不是互相之间创建了AIDL进行了通信。而是依靠Linux层分发传递了按键事件(反正PhoneWindowManager都是依靠Linux层传上来的,ViewRootImpl在依靠Linux层传上来按键事件也是合情合理的)。所以,Linux层、PhoneWindowManager、ViewRootImpl它们的三者关系如下流程图:
framework层的按键事件消费
这里插一句题外话,很多framework开发会在PhoneWindowManager的interceptKeyBeforeQueueing方法实现自定义的按键功能。比如自定义实现power键的额外功能,或者其他自定义键值的按键功能,也有在这里发送全局广播给应用层app监听后实现功能。这些操作没有问题,但是请额外注意这个变量的 int result; 的返回值,因为它关系到按键事件是否分发到应用层。
应用层的按键事件消费
ViewRootImpl内部的InputStage:
InputStage 用于实现责任链中某个阶段的基类,处理输入的责任链,在调用deliver时会遍历责任链传递事件。InputStage事件分发完成后会调用finishInputEvent,告知SystemServer进程的InputDispatcher线程,最终将该事件移除,完成此次事件的分发消费。
在ViewRootImpl的setView方法中,完成了InputStage的责任链组装,代码如下:
- SyntheticInputStage。综合处理事件阶段,比如处理导航面板、操作杆等事件。
- ViewPostImeInputStage。视图输入处理阶段,比如按键、手指触摸等运动事件,我们熟知的view事件分发就发生在这个阶段。
- NativePostImeInputStage。本地方法处理阶段,主要构建了可延迟的队列。
- EarlyPostImeInputStage。输入法早期处理阶段。
- ImeInputStage。输入法事件处理阶段,处理输入法字符。
- ViewPreImeInputStage。视图预处理输入法事件阶段,调用视图view的dispatchKeyEventPreIme方法。
- NativePreImeInputStage。本地方法预处理输入法事件阶段。
InputStage 的全部代码与继承它的实现类:
/** * Base class for implementing a stage in the chain of responsibility * for processing input events. * <p> * Events are delivered to the stage by the {@link #deliver} method. The stage * then has the choice of finishing the event or forwarding it to the next stage. * </p> */ abstract class InputStage { private final InputStage mNext; protected static final int FORWARD = 0; protected static final int FINISH_HANDLED = 1; protected static final int FINISH_NOT_HANDLED = 2; /** * Creates an input stage. * @param next The next stage to which events should be forwarded. */ public InputStage(InputStage next) { mNext = next; } /** * Delivers an event to be processed. */ public final void deliver(QueuedInputEvent q) { /// M: [ANR] Add for monitoring stage status. { ViewDebugManager.getInstance().debugInputStageDeliverd(this, System.currentTimeMillis()); /// } if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) { forward(q); } else if (shouldDropInputEvent(q)) { finish(q, false); } else { ViewDebugManager.getInstance().debugInputDispatchState(q.mEvent, this.toString()); apply(q, onProcess(q)); } } /** * Marks the the input event as finished then forwards it to the next stage. */ protected void finish(QueuedInputEvent q, boolean handled) { q.mFlags |= QueuedInputEvent.FLAG_FINISHED; if (handled) { q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED; } forward(q); } /** * Forwards the event to the next stage. */ protected void forward(QueuedInputEvent q) { onDeliverToNext(q); } /** * Applies a result code from {@link #onProcess} to the specified event. */ protected void apply(QueuedInputEvent q, int result) { if (result == FORWARD) { forward(q); } else if (result == FINISH_HANDLED) { finish(q, true); } else if (result == FINISH_NOT_HANDLED) { finish(q, false); } else { throw new IllegalArgumentException("Invalid result: " + result); } } /** * Called when an event is ready to be processed. * @return A result code indicating how the event was handled. */ protected int onProcess(QueuedInputEvent q) { return FORWARD; } /** * Called when an event is being delivered to the next stage. */ protected void onDeliverToNext(QueuedInputEvent q) { if (DEBUG_INPUT_STAGES) { Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q); } if (mNext != null) { mNext.deliver(q); } else { finishInputEvent(q); } } protected void onWindowFocusChanged(boolean hasWindowFocus) { if (mNext != null) { mNext.onWindowFocusChanged(hasWindowFocus); } } protected void onDetachedFromWindow() { if (mNext != null) { mNext.onDetachedFromWindow(); } } protected boolean shouldDropInputEvent(QueuedInputEvent q) { if (mView == null || !mAdded) { Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent); return true; } else if ((!mAttachInfo.mHasWindowFocus && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER) && !isAutofillUiShowing()) || mStopped || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON)) || (mPausedForTransition && !isBack(q.mEvent))) { // This is a focus event and the window doesn't currently have input focus or // has stopped. This could be an event that came back from the previous stage // but the window has lost focus or stopped in the meantime. if (isTerminalInputEvent(q.mEvent)) { // Don't drop terminal input events, however mark them as canceled. q.mEvent.cancel(); Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent); return false; } // Drop non-terminal input events. Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent); return true; } return false; } void dump(String prefix, PrintWriter writer) { if (mNext != null) { mNext.dump(prefix, writer); } } private boolean isBack(InputEvent event) { if (event instanceof KeyEvent) { return ((KeyEvent) event).getKeyCode() == KeyEvent.KEYCODE_BACK; } else { return false; } } } /** * Base class for implementing an input pipeline stage that supports * asynchronous and out-of-order processing of input events. * <p> * In addition to what a normal input stage can do, an asynchronous * input stage may also defer an input event that has been delivered to it * and finish or forward it later. * </p> */ abstract class AsyncInputStage extends InputStage { private final String mTraceCounter; private QueuedInputEvent mQueueHead; private QueuedInputEvent mQueueTail; private int mQueueLength; protected static final int DEFER = 3; /** * Creates an asynchronous input stage. * @param next The next stage to which events should be forwarded. * @param traceCounter The name of a counter to record the size of * the queue of pending events. */ public AsyncInputStage(InputStage next, String traceCounter) { super(next); mTraceCounter = traceCounter; } /** * Marks the event as deferred, which is to say that it will be handled * asynchronously. The caller is responsible for calling {@link #forward} * or {@link #finish} later when it is done handling the event. */ protected void defer(QueuedInputEvent q) { q.mFlags |= QueuedInputEvent.FLAG_DEFERRED; enqueue(q); } @Override protected void forward(QueuedInputEvent q) { // Clear the deferred flag. q.mFlags &= ~QueuedInputEvent.FLAG_DEFERRED; // Fast path if the queue is empty. QueuedInputEvent curr = mQueueHead; if (curr == null) { super.forward(q); return; } // Determine whether the event must be serialized behind any others // before it can be delivered to the next stage. This is done because // deferred events might be handled out of order by the stage. final int deviceId = q.mEvent.getDeviceId(); QueuedInputEvent prev = null; boolean blocked = false; while (curr != null && curr != q) { if (!blocked && deviceId == curr.mEvent.getDeviceId()) { blocked = true; } prev = curr; curr = curr.mNext; } // If the event is blocked, then leave it in the queue to be delivered later. // Note that the event might not yet be in the queue if it was not previously // deferred so we will enqueue it if needed. if (blocked) { if (curr == null) { enqueue(q); } return; } // The event is not blocked. Deliver it immediately. if (curr != null) { curr = curr.mNext; dequeue(q, prev); } super.forward(q); // Dequeuing this event may have unblocked successors. Deliver them. while (curr != null) { if (deviceId == curr.mEvent.getDeviceId()) { if ((curr.mFlags & QueuedInputEvent.FLAG_DEFERRED) != 0) { break; } QueuedInputEvent next = curr.mNext; dequeue(curr, prev); super.forward(curr); curr = next; } else { prev = curr; curr = curr.mNext; } } } @Override protected void apply(QueuedInputEvent q, int result) { if (result == DEFER) { defer(q); } else { super.apply(q, result); } } private void enqueue(QueuedInputEvent q) { if (mQueueTail == null) { mQueueHead = q; mQueueTail = q; } else { mQueueTail.mNext = q; mQueueTail = q; } mQueueLength += 1; Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength); } private void dequeue(QueuedInputEvent q, QueuedInputEvent prev) { if (prev == null) { mQueueHead = q.mNext; } else { prev.mNext = q.mNext; } if (mQueueTail == q) { mQueueTail = prev; } q.mNext = null; mQueueLength -= 1; Trace.traceCounter(Trace.TRACE_TAG_INPUT, mTraceCounter, mQueueLength); } @Override void dump(String prefix, PrintWriter writer) { writer.print(prefix); writer.print(getClass().getName()); writer.print(": mQueueLength="); writer.println(mQueueLength); super.dump(prefix, writer); } } /** * Delivers pre-ime input events to a native activity. * Does not support pointer events. */ final class NativePreImeInputStage extends AsyncInputStage implements InputQueue.FinishedInputEventCallback { public NativePreImeInputStage(InputStage next, String traceCounter) { super(next, traceCounter); } @Override protected int onProcess(QueuedInputEvent q) { if (mInputQueue != null && q.mEvent instanceof KeyEvent) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); return DEFER; } return FORWARD; } @Override public void onFinishedInputEvent(Object token, boolean handled) { QueuedInputEvent q = (QueuedInputEvent)token; if (handled) { finish(q, true); return; } forward(q); } } /** * Delivers pre-ime input events to the view hierarchy. * Does not support pointer events. */ final class ViewPreImeInputStage extends InputStage { public ViewPreImeInputStage(InputStage next) { super(next); } @Override protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } return FORWARD; } private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; if (mView.dispatchKeyEventPreIme(event)) { return FINISH_HANDLED; } return FORWARD; } } /** * Delivers input events to the ime. * Does not support pointer events. */ final class ImeInputStage extends AsyncInputStage implements InputMethodManager.FinishedInputEventCallback { public ImeInputStage(InputStage next, String traceCounter) { super(next, traceCounter); } @Override protected int onProcess(QueuedInputEvent q) { if (mLastWasImTarget && !isInLocalFocusMode()) { InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); if (imm != null) { final InputEvent event = q.mEvent; if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event); int result = imm.dispatchInputEvent(event, q, this, mHandler); if (result == InputMethodManager.DISPATCH_HANDLED) { return FINISH_HANDLED; } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) { // The IME could not handle it, so skip along to the next InputStage return FORWARD; } else { return DEFER; // callback will be invoked later } } } return FORWARD; } @Override public void onFinishedInputEvent(Object token, boolean handled) { QueuedInputEvent q = (QueuedInputEvent)token; if (DEBUG_IMF || ViewDebugManager.DEBUG_INPUT || ViewDebugManager.DEBUG_KEY) { Log.d(mTag, "IME finishedEvent: handled = " + handled + ", event = " + q + ", viewAncestor = " + this); } if (handled) { finish(q, true); return; } forward(q); } } /** * Performs early processing of post-ime input events. */ final class EarlyPostImeInputStage extends InputStage { public EarlyPostImeInputStage(InputStage next) { super(next); } @Override protected int onProcess(QueuedInputEvent q) { if (q.mEvent instanceof KeyEvent) { return processKeyEvent(q); } else if (q.mEvent instanceof MotionEvent) { return processMotionEvent(q); } 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; } private int processMotionEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent) q.mEvent; if (event.isFromSource(InputDevice.SOURCE_CLASS_POINTER)) { return processPointerEvent(q); } // If the motion event is from an absolute position device, exit touch mode final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) { if (event.isFromSource(InputDevice.SOURCE_CLASS_POSITION)) { ensureTouchMode(false); } } return FORWARD; } private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; // Translate the pointer event for compatibility, if needed. if (mTranslator != null) { mTranslator.translateEventInScreenToAppWindow(event); } // Enter touch mode on down or scroll, if it is coming from a touch screen device, // exit otherwise. final int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) { ensureTouchMode(event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)); } if (action == MotionEvent.ACTION_DOWN) { // Upon motion event within app window, close autofill ui. AutofillManager afm = getAutofillManager(); if (afm != null) { afm.requestHideFillUi(); } } if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) { mAttachInfo.mTooltipHost.hideTooltip(); } // Offset the scroll position. if (mCurScrollY != 0) { event.offsetLocation(0, mCurScrollY); } // Remember the touch position for possible drag-initiation. if (event.isTouchEvent()) { mLastTouchPoint.x = event.getRawX(); mLastTouchPoint.y = event.getRawY(); mLastTouchSource = event.getSource(); } return FORWARD; } } /** * Delivers post-ime input events to a native activity. */ final class NativePostImeInputStage extends AsyncInputStage implements InputQueue.FinishedInputEventCallback { public NativePostImeInputStage(InputStage next, String traceCounter) { super(next, traceCounter); } @Override protected int onProcess(QueuedInputEvent q) { if (mInputQueue != null) { mInputQueue.sendInputEvent(q.mEvent, q, false, this); return DEFER; } return FORWARD; } @Override public void onFinishedInputEvent(Object token, boolean handled) { QueuedInputEvent q = (QueuedInputEvent)token; if (handled) { finish(q, true); return; } forward(q); } } /** * Delivers post-ime input events to the view hierarchy. */ final class ViewPostImeInputStage extends InputStage { public ViewPostImeInputStage(InputStage next) { super(next); } @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); } } } @Override protected void onDeliverToNext(QueuedInputEvent q) { if (mUnbufferedInputDispatch && q.mEvent instanceof MotionEvent && ((MotionEvent)q.mEvent).isTouchEvent() && isTerminalInputEvent(q.mEvent)) { mUnbufferedInputDispatch = false; scheduleConsumeBatchedInput(); } super.onDeliverToNext(q); } private boolean performFocusNavigation(KeyEvent event) { int direction = 0; switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: if (event.hasNoModifiers()) { direction = View.FOCUS_LEFT; } break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (event.hasNoModifiers()) { direction = View.FOCUS_RIGHT; } break; case KeyEvent.KEYCODE_DPAD_UP: if (event.hasNoModifiers()) { direction = View.FOCUS_UP; } break; case KeyEvent.KEYCODE_DPAD_DOWN: if (event.hasNoModifiers()) { direction = View.FOCUS_DOWN; } break; case KeyEvent.KEYCODE_TAB: if (event.hasNoModifiers()) { direction = View.FOCUS_FORWARD; } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) { direction = View.FOCUS_BACKWARD; } break; } if (direction != 0) { View focused = mView.findFocus(); if (focused != null) { View v = focused.focusSearch(direction); if (v != null && v != focused) { // do the math the get the interesting rect // of previous focused into the coord system of // newly focused view focused.getFocusedRect(mTempRect); if (mView instanceof ViewGroup) { ((ViewGroup) mView).offsetDescendantRectToMyCoords( focused, mTempRect); ((ViewGroup) mView).offsetRectIntoDescendantCoords( v, mTempRect); } if (v.requestFocus(direction, mTempRect)) { playSoundEffect(SoundEffectConstants .getContantForFocusDirection(direction)); return true; } } // Give the focused view a last chance to handle the dpad key. if (mView.dispatchUnhandledMove(focused, direction)) { return true; } } else { if (mView.restoreDefaultFocus()) { return true; } } } return false; } private boolean performKeyboardGroupNavigation(int direction) { final View focused = mView.findFocus(); if (focused == null && mView.restoreDefaultFocus()) { return true; } View cluster = focused == null ? keyboardNavigationClusterSearch(null, direction) : focused.keyboardNavigationClusterSearch(null, direction); // Since requestFocus only takes "real" focus directions (and therefore also // restoreFocusInCluster), convert forward/backward focus into FOCUS_DOWN. int realDirection = direction; if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) { realDirection = View.FOCUS_DOWN; } if (cluster != null && cluster.isRootNamespace()) { // the default cluster. Try to find a non-clustered view to focus. if (cluster.restoreFocusNotInCluster()) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } // otherwise skip to next actual cluster cluster = keyboardNavigationClusterSearch(null, direction); } if (cluster != null && cluster.restoreFocusInCluster(realDirection)) { playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); return true; } return false; } private int processKeyEvent(QueuedInputEvent q) { final KeyEvent event = (KeyEvent)q.mEvent; if (mUnhandledKeyManager.preViewDispatch(event)) { if (ViewDebugManager.DEBUG_ENG) { Log.v(mTag, "App handle dispatchUnique event = " + event + ", mView = " + mView + ", this = " + this); } return FINISH_HANDLED; } // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { if (ViewDebugManager.DEBUG_ENG) { Log.v(mTag, "App handle key event: event = " + event + ", mView = " + mView + ", this = " + this); } 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; 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. 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. if (event.getAction() == KeyEvent.ACTION_DOWN) { if (groupNavigationDirection != 0) { if (performKeyboardGroupNavigation(groupNavigationDirection)) { return FINISH_HANDLED; } } else { if (performFocusNavigation(event)) { return FINISH_HANDLED; } } } return FORWARD; } private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; boolean handled = mView.dispatchPointerEvent(event); if (handled && ViewDebugManager.DEBUG_ENG) { Log.v(mTag, "App handle pointer event: event = " + event + ", mView = " + mView + ", this = " + this); } maybeUpdatePointerIcon(event); maybeUpdateTooltip(event); mAttachInfo.mHandlingPointerEvent = false; if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { mUnbufferedInputDispatch = true; if (mConsumeBatchedInputScheduled) { scheduleConsumeBatchedInputImmediately(); } } return handled ? FINISH_HANDLED : FORWARD; } private void maybeUpdatePointerIcon(MotionEvent event) { if (event.getPointerCount() == 1 && event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER || event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) { // Other apps or the window manager may change the icon type outside of // this app, therefore the icon type has to be reset on enter/exit event. mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED; } if (event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) { if (!updatePointerIcon(event) && event.getActionMasked() == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED; } } } } private int processTrackballEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; if (event.isFromSource(InputDevice.SOURCE_MOUSE_RELATIVE)) { if (!hasPointerCapture() || mView.dispatchCapturedPointerEvent(event)) { return FINISH_HANDLED; } } if (mView.dispatchTrackballEvent(event)) { return FINISH_HANDLED; } return FORWARD; } private int processGenericMotionEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; if (event.isFromSource(InputDevice.SOURCE_TOUCHPAD)) { if (hasPointerCapture() && mView.dispatchCapturedPointerEvent(event)) { return FINISH_HANDLED; } } // Deliver the event to the view. if (mView.dispatchGenericMotionEvent(event)) { return FINISH_HANDLED; } return FORWARD; } } private void resetPointerIcon(MotionEvent event) { mPointerIconType = PointerIcon.TYPE_NOT_SPECIFIED; updatePointerIcon(event); } private boolean updatePointerIcon(MotionEvent event) { final int pointerIndex = 0; final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); if (mView == null) { // E.g. click outside a popup to dismiss it Slog.d(mTag, "updatePointerIcon called after view was removed"); return false; } if (x < 0 || x >= mView.getWidth() || y < 0 || y >= mView.getHeight()) { // E.g. when moving window divider with mouse Slog.d(mTag, "updatePointerIcon called with position out of bounds"); return false; } final PointerIcon pointerIcon = mView.onResolvePointerIcon(event, pointerIndex); final int pointerType = (pointerIcon != null) ? pointerIcon.getType() : PointerIcon.TYPE_DEFAULT; if (mPointerIconType != pointerType) { mPointerIconType = pointerType; mCustomPointerIcon = null; if (mPointerIconType != PointerIcon.TYPE_CUSTOM) { InputManager.getInstance().setPointerIconType(pointerType); return true; } } if (mPointerIconType == PointerIcon.TYPE_CUSTOM && !pointerIcon.equals(mCustomPointerIcon)) { mCustomPointerIcon = pointerIcon; InputManager.getInstance().setCustomPointerIcon(mCustomPointerIcon); } return true; } private void maybeUpdateTooltip(MotionEvent event) { if (event.getPointerCount() != 1) { return; } final int action = event.getActionMasked(); if (action != MotionEvent.ACTION_HOVER_ENTER && action != MotionEvent.ACTION_HOVER_MOVE && action != MotionEvent.ACTION_HOVER_EXIT) { return; } AccessibilityManager manager = AccessibilityManager.getInstance(mContext); if (manager.isEnabled() && manager.isTouchExplorationEnabled()) { return; } if (mView == null) { Slog.d(mTag, "maybeUpdateTooltip called after view was removed"); return; } mView.dispatchTooltipHoverEvent(event); } /** * Performs synthesis of new input events from unhandled input events. */ final class SyntheticInputStage extends InputStage { private final SyntheticTrackballHandler mTrackball = new SyntheticTrackballHandler(); private final SyntheticJoystickHandler mJoystick = new SyntheticJoystickHandler(); private final SyntheticTouchNavigationHandler mTouchNavigation = new SyntheticTouchNavigationHandler(); private final SyntheticKeyboardHandler mKeyboard = new SyntheticKeyboardHandler(); public SyntheticInputStage() { super(null); } @Override protected int onProcess(QueuedInputEvent q) { q.mFlags |= QueuedInputEvent.FLAG_RESYNTHESIZED; if (q.mEvent instanceof MotionEvent) { final MotionEvent event = (MotionEvent)q.mEvent; final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { mTrackball.process(event); return FINISH_HANDLED; } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { mJoystick.process(event); return FINISH_HANDLED; } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { mTouchNavigation.process(event); return FINISH_HANDLED; } } else if ((q.mFlags & QueuedInputEvent.FLAG_UNHANDLED) != 0) { mKeyboard.process((KeyEvent)q.mEvent); return FINISH_HANDLED; } return FORWARD; } @Override protected void onDeliverToNext(QueuedInputEvent q) { if ((q.mFlags & QueuedInputEvent.FLAG_RESYNTHESIZED) == 0) { // Cancel related synthetic events if any prior stage has handled the event. if (q.mEvent instanceof MotionEvent) { final MotionEvent event = (MotionEvent)q.mEvent; final int source = event.getSource(); if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { mTrackball.cancel(); } else if ((source & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { mJoystick.cancel(); } else if ((source & InputDevice.SOURCE_TOUCH_NAVIGATION) == InputDevice.SOURCE_TOUCH_NAVIGATION) { mTouchNavigation.cancel(event); } } } super.onDeliverToNext(q); } @Override protected void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { mJoystick.cancel(); } } @Override protected void onDetachedFromWindow() { mJoystick.cancel(); } }
ViewRootImpl继续分发到DecorView
一部分事件会在各色InputStage的责任链中消费掉,如果在这些责任链中没有消费掉的事件会继续向上传递给DecorView,向DecorView传递的主要责任链是ViewPostImeInputStage。在ViewRootImpl中的mView就是DecorView,只要看到mView.dispatch这些关键字的代码基本上可以认为是在分发各种事件到DecorView,在ViewPostImeInputStage里分发的主要事件如下:
- mView.dispatchPointerEvent(event); 分发触控事件,在processPointerEvent方法里
- mView.dispatchKeyEvent(event) 分发按键事件,在processKeyEvent方法里
- mView.dispatchKeyShortcutEvent(event) 分发快捷键事件 ,在processKeyEvent方法里
- mView.dispatchCapturedPointerEvent(event) 分发轨迹球指针捕获事件(特别老式的轨迹球设备),在processTrackballEvent方法里
- mView.dispatchTrackballEvent(event) 分发轨迹球事件,在processTrackballEvent方法里
- mView.dispatchCapturedPointerEvent(event) 分发鼠标指针捕获事件,在processGenericMotionEvent方法里
- mView.dispatchGenericMotionEvent(event) 分发鼠标事件,在processGenericMotionEvent方法里
这里是继续向DecorView分发按键事件的代码片段
继续分发到View层
如下经过了DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup -> View.
另外插一句,可以举一反三其实触控事件分发也是这个顺序。
第一步,DecorView中的按键分发:
@Override public boolean dispatchKeyEvent(KeyEvent event) { final int keyCode = event.getKeyCode(); final int action = event.getAction(); final boolean isDown = action == KeyEvent.ACTION_DOWN; if (isDown && (event.getRepeatCount() == 0)) { // First handle chording of panel key: if a panel key is held // but not released, try to execute a shortcut in it. if ((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; } } } if (!mWindow.isDestroyed()) { //cb其实就是对应的Activity/Dialog,这里分发给了Activity final Window.Callback cb = mWindow.getCallback(); final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) : super.dispatchKeyEvent(event); if (handled) { return true; } } return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event); }
第二步,Activity的按键分发
/** * 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(); if (keyCode == KeyEvent.KEYCODE_MENU && mActionBar != null && mActionBar.onMenuKeyEvent(event)) { return true; } Window win = getWindow(); if (win.superDispatchKeyEvent(event)) { //这里分发给了PhoneWindow return true; } View decor = mDecor; if (decor == null) decor = win.getDecorView(); return event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this); }
第三步,PhoneWindow的按键分发
@Override public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); //这里继续分发给了DecorView }
第四步,DecorView的按键再分发,这里回头分发给DecorView,是为了DecorView与Activity的解耦,因为Activity只持有Window。 另外你可以看到上面Window的分发代码就是转个手而已
public boolean superDispatchKeyEvent(KeyEvent event) { // Give priority to closing action modes if applicable. if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { //如果是back键这里会直接消费掉 final int action = event.getAction(); // Back cancels action modes first. if (mPrimaryActionMode != null) { if (action == KeyEvent.ACTION_UP) { mPrimaryActionMode.finish(); } return true; } } if (super.dispatchKeyEvent(event)) { //这里继续分发给ViewGroup return true; } return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event); }
第五步,ViewGroup的按键分发
@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)) { if (super.dispatchKeyEvent(event)) { //这里会继续分发给View return true; } } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) { if (mFocused.dispatchKeyEvent(event)) { //这个是分发ViewGroup给有焦点的子View return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 1); } return false; }
第六步,View的按键分发,到终点了
public boolean dispatchKeyEvent(KeyEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } // Give any attached key listener a first crack at the event. //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { //这里已经回调到mOnKeyListener了,我们应用层就可以接收到按键键值了 return true; } if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) { return true; } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/16927533.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2021-11-26 Kotlin开发 协程网络请求的实践— Retrofit + 协程 + ViewModel