关于java:Android事件分发机制一事件是如何到达activity的

5次阅读

共计 6872 个字符,预计需要花费 18 分钟才能阅读完成。

事件散发,真的肯定从 Activity 开始吗?

前言

很快乐遇见你~

事件散发,android 中一个陈词滥调的话题了。前阵子去面试一家企业,他外面有一道口试题问到事件散发的流程,正确答案是抉择:Activity->window->view,根本的流程咱们也都晓得是从 Activity 开始散发。

过后我抉择完之后,我就开始思考,那事件是怎么达到 Activity 的?如果理解过 window 机制的读者会晓得,事件散发也是 window 机制的一部分,而 Activity 不属于 window 机制内,那么触摸事件应该是从 Window 开始才对,怎么是从 Activity 开始的呢?

抱着这些疑难,我重新学习了事件散发,联合之前的 window 机制内容,对于事件散发的了解又有了新的认知。这篇文章就是要答复事件是如何达到 Activity 的这个问题。

你认为我接下来要开始讲源码、零碎底层了?不不不,本文不讲这些。咱们要探索的是,一个触摸信息从零碎底层产生之后,一步步达到 Activity 进行散发的整体流程。而对于零碎底层的逻辑,不在本文的探讨范畴内。

本文是笔者 android 触摸事件系列文章的开篇,次要的内容是剖析触摸事件传递的门路。不会纠结于源码与底层,而是把触摸事件起源的大体流程出现进去,便于对事件散发体系有个更加残缺的了解。

治理单位:window

android 的 view 治理是以 window 为单位的,每个 window 对应一个 view 树。这里治理波及到 view 的绘制以及事件散发等。Window 机制不仅治理着 view 的显示,也负责 view 的事件散发。对于 window 的实质,能够浏览笔者的另一篇文章 window 机制。钻研事件散发的起源,则必须对于 window 机制有肯定的理解。

所以,首先要理解一个概念:view 树。

咱们的利用布局,个别是有多层 viewGroup 和 view 的嵌套,如下图:

而他们对应的构造关系如下图所示

此时,咱们就能够称该布局是以一个 LinearLayout 为根的一棵 view 树。LinearLayout 能够间接拜访 FrameLayout 和 RelativeLayout,因为他们都是 LinearLayout 的子 view,同样的 RelativeLayout 能够间接拜访 Button。

每一棵 view 树都有一个根,叫做ViewRootImpl,他负责管理这整一棵 view 树的绘制、事件散发等。

咱们的利用界面个别会有多个 view 树,咱们的 activity 布局就是一个 view 树、其余利用的悬浮窗也是一个 view 树、dialog 界面也是一个 view 树、咱们应用 windowManager 增加的 view 也是一个 view 树等等。最简略的 view 树能够只有一个 view。

android 中 view 的绘制和事件散发,都是以 view 树为单位。每一棵 view 树,则为一个 window。零碎服务 WindowManagerService,治理界面的显示就是以 window 为单位,也能够说是以 view 树为单位。而 view 树是由 viewRootImpl 来负责管理的,所以能够说,wms(WindowManagerService 的简写)治理的是 viewRootImpl。如下图:

  • wms 是运行在零碎服务过程的,负责管理所有利用的 window。应用程序与 wms 的通信必须通过 Binder 进行跨过程通信。
  • 每个 viewRootImpl 在 wms 中都有一个 windowState 对应,wms 能够通过 windowState 找到对应的 viewRootImpl 进行治理。

理解 window 机制的一个重要起因是:事件散发并不是由 Activity 驱动的,而是由零碎服务驱动 viewRootImpl 来进行散发,甚至能够说,在框架层角度,和 Activity 没有任何关系。这将有助于咱们对事件散发的实质了解。

那么触摸信息是如何一步步达到 viewRootImpl?为什么说 viewRootImpl 是事件散发的终点?viewRootImpl 如何对触摸信息进行散发解决的?这是咱们接下来要探讨的。

触摸信息是如何达到 viewRootImpl 的?

咱们都晓得的是,在咱们手指触摸屏幕时,即产生了触摸信息。这个触摸信息由屏幕这个硬件产生,被零碎底层驱动获取,交给 Android 的输出零碎服务:InputManagerService,也就是 IMS。

IMS 会对这个触摸信息进行解决,通过 WMS 找到要散发的 window,随后发送给对应的 viewRootImpl。所以发送触摸信息的并不是 WMS,WMS 提供的是 window 的相干信息。

这一部分波及到零碎底层的逻辑,不是本文的重点,感兴趣的读者举荐浏览 gityuan 博主的文章 Input 零碎 - 事件处理全过程。这里不开展解说。大体的过程如下图:

当 viewRootImpl 接管到触摸信息时,也正是应用程序过程事件散发的开始。

viewRootImpl 是如何散发事件的?

