共计 13702 个字符,预计需要花费 35 分钟才能阅读完成。
原文链接 Android View 事件派发流程
自从乔帮主横空出世推出了 iPhone 以来,触控式的操作便成了 21 世纪智能设施的规范输出形式。对于同是智能操作系统的 Android 来说,也不例外。事件,特地是触控事件对于挪动利用程序开发来说是一个十分重要的,也是开发人猿必须把握的事件。这里就要讨论一下 Android View 中的 Event 零碎,重点探讨一下事件的派发流程。
输出事件综述
事件的分类
对于 Android 零碎来说用户输出事件分为两类,一个是 KeyEvent,这个是硬件产生的事件,或者更精确的说是非触控手势产生的事件,通常包含硬件按扭如音量键,电源键,零碎导航(HOME,BACK 和 MENU)以及外设(如外接设备,键盘,自拍杆等)零碎层也都会对立的做成映射转成 KeyEvent 传给以后 Window 窗口(以后过程)。
还有一类就是专指解控屏幕产生的触摸式的手势事件,是 MotionEvent,为啥不叫 TouchEvent 呢,因为啊最后的 Android 版本是反对滑动球的,当初曾经没有这种设施,然而名字就这么流传下来了。这个事件专门由视图零碎 view tree 来解决,本文也将重点探讨此类事件。
事件从哪里来
简略来说事件是源自于硬件,比方屏幕或者按键,这是废话,晓得了这个意义也不大,硬件产生电子信号后会经由驱动传给内核,内核再报给输出零碎,再传给 wms(Windows Manager Server),最终会到 Window 这里。对于应用层来说,能够了解 为事件都是从 Window 对象这里来的就行了。
谁先收到事件
对于 GUI 应用程序层来说,wms 就是事件起源,那么 ViewRootImpl 对象是第一个接管到事件,ViewRootImpl 并没有间接把事件派发给 view tree,而是先给到 DecorView,宿主组件在 DecorView 处有一个专门接管事件的回调,由此事件便到了以后的宿主组件如 Activity 或者 Dialog,看它是否违心做解决,如果它不解决,那么就会把事件再派发给 GUI 视图零碎,也即 view tree,这一次没有再通过 ViewRootImpl 对象,而是由 Window 对象间接调用根节点的 dispatchTouchEvent 或者 dispatchKeyEvent。
15:57:02.254 W/System.err: java.lang.Exception: Stack trace
15:57:02.255 W/System.err: at java.lang.Thread.dumpStack(Thread.java:1348)
15:57:02.256 W/System.err: at net.toughcoder.view.ViewWindowExampleActivity.dispatchKeyEvent(ViewWindowExampleActivity.java:107)
15:57:02.256 W/System.err: at com.android.internal.policy.DecorView.dispatchKeyEvent(DecorView.java:342)
15:57:02.256 W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.processKeyEvent(ViewRootImpl.java:5053)
15:57:02.257 W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4921)
15:57:02.257 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:02.258 W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
15:57:02.258 W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
15:57:02.259 W/System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4601)
15:57:02.259 W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
15:57:02.259 W/System.err: at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4658)
15:57:02.260 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:02.260 W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
15:57:02.260 W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
15:57:02.261 W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
15:57:02.261 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:02.261 W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
15:57:02.262 W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
15:57:02.262 W/System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4634)
15:57:02.263 W/System.err: at android.view.ViewRootImpl$ImeInputStage.onFinishedInputEvent(ViewRootImpl.java:4795)
15:57:02.263 W/System.err: at android.view.inputmethod.InputMethodManager$PendingEvent.run(InputMethodManager.java:2571)
15:57:02.263 W/System.err: at android.view.inputmethod.InputMethodManager.invokeFinishedInputEventCallback(InputMethodManager.java:2081)
15:57:02.264 W/System.err: at android.view.inputmethod.InputMethodManager.finishedInputEvent(InputMethodManager.java:2072)
15:57:02.264 W/System.err: at android.view.inputmethod.InputMethodManager$ImeInputEventSender.onInputEventFinished(InputMethodManager.java:2548)
15:57:02.265 W/System.err: at android.view.InputEventSender.dispatchInputEventFinished(InputEventSender.java:141)
15:57:02.265 W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
15:57:02.265 W/System.err: at android.os.MessageQueue.next(MessageQueue.java:326)
15:57:02.265 W/System.err: at android.os.Looper.loop(Looper.java:160)
15:57:02.266 W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6692)
15:57:02.266 W/System.err: at java.lang.reflect.Method.invoke(Native Method)
15:57:02.266 W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
15:57:02.266 W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
15:57:14.582 W/System.err: java.lang.Exception: Stack trace
15:57:14.586 W/System.err: at java.lang.Thread.dumpStack(Thread.java:1348)
15:57:14.586 W/System.err: at net.toughcoder.view.DemoEventView.dispatchTouchEvent(DemoEventView.java:24)
15:57:14.586 W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
15:57:14.586 W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
15:57:14.586 W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
15:57:14.587 W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
15:57:14.587 W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
15:57:14.587 W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
15:57:14.587 W/System.err: at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)
15:57:14.587 W/System.err: at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2662)
15:57:14.587 W/System.err: at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)
15:57:14.588 W/System.err: at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)
15:57:14.588 W/System.err: at android.app.Activity.dispatchTouchEvent(Activity.java:3400)
15:57:14.588 W/System.err: at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)
15:57:14.588 W/System.err: at android.view.View.dispatchPointerEvent(View.java:12753)
15:57:14.588 W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5122)
15:57:14.588 W/System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4925)
15:57:14.588 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:14.588 W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4601)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4658)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4495)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4461)
15:57:14.589 W/System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4469)
15:57:14.590 W/System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4442)
15:57:14.590 W/System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7117)
15:57:14.590 W/System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7086)
15:57:14.590 W/System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7047)
15:57:14.590 W/System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7220)
15:57:14.590 W/System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:187)
15:57:14.590 W/System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
15:57:14.590 W/System.err: at android.os.MessageQueue.next(MessageQueue.java:326)
15:57:14.591 W/System.err: at android.os.Looper.loop(Looper.java:160)
15:57:14.591 W/System.err: at android.app.ActivityThread.main(ActivityThread.java:6692)
15:57:14.591 W/System.err: at java.lang.reflect.Method.invoke(Native Method)
15:57:14.591 W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
15:57:14.591 W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
因而,从应用程序的角度来说,第一个收到事件的是 Activity 或者 Dialog 这种持有 Window 的顶级组件,所以如果想要从窗口级别来拦挡掉所有的事件,那么 Activity 会是最好的抉择,代码示例:
@Override
public boolean dispatchKeyEvent(KeyEvent event) {if (want to intercept all key events) {return true;}
return super.dispatchKeyEvent(event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {if (want to intercept all touch events) {return true;}
return super.dispatchTouchEvent(event);
}
下面两个办法是在 view tree 之前最先收到事件的办法,是组件外面想拦挡的最佳地点,这是从后面打先锋。而要想解决掉 view tree 未解决的事件,则须要在 onKeyUp(int keyCode, KeyEvent event))和 onKeyDown(int keyCode, KeyEvent event))以及 onTouchEvent(MotionEvent event))这几个办法外面解决,这个相当于是断后。
事件的散发过程(Event Propagation)
事件达到应用程序这一端后,从 Activity 开始了散发过程,它的机制 和过程好比一个各处流动的小球,每个节点都接管一个事件对象,返回一个 boolean,如果返回 true 则示意事件在此被耗费,以后事件散发终止,而如果返回 false,示意以后节点对此事件不感兴趣,事件持续散发。
而具体的流程,则是先到 Activity(Dialog 等第一级组件),再到 view tree,在 view tree 外面也是如此从父 view 到子 view 如此一个一个的传递,这个先后顺序流程则是由整个零碎构架决定的。
事件是一个数据流
后面讲的事件的散发过程,就能够看作是一个球在在流动,这是从单个事件的解决角度看,是这样。但从整个的事件来看更如此,因为事件通常像电子信号一样,是从起源登程(如触摸屏,硬件等),有肯定工夫距离的一连串的事件对象的派发的整个过程,简略来比喻就是几个球,每隔 1 秒就收回一个球,这样一个数据流。
说了这么多,其实实在要做起来还是比较简单的,尽管是一个数据流,然而每 一个流都有开始和完结的标记,解决起来并不难。比方 KeyEvent,开始是 onKeyDown,而后是 onKeyUp,在这两个外面解决,就实现了对 KeyEvent 流的解决。
而 MotionEvent 则稍简单一些,一个 MotionEvent 流,零碎会一直的回调 onTouchEvent,直到完结,通过 MotionEvent#getAction())来判断,从 ACTION_DOWN 到 ACTION_MOVE 到 ACTION_UP 或者 ACTION_CANCEL 完结。
留神:因为 KeyEvent 的解决绝对较简略,所以剩下的局部将重点探讨 MotionEevnt。
Touch Event 的派发流程
事件产生当前,会传递给 Activity#dispatchTouchEvent,如果没有被拦挡,那么就会传给 Window,而 Window 则会传给 ViewRootImpl 来解决,view tree 解决完后,会再交给 Activity#onTouchEvent:
public boolean dispatchTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) {onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {return true;}
return onTouchEvent(ev);
}
这个办法能够分明地看到,先锋和断后和 view tree 在事件派发流动中的程序。
上面重点看看在 Window 中(view tree)外面事件的派发流动过程。其实重点看 View#dispatchTouchEvent 和 ViewGroup#dispatchTouchEvent 就能够了,须要留神的是,事件的派发流程与解决流程是不一样的,派发在先,解决在后,所以如果看事件的派发须要看 dispatch 打头的办法,而解决则是看 on 打头的。
View 的 dispatch 较为简单一些,因为它提供的是一个默认的实现,并且 View 是作为 view tree 中的一个叶子的,因而它的 dispatch 实际上就是一个起点,所以它做的事件就是,看是否有 OnTouchListener,有就调用其 onTouch,而后再调用 onTouchEvent 把事件处理一下,就完了。从这里也能够看进去 OnTouchListener 是走在 onTouchEvent 办法的后面的。
至于 ViewGroup 则绝对简单,因为它要治理子 View,向子 View 派发事件,并且还要解决拦挡。它的逻辑大略是:先看本人是否要拦挡 onInterceptTouchEvent)返回 true 示意要拦挡,false 不会拦挡,如果要拦挡,则调用本人的 onTouchEvent 解决掉事件,而后终止派发(实在的逻辑要稍微简单一些,不同的 ACTION 解决逻辑不一样)。
重点讲一下 ViewGroup 是如何向子 View 派发事件的,当不拦挡的时候,这是比拟惯例的时候,会把事件向子 View 派发,来捋一捋这一流程:首先,会通过 buildTouchDispatchChildList 这个办法来抉择子 View 的程序,这个办法是把子 View 按事件派发的流程来排序,这个程序是就是用户看到的程序,会以 Z 轴(屏幕从里到外)来排序,以及渲染(draw)的程序,毕竟从用户角度看最先点击到的,必定 是 Z 轴最大(离用户最近),最先 draw 完的(没有被遮挡)。而后按这个程序,按个子 View 调用其下面的 dispatchTouchEvent,就把事件向子 View 传递了过来,当然 这个也是事件在流动,一旦事件被耗费,就会进行派发。
从这个过程来看 view tree 事件派发是个深度优先的过程,所以 view tree 的深度不单单影响渲染的性能,连事件处理也比扁平的要慢一些。
Touch Event 事件处理办法
事件的解决也即是各种 on 结尾的办法如 onTouchEvent,或者各种 listener(OnClickListener,OnTouchListener)。个别惯例来说设置各种 listener 就够了,但如果想要自定义一些就间接 override onTouchEvent 办法,这里就不细说了,各种教程太多了。
listener 与间接 Override 父类办法的区别
须要留神的是如果要 override,那么必定 要自定义 View 才能够,所以这个是更『黑客式』的办法,只有有必要自定义 View,且惯例各种 listener 不能满足需要才有必要如此做,如实现各种自定义的手势等。
listener 最大的益处是,很简略不便,隔离性好,事件的触发与后果是隔离的,想针对 事件做解决,实现一个接口就好了,至于事件条件的触发则不必关怀,任何对象都能够实现接口以处理事件,而不用非去子类化(继承)View 对象。
还须要留神的是 OnTouchListener 产生的工夫要早于 onTouchEvent,而惯例的手势回调接口(如 OnClickListener 和 onLongClickListner)是在 onTouchEvent 中触发的。因而,OnTouchListener 其实也是一个更为低级的『黑客式』的接口,个别当须要自定义辨认手势时才须要实现此接口。
避免点击穿透
有些时候会有一些点击穿透的问题呈现,比方写了一个布局,外面有几个 Button 和 TextView,然而当点击这些次要内容之外的空白区域时,此页面下一层的 Button 却收到事件,比方触发了其 onClick 事件。当应用层叠 式的 Fragment 时,此问题较常见。其实从 View#onTouchEvent 中就能够看到解决方案,如此某个 View 是 clickable 的,那么它会把事件消耗掉,而如果 clickable 为 false 就会持续传递。
呈现穿透的起因就是空白区域,只有这个层页面的一个根布局,通常会是一个 ViewGroup,而大部分的 ViewGroup 默认 clickable 都是 false,因此事件会持续向 view tree 外面传递,直到其被耗费。
此类问题最简略的解决就是把 View 设置为 clickable=”true”,这个在布局文件中就能够设置。
根底手势辨认
根底的手势辨认,是说对于触控式操作的一些简略的操作的分类,比方轻触屏幕马上拿开,这视为点击(click 或者叫 press,或者叫 tap),长按屏幕视为 long click 或者叫 long press,还有滑动,双击等等。手势辨认,即是一套逻辑算法,用以判断用户以后是哪一种操作,而后触发相当的解决逻辑,给与用户操作上的反馈。废话就这么多,接下来来看具体如何做吧。
在 Android 的 GUI 零碎中根底的手势有点击 (click) 和长按(long click)。要辨认这些根底手势有两种办法,一是设置接口回调给 View,也即实现一个 OnClickListener,而后把此对象设置给 View#setOnClickListener)(长按就是 OnLongClickListener 和 View#setOnLongClickListener));另外一种办法,就是针对 view tree 外部,比方子类化(继承)某个 View 对象,而后 override 相应的办法。
留神: 在写布局 xml 文件中也能够不便的用 onclick 属于来指定 手势回调办法,但它的实质与设置一个 OnClickListener 是一样的。
如果,点击和长按不能满足操作需要时,就须要稍复简单的根底手势辨认对象来帮忙,也即是 GestureDetector,它与 View 的连贯形式是接口拆散,其实不见得能够用于 View,只有有 MotionEvent 事件 起源即可。应用的办法并不简单,只须要设置一个 OnTouchListener 或者子类化 View 并 override onTouch 办法,从中拿到 MotionEvent 对象,而后把 MotionEvent 塞给一个 GestureDetecotor 对象,就完了,GestureDetector 会回调你感兴趣的对应手势解决回调办法,通过 OnGestureListener 对象。因为 OnGestureListener 是一个接口,但如果你仅对某几个手势回调办法感兴趣,不想把所有办法都 写一遍(哪怕是空实现),那么能够子类化 SimpleOnGestureListener,这是一个类,它实现了 OnGestureListener 的所有办法,咱们仅须要 override 感兴趣的办法即可。
有一个须要特地留神的事件就是,当你用 GestureDetector 时,它与惯例的 onClick 或者 onLongClick 的先后顺序,或者 叫抵触解决。基于一致性的准则,如果应用了 GestureDetector 时,意味着你想要本人管制事件处理,那么就不应该再 设置 onClick 或者 onLongClick 了。但如果真不小心这么做了,后果又会怎么样呢?这就须要从 View 的事件处理流程找答案。OnTouchListener 的调用是在 View#dispatchTouchEvent,这个是在 View#onTouchEvent 之前,而 OnClickListener 和 OnLongClickListener 是在 View#onTouchEvent 中调用的。所以,程序是这样的:
- 如果你用 OnTouchListener 获取的 MotionEvent,那么你的 OnGestureListener 的回调办法是最先被调用到的,在所有的其余回调之前。
- 如果是 override View#onTouchEvent 办法获取的 event,那么取决于你调用 super#onTouchEvent 的程序,如果你是在调用 super 之前,那么还是你的 gesture listener 先执行。其实吧,正常人 override 的写法必定都先写本人的逻辑最初再调用 super,或者罗唆不调用 super,这是最正统子类 override 父类的姿式。
由此,能够得出的论断就是如果应用了 GestureDetector,那么你的 gesture listener 必定是优先被执行的。
onClick 与 onLongClick 的触发机会
再 来看另外 一个比拟 有意思的两个问题,onClick 的触发机会是啥时候?从 View#onTouchEvent 办法中可看进去,是在 ACTION_UP 时触发的,如果它还没有触发 long click,而 long click 则是在事件开始当前 ACTION_DOWN 当前开始计时,达到肯定工夫距离后便触发,不算后续的事件类型。
整体的流程是这样,在 View#onTouchEvent 外面,分事件类型来解决,ACTION_DOWN 中开始计时,前面 ACTION_MOVE 中持续计时,如果达到长按规范,则触发 long click,在失常完结的 ACTION_UP 中,看有没有达到长按规范,有就触发 long click,没有则触发 on click。
零碎阈值定义
像长按的时长,滑动的最小间隔,拉伸的最小间隔等 等 这些要害的阈值都 是有零碎倡议的定义的,这些值都 在 ViewConfiguration 外面,通常倡议间接应用零碎定义的要好一些,除非真有非凡须要。
能够查看 GestureDetector 中对这些常量的应用。
参考资料
- Mastering the Android Touch System
- How are Android touch events delivered?
原创不易,打赏 , 点赞 , 在看 , 珍藏 , 分享 总要有一个吧