一、Android消息获取过程概述
Android输入系统架构图:(图片来源见附录及水印)
事件处理详细流程:
事件传递都是以window为单位传递的,系统进程即system_server进程会计算事件需要传递到哪个window之上。找到合适window之后就将事件添加到window的Connection事件队列之上。至此都是在系统进程中进行。
需要将事件从系统进程传递到程序进程,涉及到Connection实现。Connection内部存在InputChannel,其内部是对unix socket的一种封装。unix socket是一种跨进程方式。通过创建一对InputChannel,系统进程和程序进程各一个这样就可以互相发送消息,传递事件。
程序进程InputChannel负责读取系统进程发送的事件,处理完成之后在通过该InoutChannel给系统发送事件处理结果。
系统进程InputChannel负责为客户端发送事件,同时接收事件处理结果。
二、Android输入系统
1.InputManagerService初始化
如下图初始化过程:
一、在system_server进程初始化的时候,会首先新建一个InputManagerService对象。
在InputManagerService构造函数中调用到native层的nativeInit()函数,该函数中又新建NativeInputManager对象。而在NativeInputManager构造函数中接着会新建InputManager对象。
在构造InputManager对象的时候,新建两个对象InputDispatcher和InputReader。InputReader负责监听系统底层输入设备的变化,包含增加删除输入设备、有新事件输入等。InputDispatcher负责处理分发InputReader传递过来的事件。紧接着调用InputManager.initialize(),该函数内部新建两个线程传入上步骤的两个对象负责消息读取和分发。
二、初始化WMS服务传入上步骤创建的InputManagerService。
三、调用InputManagerService.setWindowManagerCallbacks()这个函数设置一个回调,当native调用java层函数,通过这个回调通知WMS实现特定逻辑。
四、调用InputManagerService.start()该函数间接调用到native层的InputManager.start()函数,该函数内部会启动上述新建的两个线程开始工作。
综上:InputManagerService初始化主要完成工作:新建native层的InputManager、InputDispatcher、InputReader对象。InputManager负责管理这两个对象。启动对应线程工作。
2.新建InputChannel系统进程注册监听
如下图:
当应用进程需要启动Activity时显示window内容时,会调用到ViewRootImpl.setView()。该过程由此处开始、
一、在ViewRootImpl.setView()中,首先新建java层的InputChannel对象,接着通过IPC调用到WMS.addWindow()该对象会传入函数中,赋值在系统进程中创建真正的InputChannel。InputChannel负责接受系统进程端InputChannel发送的socket事件消息。
二、在WMS.addWindow()函数中首先调用InputChannel.nativeOpenInputChannelPair()。在native层新建两个NaitveInputChannel对象,同时创建两个socket文件用于跨进程事件传递。InputChannel对象分别封装保存socket文件句柄。接着创建一个数组将这两个InputChannel对象转化为java层InputChannel对象后保存并返回给调用者。可以看到InputChannel是真正负责进程通信事件传递的对象。
三、创建完成InputChannel对象后,为WMS保存一个,另一个客户端进程使用。接着调用InputManagerService.registerInputChannel()将WMS的InputManagerService注册到InputDispatcher。
InputManagerService.registerInputChannel()实际会调用到native层的nativeRegisterInputChannel(),在该函数中首先获取对于的NativeInputManager,该对象内部封装了InputDispatcher、InputReader对象。同时获取对应的native层的NaitveInputChannel对象。
调用NativeInputManager.registerInputChannel(),该函数中首先获取NativeInputManager保存的InputDispatcher,接着调用InputDispatcher.registerInputChannel(),在该函数中首先新建Connection对象分装传入InputChannel对象,接着获取InputChannel对应的文件描述符,以该描述符为key,Connection对象为value存入mConnecitonByFd中。最后调用Looper->addFd()监听该描述符,处理应用进程发送的事件处理结果。
综上:
java层只是表现层,实际操作都在vative层。
InputChannel.java:只是接口,实际对应native层的NaitveInputChannel
InputManagerService.java:对应InputManagerService.cpp,其中包含NativeInputManager负责分装管理系统的两个InputDispatcher、InputReader对象。
3.InputReader接收新事件处理流程
如下图:
如上述步骤,在InputManagerService新建初始化之后就会调用其start(),该函数内部就会分别调用InputReader、InputDispatcher对应线程开始工作。InputReader由此开始执行逻辑。
一、在线程中调用到InputReader->loopOnce()。在该函数中
首先调用的mEventHub->getEvents()获取底层设备现在状态:有没有新事件传入、设备的添加或者删除。
如果有以上情况发生,接着判断是那种具体状态。
二、如果是有了新的设备添加。
首先新建InputDevices对象,接着根据该设备类型:触摸屏、键盘等设置InputDevices具体事件处理对象InputMapper的子类:KeyboardInputMapper、MultiTouchInputMapper。当有新事件时就在具体的InputMapper中处理。一个InputDevices可以设置多个mapper。
三、如果是有新事件传入设备需要处理。
查找事件来源的InputDevices。接着调用InputDevices->process()执行事件处理。在该函数中交给其设置的InputMapper子类处理事件即mapper->process()。
如果是KeyboardInputMapper处理。首先将linux设备input event转化为android的输入事件,接着调用KeyboardInputMapper->processKey(),在该函数中调用InputDispatcher->notifyKey()执行具体分发事件。
InputDispatcher->notifyKey()函数中,首先在分发事件之前先通知WMS该事件,决定是否拦截。调用mPolicy->interceptKeyBeforeQueueing()该函数最终调用InputManagerService.interceptKeyBeforeQueueing(),IMS内部根据callback回调到WMS中。
接着InputDispatcher->enqueueInboundEventLocked()将该事件添加到队列判断该事件是否需要分发,如果需要分发looper->wake()唤醒InputDispatcherThread处理新事件。
如果是MultiTouchInputMapper处理。首先是判断当前设备触摸类型,多点触摸后分发触摸事件TouchInputMapper::dispatchTouches(),最终还是调用到InputDispatcher->notifyKey()执行具体分发事件。之后逻辑与键盘事件处理基本一直,也是先回调WMS处理是否拦截事件,接着添加到事件队列唤醒线程准备分发出去。
4.InputDispatcher分发新事件处理流程
如下图:
当InputReader读取到新事件并且判断需要分发会唤醒InputDispatcherThread执行。
一、首先调用到InputDispatcher->dispatchOnce()函数其内部调用dispatchOnceInnerLocked()。
在该函数中首先从队列中获取一个待处理的事件Entry。
根据该事件类型处理。
二、根据事件类型判断处理:
TYPE_KEY:如果是一个按键事件,调用dispatchKeyLocked()处理,该函数处理两个部分。
- 在分发之前先给Java层处理决定是否分发这个按键事件InputDispatcher->doInterceptKeyBeforeDispatchingLockedInterruptible()该函数通过IMS会间接调用到
PhoneWindowManager.interceptKeyBeforeDispatching()方法,该方法内部根据判断是否是home键,menu键判断是否应该将事件分发。
TYPE_MOTION:如果是一个触摸事件,调用dispatchMotionLocked(),该函数内部也会执行如下
- 在确定不是系统按键需要分发时或者是触摸事件时,调用findFocusedWindowTargetsLocked()获取需要接受事件的window对应的native对象。接着调用InputDispatcher->dispatchEventLocked()
该函数内部首先获取该事件的InputTarget接着获取对于的Connection对象。Connection分装了InputChannel可以通过socket传递事件到应用进程。
重新将事件封装为DispatchEntry,调用Connection->outboundQueue.enqueueAtTail()将该文件添加到Connection的队列中。
如果原先队列为空现在不为空则直接发送该消息调用startDispatchCycleLocked(),在该函数中会while循环遍历Connection队列。
将队列中的事件按照类型分别调用connection->inputPublisher.publishKeyEvent()和connection->inputPublisher.publishMotionEvent()执行事件发送逻辑。
判断事件是否发送成功,发送失败将事件重新加入队列中。
总结:InputDispatcher线程被唤醒读取事件–>根据事件类型在分发之前处理逻辑–>加入Connection事件队列中–>循环遍历发送事件。
5.InputChannel应用程序进程读取事件处理流程
如下图所示:
共有两个部分处理:
一、首先是注册WMS返回的InputChannel对象到native looper中。
在setView()中调用mWindowSession.addToDisplay()函数执行完成后客户端这里的InputChannel对象已经被生成并且赋值。接着新建WindowInputEventReceiver对象传入生成的InputChannel,在该对象构造函数中调用nativeInit()传入参数InputChannel。
在nativeInit()函数中获取传入参数对应的native层的InputChannel对象,接着新建NativeInputEventReceiver对象传入参数,然后调用该对象的NativeInputEventReceiver->initialize()方法。在该方法中接着调用NativeInputEventReceiver->setFdEvents(),在此方法中会首先获取InputChannel保存的fd文件描述符,接着调用客户端native层的looper->addFd()将该fd加入到消息监听中。
总结:将wms生成的InputChannel传入native层–>查找native对应的InputChannel生成NativeInputEventReceiver对象–>将InputChannel的fd加入到looper监听中。
二、获取事件消息处理流程
在java层looper循环执行时每次会区检查native层looper状态。
调用looper->nativePollOnce()函数会最终调用到native层的mLooper->pollOnce(),在该函数中循环调用到pollInner(),起内部又会调用epoll_wait()函数,还函数会等待新消息。
当有新消息到来首先调用pushResponse()将消息放到事件队列中,接着调用NativeInputEventReceiver->handleEvent()处理新事件进一步调用consumeEvents(),然后在该函数中根据事件类型封装InputEvent实体最后回调到java层函数处理。
java层对应的是WindowInputEventReceiver->dispatchInputEvent()接着根据事件分发到客户端程序。
总结:looper循环接收新事件–>有新事件放入事件队列,native回调java层处理–>java实际处理事件
参考连接:
http://blog.csdn.net/itleaks/article/details/27165657 Android输入系统之InputChannel(上)