背景
之前有同事吐槽Android的事件分发拦截框架对于事件的拦截的方式和返回值比较乱,不同的地方需要返回不同的值,因此希望撰写一篇文档以作记录。
本文主要分析触摸事件和按键事件在不同阶段被拦截的流程,总结在不同阶段不同方法中返回值的含义。
按键的拦截
interceptKeyBeforeQueueing
在inputflinger分发keyevent前,会先将event上报给PhoneWindownManager处理,如果PhoneWindowManager消费掉,则不会继续分发给应用。这个PhoneWindowManager消费的流程如下:
interceptKeyBeforeQueueing方法的意义就是在事件入队列前拦截按键事件,也就是如果这个阶段被拦截,事件将不会再被分发,其过程如下:
1.InputFlinger的InputDispatcher.notifyKey时,会通过JNI的方式向PhoneWindowManager通报事件,先交由PhoneWindowManager来处理一些系统的按键,如果PhoneWindowManager已经处理消费且认为按键事件不应再分发给应用程序,则会返回0,否则返回1(ACTION_PASS_TO_USER)。
2.NativeInputManager.interceptKeyBeforeQueueing会根据PhoneWindowManager的返回值,调用handleInterceptActions将返回值转化为keyevent的policyFlags,而这个flag会跟随着keyevent走。
3.当InputDispatcher分发事件时,会去查询keyevent的policyFlags,如果事件没有设置POLICY_FLAG_PASS_TO_USER这个标志,则会将事件抛弃,不会分发。
本节不详细分析哪些事件会被PhoneWindowManager处理消费,在这里只大概阐述一下,在我们framework开发人员中,有时候会有需求在系统处理一些事件,且不希望这个事件分发给应用,直接在系统层消化掉的,那么可以在这个阶段添加我们的逻辑,只需要将PhoneWindowManager.interceptKeyBeforeQueueing返回0即可。
interceptKeyBeforeDispatching
interceptKeyBeforeDispatching是在事件分发前进行拦截,同样也是交给PhoneWindomManager来进行处理,处理流程如下:
1.InputDisptcher调用dispatchKeyLocked来处理InputReader发送过来的事件,此时的KeyEntry.interceptKeyResult为INTERCEPT_KEY_RESULT_UNKNOWN,如果此事件在上一阶段被设置为POLICY_FLAG_PASS_TO_USER,那么就会生成一个command,去交给PhoneWindowManager去处理。
2.PhoneWindowManager调用interceptKeyBeforeDispatching来处理一些特殊的key事件,如power、home等,然后返回事件被处理的时机(延时),如果事件应被拦截,则返回-1,立即分发则返回0,延迟分发则返回延时的时间。
3.InputDispatcher在处理command的时候根据PhoneWindowManager处理的返回值,将KeyEntry.interceptKeyResult置为不同的flag。
4.由于InputDispatcher loop里每次只处理一个事件,而每次循环都会优先处理堆积的command,而且mPendingEvent只有当事件被处理完才会置null,因此在下一次loop的时候,处理完command就立即继续处理这个key事件,根据interceptKeyResult来选择是否拦截。
dispatchKeyEvent
前面两个阶段都是框架层就将事件拦截,主要是交由framework工程师去修改的,dispatchKeyEvent主要讲的是应用所能修改的,按照调用的逻辑层次来分析。
InputDispatcher找到焦点窗口的InputChannel后就会将事件分发到ViewRootImpl中,经过一系列的InputState后,到了ViewPostImeInputState,执行processKeyEvent方法。
1.调用View.dispatchKeyEvent(mView就是DecorView),当Window没有被destory,走Activity.dispatchKeyEvent -> PhoneWindow.superDispatchKeyEvent -> DecorView.superDispatchKeyEvent -> ViewGroup.dispatchKeyEvent -> View.dispatchKeyEvent。此调用链中,Activity.dispatchKeyEvent、View.dispatchKeyEvent都是应用开发常常override的方法,ViewGroup.dospatchKeyEvent会优先找到当前的焦点窗口去处理事件。
2.在Activity.dispatchKeyEvent方法(默认实现)中,当PhoneWindow.superDispatchKeyEvent返回了false值,则走KeyEvent.dispatch方法,然后回调Activity的onKeyDown/onKeyUp等方法。
3.如果Activity.dispatchKeyEvent方法返回了false,则调用到PhoneWindow的onKeyDown/onKeyUp方法。
4.如果DecorView.dispatchKeyEvent方法返回了false,则会调用ViewPostImeInputState.performFocusNavigation方法来处理方向键选择焦点view的流程。
其中View.dispatchKeyEvent会调用到应用注册给某个view的OnKeyListener回调。
KeyEvent的拦截总结
KeyEvent的拦截基本如上所述,总结我们常用的一些拦截的位置及其返回值,如下表。
以上不是全部的拦截位置,只是列出了常规的一些拦截。
触摸事件的拦截
触摸的拦截相对按键事件的拦截就少了很多了。在InputReader阶段,有interceptMotionBeforeQueueing,默认情况下基本是不会拦截事件的,因此不做分析。
在InputDispatcher阶段,并没有看到做拦截的操作,也无需分析。
dispatchTouchEvent
在ViewRootImpl阶段,拦截的过程如下:
1.InputDispatch在找到触摸位置所在最前方的窗口(monitor不作分析)后,将事件分发给对应的ViewRootImpl中,有VRI将事件分发给DecorView,调用其祖类View.dispacthPointerEvent(motion事件有几类,以pointer事件作为分析)。然后调用到Activity.dispatchTouchEvent -> ViewGroup.dispatchTouchEvent -> View.dispatchTouchEvent。当有一个流程返回true,则将事件拦截。
2.当Activity.dispatchTouchEvent返回false没有拦截事件,则会调用Activity.onTouchEvent来处理。
总结