后面咱们讲到,viewRootImpl 治理一棵 view 树,view 树的最外层是 viewGroup, 而 viewGroup 继承于 view。因而整一棵 view 树,从内部能够看做一个 view。viewRootImpl 接管到触摸信息之后,通过解决之后,封装成 MotionEvent 对象发送给他所治理的 view,由 view 本人进行散发。

后面咱们讲到,view 树的根节点能够是一个 viewGroup,也能够是一个独自的 view,因而,这里的派发就会有两种不同的形式:间接给 view 进行解决 or viewGroup 进行事件散发。viewGroup 继承自 view,view 中有一个办法用于散发事件:dispatchTouchEvent。子类可重写该办法来实现本人的散发逻辑,ViewGroup 重写了该办法。

咱们的利用布局界面或者 dialog 的布局界面,顶层的 viewGroup 为 DecorView,因而会调用 DecorView 的 dispatchTouchEvent 办法进行散发。DecorView 重写了该办法,逻辑比较简单,仅仅做了一个判断:

DecorView.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
  1. 如果 window callBack 对象不为空,则调用 callBack 对象的散发办法进行散发
  2. 如果 window callBack 对象为空,则调用父类 ViewGroup 的事件散发办法进行散发

这里的 windowCallBack 是一个接口,他外面蕴含了一些 window 变动的回调办法,其中就有 dispatchTouchEvent,也就是事件散发办法。

Activity 实现了 Window.CallBack 接口,并在创立布局的时候,把本人设置给了 DecorView,因而在 Activity 的布局界面中,DecorView 会把事件分发给 Activity 进行解决。同理,在 Dialog 的布局界面中,会分发给实现了 callBack 接口的 Dialog。

而如果顶层的 viewGroup 不是 DecorView,那么对调用对应 view 的 dispatchTouchEvent 办法进行散发。例如,顶层的 view 是一个 Button,那么会间接调用 Button 的 dispatchTouchEvent 办法;如果顶层 viewGroup 子类没有重写 dispatchTouchEvent 办法,那么会间接调用 ViewGroup 默认的 dispatchTouchEvent 办法。

整体的流程如下图:

  1. viewRootImpl 会间接调用治理的 view 的 dispatchTouchEvent 办法,依据具体的 view 的类型,调用具体的办法。
  2. view 树的根 view 可能是一个 view,也可能是一个 viewGroup,view 会间接处理事件,而 viewGroup 则会进行散发。
  3. DecorView 重写了 dispatchTouchEvent 办法,会先判断是否存在 callBack,优先调用 callBack 的办法,也就是把事件传递给了 Activity。
  4. 其余的 viewGroup 子类会依据本身的逻辑进行事件散发。

因而,触摸事件肯定是从 Activity 开始的吗?不是,Activity 只是其中的一种状况,只有 Activity 本人负责的那一棵 view 树,才肯定会达到 activity,而其余的 window,则不会通过 Activity。触摸事件是从 viewRootImpl 开始,而不是 Activity。

控件对于事件的散发

到这里,咱们晓得触摸事件是先发送到 viewRootImpl,而后由 viewRootImpl 调用其所治理的 view 的办法进行事件散发。依照失常的流程,view 会依照控件树向上来散发。而事件却到了 activity、dialog,就是因为 DecorView 这个“叛徒”的存在。

后面讲到,DecorView 和其余的 viewGroup 很不一样,他有一个 windowCallBack,会优先把触摸事件发送给 callBack,从而导致触摸事件脱离了控件树。那么,这些 callBack 是如何解决触摸事件的?触摸事件又是如何再一次回到控件树进行散发的呢?

理解具体的散发之前,须要先来理解一个类:PhoneWindow。

PhoneWindow 继承自抽象类 Window,然而,他自身并不是一个 window,而是一个窗口性能辅助类。咱们晓得,一个 view 树,或者说控件树,就是一个 window。PhoneWindow 外部保护着一个控件树和一些 window 参数,这个控件树的根 view,就是 DecorView。他们和 Activity 的关系如下图:

咱们的 Activity 通过间接持有 PhoneWindow 实例从而来治理这个控件树。DecorView 能够认为是一个界面模板,他的布局大略如下:

咱们的 Activity 布局,就被增加到内容栏中,属于 DecorView 控件树的一部分。这样 Activity 能够通过 PhoneWindow,间接治理本身的界面,把 window 相干的操作都托管给 PhoneWindow,加重本身累赘。

PhoneWindow 并不是 Activity 专属的,其余如 Dialog 也是本人创立了一个 PhoneWindow。PhoneWindow 仅仅只是作为一个窗口性能辅助类,帮忙控件更好地创立与治理界面。

后面讲到,DecorView 接管到事件之后,会调用 windowCallBack 的办法进行事件散发,咱们先来看看 Activity 是如何散发的:

