事件散发,真的肯定从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 api29public 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对象不为空,则调用callBack对象的散发办法进行散发
- 如果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
办法。
整体的流程如下图:
- viewRootImpl会间接调用治理的view的
dispatchTouchEvent
办法,依据具体的view的类型,调用具体的办法。 - view树的根view可能是一个view,也可能是一个viewGroup,view会间接处理事件,而viewGroup则会进行散发。
- DecorView重写了
dispatchTouchEvent
办法,会先判断是否存在callBack,优先调用callBack的办法,也就是把事件传递给了Activity。 - 其余的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 api29public 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 api29public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);}
这里的mDecor就是PhoneWindow外部保护的DecorView了,简略粗犷,间接调用DecorView的办法进行散发。看到DecorView的办法:
DecorView.java api29public 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 api29public 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 api29public boolean dispatchTouchEvent(MotionEvent ev) { if (mTouchInterceptor != null && mTouchInterceptor.onTouch(this, ev)) { return true; } return super.dispatchTouchEvent(ev);}
mTouchInterceptor
是一个拦截器,咱们能够手动给PopupWindow设置拦截器。工夫会优先交给拦截器解决,如果没有拦截器或拦截器没有生产事件,那么才会交给viewGroup去进行散发。
总结
最初咱们对整个流程进行一次回顾:
- IMS从零碎底层接管到事件之后,会从WMS中获取window信息,并将事件信息发送给对应的viewRootImpl
- viewRootImpl接管到事件信息,封装成motionEvent对象后,发送给治理的view
- view会依据本身的类型,对事件进行散发还是本人解决
- 顶层viewGroup个别是DecorView,DecorView会依据本身callBack的状况,抉择调用callBack或者调用父类ViewGroup的办法
- 而不论顶层viewGroup的类型如何,最终都会达到ViewGroup对事件进行散发。
到这里,尽管触摸事件的“去脉”咱们还不分明,然而他的“来龙”就曾经十分清晰了。
本文的次要内容是讲事件的起源,但事件散发的起源远没有这么简略,源码的细节有十分多的内容值得咱们去学习,而本文只是把整体的流程抽了进去。而其余对于零碎底层的内容,对于有趣味读者举荐一系列书籍:《深刻了解androidⅠⅡⅢ》。
下一篇文章,则是着重剖析viewGroup是如何把一个个的事件准确无误地分发给他的子view。
感激浏览。
全文到此,原创不易,感觉有帮忙能够点赞珍藏评论转发。
笔者满腹经纶,有任何想法欢送评论区交换斧正。
如需转载请评论区或私信交换。另外欢迎光临笔者的集体博客:传送门