Android input 原理分析(六) _ input上层分发流程

 系列博文:

Android input 原理分析_总序

Android input 原理分析(一) _ input 启动

Android input 原理分析(二) _ EventHub

Android input 原理分析(三) _ scanCode与keyCode映射

Android input 原理分析(四) _ input 分发

Andorid input 原理分析(五) _ input 命令

Android input 原理分析(六) _ input 上层分发流程

Android input 原理分析(七)_ input ANR

源码基于:android R 

0. 前言

在 《原理分析(四)》 中我们得知input 的按键最终通信是通过InputChannel:

通过ViewRootImpl.setView,并通过IWindowSession 调用addToDisplayAsUser 通知WMS 进行最终的socket 创建和InputChannel pair 的基本初始化。InputChannel 的server 注册到了InputDispatcher 中,而InputChannell 的Client 留在了App 端,并且 WindowInputEventReceiver 对Client fd 进行监听:

ViewRootImpl.java->setView():mInputEventReceiver = new WindowInputEventReceiver(inputChannel,Looper.myLooper());

当Client 收到消息时会通过Looper 中的epoll 触发,并最终调用到Java 端WindowInputEventReceiver 中的回调函数onInputEvent。详细过程可以查看《原理分析(四)》 的5.3 节。

本文继续上一篇的流程剖析,重点总结Java 端KeyEvent 的分发、处理过程。

1. 以 onInputEvent为起点进行分发处理

frameworks\base\core\java\android\view\ViewRootImpl.java

        public void onInputEvent(InputEvent event) {...if (processedEvents != null) {...} else {enqueueInputEvent(event, this, 0, true);}}

---->

    void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); //创建QueuedInputEvent,后期分发以这个对象...if (processImmediately) {doProcessInputEvents(); //将QueuedInputEvent 插入队列mPendingInputEventTail后进行处理} else {scheduleProcessInputEvents();}}

---->

    void doProcessInputEvents() {...deliverInputEvent(q); //读取队列里的event 进行deliver...}

2. InputStage 及其deliver

    private void deliverInputEvent(QueuedInputEvent q) {...try {if (mInputEventConsistencyVerifier != null) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");try {mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}InputStage stage;if (q.shouldSendToSynthesizer()) {stage = mSyntheticInputStage;} else {stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;}if (q.mEvent instanceof KeyEvent) {try {mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}if (stage != null) {handleWindowFocusChanged();stage.deliver(q);} else {finishInputEvent(q);}} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}}

这里处理会调用InputStage 进行相应逻辑的deliver,InputStage 的初始化是在ViewRootImpl 中的setView:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {...// 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;...}

 首先这些InputStage 的父类都是InputStage;

另外,这些Stage 的构造参数都是上一个stage,而参数则标记为下一个需要处理的stage:

        public InputStage(InputStage next) {mNext = next;}

这样,这些stage 就形成了一个处理的顺序:

2.1 InputStage 类别

  • NativePreImeInputStage:主要是为了将消息放到NativeActivity中去处理,NativeActivity和普通Acitivty的功能区别不大,只是很多代码都在native层去实现,这样执行效率更高,并且NativeActivity在游戏开发中很实用;
  • ViewPreImeInputStage:从名字中就可得知,最后会调用Acitivity的所有view的onkeyPreIme方法,这样就给View在输入法处理key事件之前先得到消息并处理的机会;
  • ImeInputStage:ImeInputStage的onProcess方法会调用InputMethodManager的dispatchInputEvent方法处理消息。
  • EarlyPostImeInputStage:屏幕上有焦点的View会高亮显示,用来提示用户焦点所在;
  • NativePostImeInputStage:为了让IME处理完消息后能先于普通的Activity处理消息;
  • ViewPostImeInputStage:Acitivity和view处理各种消息;
  • SyntheticInputStage: 流水线的最后一级,经过层层过滤之后,到达这里的消息已经不多了,例如手机上的虚拟按键消息;

2.2 InputStage.deliver()

        public final void deliver(QueuedInputEvent q) {if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {forward(q); //已经处理,则往后处理} else if (shouldDropInputEvent(q)) {finish(q, false);  //若丢弃该次event,则finish} else {traceEvent(q, Trace.TRACE_TAG_VIEW);final int result;try {result = onProcess(q); //根据不同的处理,返回处理状态} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}apply(q, result); //针对result 确定是否继续或finish}}

注意,这里的deliver() 只是父类的函数,对于不同的stage 其对应的forward()、onPorcess()、apply() 都是不同的。

大致流程是在不同Stage 进行处理,然后通过forward 传递给下一stage,或通过onProcess 确认stage 处理的结果,并直接影响apply的处理方式。

例如,父类InputStage 中apply 如下:

        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);}}

而,在NativePreImeInputStage 中的apply:

        protected void apply(QueuedInputEvent q, int result) {if (result == DEFER) {defer(q);} else {super.apply(q, result);}}

综上,需要关心下每个stage 的onProcess 的分发处理,以及后期apply、forward 进行的延续。

在例如,对于NativePreImeInputStage 的onProcess:

        @Overrideprotected int onProcess(QueuedInputEvent q) {if (mInputQueue != null && q.mEvent instanceof KeyEvent) {mInputQueue.sendInputEvent(q.mEvent, q, true, this);return DEFER;}return FORWARD;}

如果此时event 为KeyEvent,则会进行sendInputEvent 处理,并返回DEFER,在apply 中如果返回DEFER 则调用defer()。

2.3 ViewPostImeInputStage.onProcess()

上面通过代码得知,stage 是串联起来的,通过不同的stage 处理,会返回不同的result,在apply 时决定是否finish 此次event。我们这里以ViewPostImeInputStage 为例。

        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);}}}