Activity

咱们首先看到 Activity 对于 callBack 接口办法的实现:

Activity.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {
    // down 事件,回调 onUserInteraction 办法
    // 这个办法是个空实现,给开发者去重写
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();
    }
    // getWindow 返回的就是 PhoneWindow 实例
    // 间接调用 PhoneWindow 的办法
    if (getWindow().superDispatchTouchEvent(ev)) {return true;}
    // 如果后面散发过程中事件没有被解决,那么调用 Activity 本身的办法对事件进行解决
    return onTouchEvent(ev);
}

能够看到 Activity 对于事件的散发逻辑还是比较简单的,间接调用 PhoneWindow 的办法进行散发。如果事件没有被解决,那么本人解决这个事件。接下来看看 PhoneWindow 如何解决:

PhoneWindow.java api29
public boolean superDispatchTouchEvent(MotionEvent event) {return mDecor.superDispatchTouchEvent(event);
}

这里的 mDecor 就是 PhoneWindow 外部保护的 DecorView 了,简略粗犷,间接调用 DecorView 的办法进行散发。看到 DecorView 的办法:

DecorView.java api29
public boolean superDispatchTouchEvent(MotionEvent event) {return super.dispatchTouchEvent(event);
}

好家伙,DecorView 对于事件也是没有做任何解决,间接调用父类的办法进行散发。DecorView 继承自 FrameLayout,然而 FrameLayout 并没有重写 dispatchTouchEvent 办法,所以调用的就是 viewGroup 类的办法了。所以到这里,事件就交给 viewGroup 去分发给控件树了。

咱们来回顾一下:DecorView 交给 Activity 解决,Activity 间接交给 PhoneWindow 解决,PhoneWindow 间接交给其外部的 DecorView 解决,而 DecorView 则间接调用父类 ViewGroup 的办法进行散发,ViewGroup 则会依照具体的逻辑散发到整个控件树中感兴趣的子控件。对于 ViewGroup 如何进行散发的内容,在后续的文章持续剖析。

从 DecorView 开始,绕了一圈,又回到控件树进行散发了。接下来看看 Dialog 是如何散发的:

Dialog

间接看到 Dialog 中的 diapatchTouchEvent 代码:

Dialog.java api29
public boolean dispatchTouchEvent(@NonNull MotionEvent ev) {if (mWindow.superDispatchTouchEvent(ev)) {return true;}
    return onTouchEvent(ev);
}

这里的 mWindow,就是 Dialog 外部保护的 PhoneWindow 实例,接下去的逻辑就和 Activity 的流程一样了,这里不再赘述。

而如果没有应用 DecorView 作为模板的窗口,流程就会和上述不统一了,例如 PopupWindow:

PopupWindow

PopupWindow 他的根 View 是 PopupDecorView,而不是 DecorView。尽管他的名字带有 DecorView,然而却和 DecorView 一点关系都没有,他是间接继承于 FrameLayout。咱们看到他的事件散发办法:

PopupWindow.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) {return true;}
    return super.dispatchTouchEvent(ev);
}

mTouchInterceptor 是一个拦截器,咱们能够手动给 PopupWindow 设置拦截器。工夫会优先交给拦截器解决,如果没有拦截器或拦截器没有生产事件,那么才会交给 viewGroup 去进行散发。

总结

最初咱们对整个流程进行一次回顾:

  1. IMS 从零碎底层接管到事件之后,会从 WMS 中获取 window 信息,并将事件信息发送给对应的 viewRootImpl
  2. viewRootImpl 接管到事件信息,封装成 motionEvent 对象后,发送给治理的 view
  3. view 会依据本身的类型,对事件进行散发还是本人解决
  4. 顶层 viewGroup 个别是 DecorView,DecorView 会依据本身 callBack 的状况,抉择调用 callBack 或者调用父类 ViewGroup 的办法
  5. 而不论顶层 viewGroup 的类型如何,最终都会达到 ViewGroup 对事件进行散发。

到这里,尽管触摸事件的“去脉”咱们还不分明,然而他的“来龙”就曾经十分清晰了。

本文的次要内容是讲事件的起源,但事件散发的起源远没有这么简略,源码的细节有十分多的内容值得咱们去学习,而本文只是把整体的流程抽了进去。而其余对于零碎底层的内容,对于有趣味读者举荐一系列书籍:《深刻了解 androidⅠⅡⅢ》。

下一篇文章,则是着重剖析 viewGroup 是如何把一个个的事件准确无误地分发给他的子 view。

感激浏览。

全文到此,原创不易,感觉有帮忙能够点赞珍藏评论转发。
笔者满腹经纶,有任何想法欢送评论区交换斧正。
如需转载请评论区或私信交换。

另外欢迎光临笔者的集体博客:传送门

正文完
 0