简述:
在软件层面,读入和分发触摸事件的是系统服务 InputManagerService,实际上是jni层的 NativeInputManager。
NativeInputManager 里的两个对象 EventHub 和 InputManager。
EventHub监听设备节点,收集原始的输入事件。
InputManager 中是生产者消费者模型。InputReader从EventHub读取输入事件存入队列,InputDispatcher从队列事件取出事件分发。
事件由InputChannel跨进程传递,它一端连接作为Server的InputDispatcher,另一端给到作为Client的应用层View。
应用进程添加窗口时[ 关联handleResumeActivity()Activity启动流程],ViewRootImpl 的 setView() 最终调用到 WindowManagerService addWindow() 方法。它初始化了一对 InputChannel 对象,InputChannel 建立socket连接,跨进程传递输入事件。Server端的 InputChannel 注册到 InputManagerService 的 InputDispatcher;Client端则是 ViewRootImpl 的内部类 WindowInputEventReceiver。
dispatchTouchEvent(),事件在ViewGroup中进行分发,如果没有被消耗,则通过onInterceptTouchEvent()判断是否需要拦截,如果拦截则交给本ViewGroup的onTouchEvent()进行处理,如果不拦截则交给子View(或ViewGroup)继续重复dispatchTouchEvent()的过程进行分发,从外向内,以此类推。
详细:
在软件层面,读入和分发触摸事件的是系统服务InputManagerService。
在其构造函数中创建了jni层的对象 NativeInputManager,
而后者 NativeInputManager 的构造函数中又进一步创建了 EventHub 和 InputManager 两个对象。
其中EventHub依赖Linux内核的INotify与Epoll机制监听设备节点,通过 getEvent 函数读取设备节点的增删事件和原始输入事件。
InputManager 内部对于事件读取分发这一块维护了两个线程,运行于 InputReaderThread 线程中的 InputReader 对象负责循环从 EventHub 中通过 getEvents 接口读取输入事件并存入队列 mInboundQueue 中;运行于 InputDispatcherThread 线程中的 InputDispatcher 则负责循环从 mInboundQueue 队列中取出事件并处理分发。这是一个典型的生产消费者模型。
InputChannel 用来跨进程传递输入事件,InputChannel底层通过Socket通信,一端连接作为Server的InputDispatcher,另一端给到作为Client的应用层View。
1 作为Client端的应用进程,当一个新的窗口被添加,即 ViewRootImpl 的 setView 方法被调用时,会通过 IWindowSession.addToDisplay 方法来完成窗口的添加,这是一个binder调用,对岸的是 WindowManagerService,最终调用到的是 WindowManagerService 的 addWindow 方法。
2 WindowManagerService 的 addWindow 方法会通过 InputChannel.openInputChannelPair 这个静态方法生成两个socket的file descriptor ,代表了通信管道的两端 ,进而初始化了一对 InputChannel 对象;
3 Server端的 InputChannel 对象通过 InputManagerService 的 registerInputChannel 方法注册到 InputDispatcher 中;而Client端则是通过 InputChannel.transferTo 方法(这是一个binder调用)将socket的file descriptor返回给 ViewRootImpl 的内部类 WindowInputEventReceiver 的实例。至此,socket连接建立。
应用进程内的传递链
在应用进程这一侧,对接服务端读取输入事件的是 InputEventReceiver,其实现类是 ViewRootImpl 的内部类WindowInputEventReceiver,对于MotionEvent事件,native层会根据不同情况分别回调 dispatchInputEvent 和 onBatchedInputEventPending(在较早版本的代码中,是回调dispatchBatchedInputEventPending)方法。两个方法具体来说,dispatchInputEvent 是需要立即处理的单个事件,而 onBatchedInputEventPending 顾名思义,是延迟处理一组聚合的事件。
为什么会有这样的机制?
一般触控采样率则会高于刷新率,一个Vsync信号周期内会有多次触控采样。这就是会触发 onBatchedInputEventPending 的场景。MotionEvent 中也提供了 getHistoricalSize 这个方法供我们查看该event中聚合了几个实际触控点。
DOWN事件立即处理:DOWN事件的传递链,native层直接回调了dispatchInputEvent,这个事件也立即被处理
MOVE事件的传递链,红框的部分可以看到编舞者Choreographer的介入,当然这不是最初native层回调onBatchedInputEventPending方法的堆栈,而是Vsync信号到来时编舞者触发的回调。对于最初的MOVE事件,流程如下:
WindowInputEventReceiver 的 onBatchedInputEventPending 方法被回调,紧接着通过 postCallback() 方法向编舞者添加了一个类型为 Choreographer.CALLBACK_INPUT 的 ConsumeBatchedInputRunnable 对象,作为回调。
当下一个Vsync信号到来时,FrameDisplayEventReceiver 被回调并进一步调用编舞者的 doFrame 方法,进行当前帧的刷新。这时编舞者会依次对于已注册的 CALLBACK_INPUT,CALLBACK_ANIMATION,CALLBACK_INSETS_ANIMATION,CALLBACK_TRAVERSAL,CALLBACK_COMMIT 类型的callback进行回调,其中 CALLBACK_INPUT 就会包含 WindowInputEventReceiver 之前添加进来的 ConsumeBatchedInputRunnable 对象,而 CALLBACK_TRAVERSAL 对应的就是负责view tree重新measure/layout/draw的performTraversals方法。
ConsumeBatchedInputRunnable被执行时,会去消费之前pending的MOVE事件,后续的流程跟DOWN事件相同。
两者最终都走到了 dispatchTouchEvent(),事件在ViewGroup中进行分发,如果没有被消耗,则通过onInterceptTouchEvent()判断是否需要拦截,如果拦截则交给本ViewGroup的onTouchEvent()进行处理,如果不拦截则交给子View(或ViewGroup)继续重复dispatchTouchEvent()的过程进行分发,从外向内,以此类推。
//todo 为什么
如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。
//todo 为什么
如果View没有对ACTION_DOWN进行消费,之后的其他事件不会传递过来。
FAQ
move事件里只有一个坐标点吗?