如果是KeyEvent,则调用processKeyEvent(),如果是点击的,会触发processPointerEvent(),如果是操纵球,则会触发processTrackballEvent()。

2.4 processKeyEvent()

这里以keyevent 为例进一步解析:

        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.if (mView.dispatchKeyEvent(event)) {return FINISH_HANDLED;}if (shouldDropInputEvent(q)) {return FINISH_NOT_HANDLED;}...return FORWARD;}

这个mView 比较重要,当Activity 或Dialog,mView 为DecorView,是所有view 的根;如果是Toast,mView 是id 为com.android.internal.R.id.message,这点在Toast.makeText方法中可以得出。

3. Activity  ViewGroup  View的event 处理

View 继承关系:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{...}
public class FrameLayout extends ViewGroup {...}
public abstract class ViewGroup extends View implements ViewParent, ViewManager{..}

3.1 DecorView.dispatchKeyEvent()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

    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 keyif ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {return true;}}}if (!mWindow.isDestroyed()) {//cb 是Activity或Dialog,若Activity时,会调用Activity 的dispatchKeyEventfinal 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);}

3.2 Activity 的dispatchKeyEvent

    public boolean dispatchKeyEvent(KeyEvent event) {onUserInteraction();// Let action bars open menus in response to the menu key prioritized over// the window handling itfinal int keyCode = event.getKeyCode();//如果按键是menu,则先调用Actionbar的onMenuKeyEvent 处理,如果返回没有处理则继续if (keyCode == KeyEvent.KEYCODE_MENU &&mActionBar != null && mActionBar.onMenuKeyEvent(event)) {return true;}Window win = getWindow();//调用PhoneWindow 的superDispatchKeyEvent,最终调用到DecorView的dispatchKeyEventif (win.superDispatchKeyEvent(event)) {return true;}View decor = mDecor;if (decor == null) decor = win.getDecorView();//如果PhoneWindow 分发后返回false,则交由KeyEvent 派发事件,调用Activity 的onKeyDown/Up方法return event.dispatch(this, decor != null? decor.getKeyDispatcherState() : null, this);}

这里主要有两个重要操作:

  • Window.superDispatchKeyEvent(),这里通过Window 调用ViewGroup 和View 中的按键处理;
  • event.dispatch(),这里是Window 中没有处理的按键,最终会回到Activity();

step1. superDispatchKeyEvent

frameworks\base\core\java\com\android\internal\policy\DecorView.java

    public boolean superDispatchKeyEvent(KeyEvent event) {...if (super.dispatchKeyEvent(event)) {return true;}return (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);}

这里的super 为ViewGroup,另外,如果ViewGroup 里面不处理,最终会丢给ViewRootImpl 处理。

step2. ViewGroup.dispatchKeyEvent

frameworks\base\core\java\android\view\ViewGroup.java

    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)) {return true;}} else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)== PFLAG_HAS_BOUNDS) {if (mFocused.dispatchKeyEvent(event)) {return true;}}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);}return false;}

