View的事件分发机制
在最新的 Android 系统中,事件的处理者不再由 InputEventReceiver 独自承担,而是通过多种形式的 InputStage 来分别处理,它们都有一个回调接口 onProcess 函数,这些都声明在 ViewRootImpl 内部类里面,并且在 setView 里面进行注册,比如有 ViewPreImeInputStage 用于分发 KeyEvent,这里我们重点关注与 MotionEvent 事件分发相关的 ViewPostImeInputStage。在它的 onProcess 函数中,如果判断事件类型是 SOURCE_CLASS_POINTER,即触摸屏的 MotionEvent 事件,就会调用 mView 的 dispatchPointerEvent 方法处理。也就是说事件分发最开始是传递给 DecorView 的,DecorView 的 dispatchTouchEvent 是传给 Window Callback 接口方法 dispatchTouchEvent,而 Activity 实现了 Window Callback 接口,在 Activity 的 dispatchTouchEvent 方法里,是调到 Window 的 dispatchTouchEvent,Window 的唯一实现类 PhoneWindow 又会把这个事件回传给 DecorView,DecorView 在它的 superDispatchTouchEvent 把事件转交给了 ViewGroup。
所以,事件分发的流程是:
DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup -> View
ViewGroup
dispatchTouchEvent ->onInterceptTouchEvent ->onTouchEvent
Activity/View
dispatchTouchEvent--> onTouchEvent
事件冲突一般都在onInterceptTouchEvent中处理,touchslop
onTouch优于onTouchEvent
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
点击事件产生后,首先传递给Activity的dispatchTouchEvent方法,通过PhoneWindow传递给DecorView,然后再传递给根ViewGroup,进入ViewGroup的dispatchTouchEvent方法,执行onInterceptTouchEvent方法判断是否拦截,再不拦截的情况下,此时会遍历ViewGroup的子元素,进入子View的dispatchToucnEvent方法,如果子view设置了onTouchListener,就执行onTouch方法,并根据onTouch的返回值为true还是false来决定是否执行onTouchEvent方法,如果是false则继续执行onTouchEvent,在onTouchEvent的Action Up事件中判断,如果设置了onClickListener ,就执行onClick方法。
ACTION_CANCEL什么时候触发,触摸button然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?
- 一般ACTION_CANCEL和ACTION_UP都作为View一段事件处理的结束。如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
- 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现action_cancel。
点击事件被拦截,但是想传到下面的View,如何操作?
重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View。
Event事件由来
这就要涉及 ImputManagerService 相关的知识了。IMS 的创建过程和 WMS 类似,都是由 SystemServer 统一启动,在创建时 IMS 会把自己的实例传给 WMS,也就是表明 WMS 是 InputEvent 的派发者。
IMS 在 Native 层创建了两个线程,InputReaderThread 和 InputDispatcherThread,前者负责从驱动节点中读取 Event,后者专责与分发事件。InputReaderThread 中的实现核心是 InputReader 类,但在 InputReader 实际上并不直接去访问设备节点,而是通过 EventHub 来完成这一工作。EventHub 通过读取 /dev/input 下的相关文件来判断是否有新事件,并通知 InputReader。InputDispatcherThread 在创建之初,就把自己的实例传给了 InputReaderThread,这样便可以源源不断的获取事件进行分发了。
分发时是如何找到投递目标呢?
也就是 findFocusedWindowTargetsLocked 方法的实现。也就是通过 InputMonitor 来找到 “最前端” 的窗口即可。这个 InputMonitor 是 WMS 提供的,而 IMS 实现了 WindowManagerCallbacks 接口,并把 InputMonitor 作为参数传递进去。在获知 InputTarget 之后,InputDispatcher 就需要和窗口建立连接,是通过 InputChannel,这也是一个跨进程通信,但是并不是采用 Binder,而是 Unix Domain Socket 实现。
在 Java 层,InputEventReceiver 对 InputChannel 进行包装,它是一个抽象类,它唯一的实现 WindowInputEventReceiver 就是在 ViewRootImpl 中,这样 ViewRootImpl 就可以获取到事件了。在 ViewRootImpl 中,一旦获知 InputEvent,就会进行入队操作,如果是紧急事件就直接调用 doProcessInputEvent 处理,如果不是紧急事件,就会把这个 InputEvent 推送到消息队列,然后按顺序处理,此时需要注意 Message 为异步消息。
至此,事件就流向了 ViewRootImpl 了。