android源码-事件分发处理机制(上)- java层事件分发流程

分为上下两篇,

上篇18年写的,下篇主要讲native的分发流程,2022年写的。

下篇链接:android源码-事件分发处理机制(下)-从信号源输入到处理完成的完整源码解读_失落夏天的博客-CSDN博客

首先来一张图镇楼,说明一下方法的依次调用顺序:

这张图囊括了,从native回调java,一直到ViewGroup处理的所有方法堆栈调用。

我把事件分发分为4个部分:

第一部分:InputEventReceiver

1、当用户点击了屏幕上的某个位置之后,native层会接收到。会通过方法回调通知java层这个点击事件。方法位于InputEventReceiver类中的dispathInputEvent方法。

// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);
}

我们可以清楚的看到,这个调用是来自于native层。而我们的代码研究,也就只从java层开始。

这里传入的InputEvent的类型其实是MotionEvent,比较重要的属性会有下面这些:

action//类型,比如ACTION_DOWN、ACTION_UP、ACTION_MOVE等等。

x[0]//X轴坐标

y[0]//y轴坐标

toolType[0]//触发类型,目前有TOOL_TYPE_FINGER手指点击触发、TOOL_TYPE_STYLUS手写板触发、TOOL_TYPE_MOUSE鼠标触发、TOOL_TYPE_UNKNOWN未知四种。PS:都给鼠标和手写板准备上了,看来以后真的是准备把android做到PC上了。

eventTime//点击时间

mSeq//对列号。这个参数还有搞太清楚,推测是序列号,一次性N多事件触发时,按照这个顺序来逐个处理的吧。

2、ViewRoomImpl类当中,会有一个类WindowInputEventReceiver,这个类继承自InputEventReceiver。在ViewRoomImpl类setView的时候,初始化WindowInputEventReceiver。dispathInputEvent方法中会调用onInputEvent方法,而onInputEvent这个方法被子类重写掉了。WindowInputEventReceiver中的onInputEvent的实现方法如下:

@Override
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true);
}

调用enqueueInputEvent实现了向ViewRootImpl层的传递。

第二部分:ViewRootImpl部分

1、ViewRootImpl中会有一个ViewRootHandler来接收各种传递过来的事件、其中如果MessageType为MSG_DISPATCH_INPUT_EVENT、MSG_SYNTHESIZE_INPUT_EVENT、MSG_DISPATCH_KEY_FROM_IME时,会调用enqueueInputEvent方法。

2.调用ViewRootImpl类中的enqueueInputEvent方法,处理输入事件。

参数为

void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {

其中通过ViewRootHandler调用的processImmediately值必定为true。

3.这里的enqueueInputEvent方法中如果processImmediately的值为false,则是由于其余的补偿逻辑执行调用过来的,最终也会通过发送what值为MSG_PROCESS_INPUT_EVENTS的Message事件,传递给主线程来执行。

4.ViewRootHandler中接收事件,如果waht值为MSG_PROCESS_INPUT_EVENTS,则会调用doProcessInputEvents方法。
5.doProcessInputEvents方法中,处理输入事件是以while链表的方式来去逐个的处理。获取最前端的HeadEvent,然后把这个对象设置给一个方法内变量q。设置HeadEvent为q.next,再把q.next设置为空。最终执行deliverInputEvent(q),处理这个点击事件。也就是说,无论这个事件是否处理成功或者完成,都已经消费掉了,不会再加入到队列当中。(PS:最终处理失败后是否再加回队列的逻辑再说)
6.deliverInputEvent方法中,获取InputStage对象。并调用stage的deliver(QueuedInputEvent)方法

7.普通流程的话InputStage会调用apply方法。

apply(q, onProcess(q));

这里的参数2是以onProcess(QueuedInputEvent)形式传入的。

其实这里的stage是父类,其子类是ViewPostImeInputStage,所以实际上会调用子类的onProcess方法,然后调用processPointerEvent(QueuedInputEvent)方法
8.这里有一行代码如下:
 boolean handled = mView.dispatchPointerEvent(event);
mView指向的就是我们展示层的View对象,从而ViewRoomImpl的使命结束,传递事件给View层处理。

第三部分:Activity层面的部分

1、上面所说的view,实际上指的是ViewRootImpl中的最上层根节点的view,这个view其实就是DecorView,一个界面对应一个window,对应着唯一的一个DecorView。

DecorView中的方法:

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

Window.Callback这个对象就是Activity,可以看到Activity是实现了Window.Callback这个接口的。

所以最终会调用到Activity的dispatchTouchEvent的方法。

2、Activity中包含为一个PhoneWindow,PhoneWindow中包含唯一的一个DecorView,这个就是整个界面的根布局。

DecorView通过superDispatchTouchEvent方法调用父类方法dispatchTouchEvent(),而父类就是ViewGroup。

第四部分:View以及ViewGroup层面的部分

1、dispatchPointerEvent方法中,判断是否是正常的touch事件,如果是则调用dispatchTouchEvent方法。(android26以后改了,直接跳过了dispatchPointerEvent方法)
2、对于dispatchTouchEvent方法,View和ViewGroup的实现逻辑就不一致了。
对于View来说,经过简单的判断逻辑后,直接调用View类中的onTouchEvent(MotionEvent)方法来处理输入事件。
3、对于ViewGroup来说,首先判断
intercepted = onInterceptTouchEvent(ev);//获取是否打断事件传递,如果没有并且没有取消该事件。
需要for循环其内部的mChildren子View队列,顺序是从后向前遍历。
 for (int i = childrenCount - 1; i >= 0; i--) {//实现逻辑}
for循环当中,会调用dispatchTransformedTouchEvent方法,该方法中,会调用子View的dispatchTouchEvent方法。实现递归逐级调用。如果子View中没有处理完成,则会根据当前child生成newTouchTarget,把添加到队列mFirstTouchTarget的最前面。

然后根据TouchTarget判断队列中其他的是否处理完成。

4、onTouchEvent方法。ViewGroup逐级下发,最终会传递到最终的View控件的onTouchEvent方法当中。
这里面,会根据状态的不同设置不同的状态。比如setPressed这样的状态。
其次,如果是ACTION_UP事件的话,会执行下面的代码段

  if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }

如果没有执行过,则执行performClick方法。而performClick方法会最终会回掉mOnClickListener.OnClick(this),这也就是我们最经常使用的setOnClickListener的回调来源。

5、如果你还想问其它的回调怎么处理的。View方法中搜一搜就知道了。

比如mOnTouchListener,就是在dispatchTouchEvent方法中执行的,如果onTouch返回true,那么后面的onTouchEvent方法就不会执行。

代码如下:

 ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }


            if (!result && onTouchEvent(event)) {
                result = true;
            }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失落夏天

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值