无论是否获取焦点,都会交给View 处理dispatchKeyEvent。

mFocused 存在于每个ViewGroup,其标识了ViewGroup 的直接子View 是否拥有或包含焦点,通过mFocused.dispatchKeyEvent 即可递归调用找到最终获取焦点的View,然后再调用View 的dispatchKeyEvent()。

step3. View.dispatchKeyEvent

frameworks\base\core\java\android\view\View.java

    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//当对view 设置了onKeyListener,且该view 处于enable 状态,则调用onKeyListener 的onKey()ListenerInfo li = mListenerInfo;if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {return true;}//KeyEvent派发事件,receiver为view,会回调view 的onKeyDown/Upif (event.dispatch(this, mAttachInfo != null? mAttachInfo.mKeyDispatchState : null, this)) {return true;}if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);}return false;}

step4. KeyEvent.dispatch

frameworks\base\core\java\android\view\KeyEvent.java

   //参数是从view 传进来,receiver 就是view / activitypublic final boolean dispatch(Callback receiver, DispatcherState state,Object target) {switch (mAction) {case ACTION_DOWN: {mFlags &= ~FLAG_START_TRACKING;boolean res = receiver.onKeyDown(mKeyCode, this);if (state != null) {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 {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 (state != null) {state.handleUpEvent(this);}return receiver.onKeyUp(mKeyCode, this);case ACTION_MULTIPLE:...}return false;}

注意该函数的参数中receiver,有可能按照当前的流程从View 调下来,此时的receiver 为View。

当View 没有处理是,就会一直往上一层抛,这就回到了Activity.dispatchKeyEvent(),最终会在Activity 中调用keyevent.dispatch,还是会回到该函数,但是此时的receiver 就为Activity 了。

---->

    public boolean onKeyUp(int keyCode, KeyEvent event) {if (KeyEvent.isConfirmKey(keyCode)) {if ((mViewFlags & ENABLED_MASK) == DISABLED) {return true;}if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {setPressed(false);if (!mHasPerformedLongPress) {// This is a tap, so remove the longpress checkremoveLongPressCallback();if (!event.isCanceled()) {return performClickInternal();}}}}return false;}

onKeyUp() 中会触发view 的click 操作,即performClickInternal():

    private boolean performClickInternal() {...return performClick();}

---->

    public boolean performClick() {...final ListenerInfo li = mListenerInfo;if (li != null && li.mOnClickListener != null) {playSoundEffect(SoundEffectConstants.CLICK);li.mOnClickListener.onClick(this);result = true;} else {result = false;}...}

这里就调用到了View 的onClick(),只要view 中setOnClickListener(),就会在onKeyUp 时调用到。

同理,在onTouchEvent() 也会调用到PerformClick(),最终也会调用到view 的onClick()。

step5. 回到Activity 中的event.dispatch()

在3.2 节最开始已经说明,本节主要有两个重要操作:

  • Window.superDispatchKeyEvent(),这里通过Window 调用ViewGroup 和View 中的按键处理;
  • event.dispatch(),这里是Window 中没有处理的按键,最终会回到Activity();

上面 step1~step4 主要分析了Window 中的处理,下面继续分析Window 没有处理的按键,Activity 是接着如何处理的。

event.dispatch() 在step4 中已经简单分析过,只不过receiver 从view 变成了Activity,而onKeyDown/Up 则也就变成调用Activity 中的处理函数。

Activity 中的onKeyDown/Up 暂时不做解析,看源码吧。App 中的Activity 也可以重写这两个函数进行特殊处理。

onKeyDown() 太长了,onKeyUp() 贴一下留个印象。

    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;}

3.3 PhoneWindow.onKeyDown()

Activity 的dispatchKeyEvent() 基本在3.2 节分析完了,重新回到3.1 节代码的最后。如果Activity 中并没有处理event,最终则会留到最后:

frameworks\base\core\java\com\android\internal\policy\DecorView.java

    public boolean dispatchKeyEvent(KeyEvent event) {...return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event): mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);}

这里就不具体列举代码了,主要是对一些特殊的按键,例如KEYCODE_VOLUME_UP、KEYCODE_VOLUME_DOWN、KEYCODE_MEDIA_PLAY 等。

4. finishInputEvent

回到2.2 节,当stage.deliver() 完成时会返回一个状态(FORWARD、FINISH_HANDLED、FINSH_NOT_HANDLED),stage 的流程如下:

如果前面stage 截获了event,那么返回值为FINISH_HANDLED,如果前面的stage 都没有处理event,最终会一直持续到 SyntheticInputStage,而该 stage 处理的结果只有两个:

        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;}

