InputManagerService 十分钟让你了解Android触摸事件原理

从手指接触屏幕到MotionEvent被传送到Activity或者View , 中间究竟经历了什么?Android中触摸事件到底是怎么来的呢?源头是哪呢?本文就直观的描述一个整个流程 , 不求甚解 , 只求了解 。
Android触摸事件模型触摸事件肯定要先捕获才能传给窗口 , 因此 , 首先应该有一个线程在不断的监听屏幕 , 一旦有触摸事件 , 就将事件捕获;其次 , 还应该存在某种手段可以找到目标窗口 , 因为可能有多个App的多个界面为用户可见 , 必须确定这个事件究竟通知那个窗口;最后才是目标窗口如何消费事件的问题 。

InputManagerService 十分钟让你了解Android触摸事件原理

文章插图
 
InputManagerService是Android为了处理各种用户操作而抽象的一个服务 , 自身可以看做是一个Binder服务实体 , 在SystemServer进程启动的时候实例化 , 并注册到ServiceManager中去 , 不过这个服务对外主要是用来提供一些输入设备的信息的作用 , 作为Binder服务的作用比较小:
private void startOtherServices() { ... inputManager = new InputManagerService(context); wm = WindowManagerService.main(context, inputManager, mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL, !mFirstBoot, mOnlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); ... }InputManagerService跟WindowManagerService几乎同时被添加 , 从一定程度上也能说明两者几乎是相生的关系 , 而触摸事件的处理也确实同时涉及两个服务 , 最好的证据就是WindowManagerService需要直接握着InputManagerService的引用 , 如果对照上面的处理模型 , InputManagerService主要负责触摸事件的采集 , 而WindowManagerService负责找到目标窗口 。接下来 , 先看看InputManagerService如何完成触摸事件的采集 。
如何捕获触摸事件InputManagerService会单独开一个线程专门用来读取触摸事件 , 
NativeInputManager::NativeInputManager(jobject contextObj, jobject serviceObj, const sp<Looper>& looper) : mLooper(looper), mInteractive(true) { ... sp<EventHub> eventHub = new EventHub(); mInputManager = new InputManager(eventHub, this, this);}这里有个EventHub , 它主要是利用linux的inotify和epoll机制 , 监听设备事件:包括设备插拔及各种触摸、按钮事件等 , 可以看做是一个不同设备的集线器 , 主要面向的是/dev/input目录下的设备节点 , 比如说/dev/input/event0上的事件就是输入事件 , 通过EventHub的getEvents就可以监听并获取该事件:
InputManagerService 十分钟让你了解Android触摸事件原理

文章插图
 
在new InputManager时候 , 会新建一个InputReader对象及InputReaderThread Loop线程 , 这个loop线程的主要作用就是通过EventHub的getEvents获取Input事件
InputManagerService 十分钟让你了解Android触摸事件原理

文章插图
 
InputManager::InputManager( const sp<EventHubInterface>& eventHub, const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { <!--事件分发执行类--> mDispatcher = new InputDispatcher(dispatcherPolicy); <!--事件读取执行类--> mReader = new InputReader(eventHub, readerPolicy, mDispatcher); initialize();} void InputManager::initialize() { mReaderThread = new InputReaderThread(mReader); mDispatcherThread = new InputDispatcherThread(mDispatcher);} bool InputReaderThread::threadLoop() { mReader->loopOnce(); return true;} void InputReader::loopOnce() { int32_t oldGeneration; int32_t timeoutMillis; bool inputDevicesChanged = false; Vector<InputDeviceInfo> inputDevices; {...<!--监听事件--> size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE); ....<!--处理事件--> processEventsLocked(mEventBuffer, count); ... <!--通知派发--> mQueuedListener->flush(); }通过上面流程 , 输入事件就可以被读取 , 经过processEventsLocked被初步封装成RawEvent , 最后发通知 , 请求派发消息 。以上就解决了事件读取问题 , 下面重点来看一下事件的分发 。
事件的派发在新建InputManager的时候 , 不仅仅创建了一个事件读取线程 , 还创建了一个事件派发线程 , 虽然也可以直接在读取线程中派发 , 但是这样肯定会增加耗时 , 不利于事件的及时读取 , 因此 , 事件读取完毕后 , 直接向派发线程发个通知 , 请派发线程去处理 , 这样读取线程就可以更加敏捷 , 防止事件丢失 , 因此InputManager的模型就是如下样式:


推荐阅读