如代码所示,该stage 的处理只会有两个结果,FORWARD 和 FINISH_HANDLED,最终来看下其 apply 处理(继承的父类 InputStage):

        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) {...} else {throw new IllegalArgumentException("Invalid result: " + result);}}

针对这两种状态,走了forward() 和finish()。

  • 如果是forward(),SyntheticInputStage 最终会调用到父类的onDeliverToNext(),而此时作为最后的stage,next 为null,最终调用的是finishInputEvent();
  • 如果是finsh(q, true),最终如下代码,会设定QueuedInputEvent.mFlags 为FLAG_FINISHED 和 FLAG_FINISHED_HANDLED;
        protected void finish(QueuedInputEvent q, boolean handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED;if (handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;}forward(q);}

这里最终还是回到了forward()。

下面来正式看下 finishInputEvent() 如何处理:

frameworks\base\core\java\android\view\InputEventReceiver.java

    private void finishInputEvent(QueuedInputEvent q) {Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "deliverInputEvent",q.mEvent.getId());if (q.mReceiver != null) {boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;boolean modified = (q.mFlags & QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY) != 0;if (modified) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventBeforeFinish");InputEvent processedEvent;try {processedEvent =mInputCompatProcessor.processInputEventBeforeFinish(q.mEvent);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}if (processedEvent != null) {q.mReceiver.finishInputEvent(processedEvent, handled);}} else {q.mReceiver.finishInputEvent(q.mEvent, handled);}} else {q.mEvent.recycleIfNeededAfterDispatch();}recycleQueuedInputEvent(q);}

---->

frameworks\base\core\java\android\view\InputEventReceiver.java

    public final void finishInputEvent(InputEvent event, boolean handled) {if (event == null) {throw new IllegalArgumentException("event must not be null");}if (mReceiverPtr == 0) {Log.w(TAG, "Attempted to finish an input event but the input event "+ "receiver has already been disposed.");} else {int index = mSeqMap.indexOfKey(event.getSequenceNumber());if (index < 0) {Log.w(TAG, "Attempted to finish an input event that is not in progress.");} else {int seq = mSeqMap.valueAt(index);mSeqMap.removeAt(index);nativeFinishInputEvent(mReceiverPtr, seq, handled);}}event.recycleIfNeededAfterDispatch();}

这里会调用nativeFinishInputEvent,将seq 和handled 状态都传过去。 最终会通过socket 的方式通知InputDispatcher 进行下一步的处理,详细看handleReceiveCallback()。

至此,上层的按键分发流程大致分析完成了,其中的逻辑、细节很多,感兴趣的可以详细研究下源码,归根结底还是RTFSC。。。总结如下:

  • 注意按键操作是分 input stage的,不同的stage 进行不一样的逻辑处理,详细看2.1 节;
  • 当需要将event 分发给Activity 或view 时,是进入了ViewPostImeInputStage;
  • event 会一直抛给view,如果view 不处理,则返回到ViewGroup继续确认,如果ViewGroup 也不处理,就会抛到Activity,如果Activity 不处理,则会在PhoneWindow中特殊处理;
  • App 端在event 处理按照stage 顺序处理结束后,会finish 通知回InputDispatcher;

参考:

https://justinwei.blog.csdn.net/article/details/49764595

https://blog.csdn.net/wd229047557/article/details/100769791


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部