在Android系统中,类似于键盘按键、触摸屏等事件是由WindowManagerService服务来管理的,然后再以消息的形式来分发给应用程序进行处理。系统启动时,窗口管理服务也会启动,该服务启动过程中,会通过系统输入管理器InputManager来负责监控键盘消息。当某一个Activity激活时,会在该Service***册一个接收消息的通道,表明可以处理具体的消息,然后当有消息时,InputManager就会分发给当前处于激活状态下的Activity进行处理。
InputManager的启动过程
InputManager负责事件的监控以及分发,而其启动需要WindowManagerService的启动来完成。而系统启动Win dowManagerService的时候,会执行WindowManagerService.java文件的main方法。
main方法下会创建一个线程类的实例,并执行其start方法,即调用该线程类下的run方法。run方法里面首先创建一个Looper消息循环,而looper是Android系统里面处理消息的一种机制,然后调用WindowManagerService的构造方法来启动WindowManagerService。
最后调用Looper的loop方法,将该线程添加到一个消息循环里面去,这样系统就可以持续的等待接收并处理到来的消息。
在WindowManagerService的构造方法里,调用InputManager的构造方法创建一个InputManager对象的实例,并将WindowManagerService对象作为参数传入构造方法里面。在InputManager的构造方法里面得到前面创建的looper对象并将其内部的消息队列作为参数传给JNI层的nativeInit函数里面。
nativeInit函数的实现位于JNI层的com_android_server_InputManager.cpp里面。该函数下主要做两件事情。
◆ 通过java层传入的looper消息队列得到和JNI层的looper对象,是其对应起来
◆同样为使和java层的InputManager对象对应起来,创建了本地的InputManager对象实例即NativeInputManager。
在创建NativeInputManager的过程中,会创建属于JNI层真正地InputManager对象,并将创建的EventHub对象实例当做参数传入到其构造函数里面。(注意这儿传入的EventHub实例)。
Initialize函数
在InputManager构造函数里面,会分别创建InputDispatcher、InputReader对象的实例并将在创建InputDispatcherThread和InputReaderThread作为参数传入。此时需要注意传入InputReader构造函数里面的eventHub和mDispatcher。并且在创建InputDispatcher的时候,创建了一个属于自己的Looper实例。
此时,InputDispatcher和InputReader会通过InputDispatcherThread和InputReaderThread两个线程类来具体的完成事件的分发和读取。到此,InputManager启动完成。
应用程序注册键盘消息接收通道
Activity启动时,系统会为其创建一个ViewRoot实例,并通过其函数setView方法来将有关的view设置到ViewRoot中去,而Activity正是通过setView来注册消息接收通道的。
setView方法中会创建一个输入通道InputChannel的对象实例,并作为参数传入到接下来调用的Session类中的add方法中。
而该方法正是调用WindowManagerService类中的addWindow方法,且将InputChannel实例作为参数传入到该方法中。
此处调用openInputChannelPair方法来创建一个InputChannel类型的数组,而该方法的具体实现是在InputTransport.cpp内完成的。
在该方法中,先创建一个服务端的匿名共享内存,可读可写。并将复制一份用于客户端的匿名共享内存,然后通过调用InputChanel的构造函数对服务端和客户端的通道进行实例化。
其中服务端为反向管道读端与正向管道写端,客户端正相反。这样就可以使客户端和服务端两通道交叉连接,进行消息的传递。
继续回到WindowManagerService的addWindow方法里面,当创建了两个通道后,需要将各自的通道分别注册到客户端和服务端。
registerInputChannel方法即完成对服务端通道的注册。经过层层函数调用,最后是在InputDispatcher.cpp下的registerInputChannel函数完成具体的注册操作的。
首先将通道封装在一个Connection对象中,然后获得该通道的读端与Connection一起保存在InputDispatcher中,然后将获得的读端加入到Looper中。
在Looper类内部,会创建一个管道,然后Looper会睡眠在这个管道的读端,等待另外一个线程来往这个管道的写端写入新的内容,从而唤醒等待在这个管道读端的线程,除此之外,Looper还可以同时睡眠等待在其它的文件描述符上,因为它是通过Linux系统的epoll机制来批量等待指定的文件有新的内容可读的。这些其它的文件描述符就是通过Looper类的addFd成函数添加进去的了,在添加的时候,还可以指定回调函数,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个hanldeReceiveCallback函数。
在对服务端通道注册前,已经将在应用程序层创建的通道与客户端的通道关联起来。回到ViewRoot下的setView来注册客户端的通道。
具体的注册实现是在android_view_InputQueue.cpp的registerInputChannel来完成的,和服务端注册大致相同。此时的looper是客户端应用层的looper。应用层创建Looper的时候,会用到一个线程局部变量,sThreadLocal.set(new Looper()),意在保证每个线程内都有一个独立的Looper对象。因此此处的Looper.myQueue即为应用层的消息队列。
到此,服务端和客户端通道已注册完成。
InputManager分发touch消息给应用程序的过程
在前面的分析中,当InputManager启动完成以后,会在WindowManagerService的构造方法里面执行InputManager对象的start方法。
最后会调用InputReaderThread和InputDispatcherThread线程类的run函数来启动两个线程。分别调用其threadLoop函数。
InputReaderThread->threadLoop调用InputReader->loopOnce函数
InputDispatcherThread->threadLoop调用InputDispatcher->dispatchOnce函数
InputReader->loopOnce
此处的mEventHub即创建InputManager对象的时候,传入的EventHub实例,而EventHub是一个手机设备的文件描述。当手机设备有事件发生时如有键盘按下时,可以通过其函数getEvents函数获得该事件的具体描述和详细信息。
当有事件发生时,会调用processEventsLocked函数来处理,并将对事件信息的详细描述作为参数传入。
首先得到所触发事件的类型以及触发此事件的设备Id,然后分别作为参数传入到processEventsForDeviceLocked函数中。
processEventsForDeviceLocked:
通过deviceId来得到具体的描述当前设备的InputDevice的对象实例并调用其process函数。
手机设备中都有一个和其相匹配的mapper,如果添加一个新的设备,同时会给该设备添加一个mapper的描述,这样当某个设备发生事件时,可以根据与其相匹配的mapper来调用相应的process来处理,在此是以touch事件为例,所以会执行类型为TouchInputMapper的process函数处理相应的事件。
在对应的process里面调用sync,在sync函数下会对touch事件做进一步的处理,然后调用dispatchTouches将触摸事件往上分发。在dispatchTouches里面会对touch事件进行判断,分为三种事件类型,包括Down、Move和Up三种,不同的事件类型将以不同的参数调用dispatchMotion继续向上传递。
在dispatchMotion里面, touch事件封装在args里面并作为参数传给notifyMotion函数。
这里getListener得到的即为mQueuedListener,而在创建该实例时所传入的参数listener即为在创建InputReader对象实例时,传入的InputDispatcher实例。因此此处调用notifyMotion函数,即调用InputDispatcher.cpp下的notifyMotion函数。
InputDispatcher下的notifyMotion函数:
将封装事件的args里面的内容读出来,并重新封装到一个类型为newEntry的对象实例中,并通过调用enqueueInboundEventLocked将事件放入mInboundQueue队列里面。
InputDispatcherThread启动时,调用的是InputDispatcher的dispatchOnce,而此函数调用Looper的pollOnce函数,而当没有输入事件发生时,线程会一直睡在Looper所创管道的读端,此时唤醒即唤醒InputDispatcherThead线程。唤醒后,可以继续执行dispatchOnce函数,
调用该函数来完成进一步的操作,
把事件从队列mInboundQueue中取出来,然后赋给mPendingEvent,即将事件封装在了mPendingEvent中,并根据其事件类型(以touch事件为例)来调用相应的函数进行处理。
首先将mPendingEvent的类型转换,即将事件重新封装在一个类型为MotionEntry的对象实例中,并作为参数传入dispatchMotionLocked函数中去。
首先判断当前激活窗口是否存在,若不存在,需要先找出激活窗口,然后调用dispatchEventToCurrentInputTargetsLocked该函数将事件分发给当前激活窗口。
首先是得到当前的激活窗口,然后通过窗口所注册的inputChannel得到封装在Server端通道的Connection对象,最后调用prepareDispatchCycleLocked函数继续进行进一步处理,接着调用equeueDispatchEntriesLocked函数。
将事件封装成一个DispatchEntry对象,并将其添加到connection的outboundQueue中,表明当前有一个待处理的touch事件。最后调用startDispatchCycleLocked函数来继续分发事件。
首先将事件由队列中取出,将其封装到inputPublisher中,然后调用sendDispatchSignal函数来通知关联的Activity有事件需要处理。publishMotionEvent实际上是把事件的信息放入到一个共享内存中(mSharedMessage)这样该管道的反向读端和正向写端以及匿名内存信息都清楚了。
前面在创建管道的时候,Server端和Client端是公用一个匿名内存,管道的前向和反向只是通知相互通知有事件发生了,而真正的事件内容需要去匿名内存里面读取。
Server端通道写端有东西写入,则唤醒主线程进行读取。此时调用handleReceiveCallback函数。
handleReceiveCallback:
首先得到Client端封装通道的Connection对象,然后得到InputConsumer对象并调用receiveDispatchSignal判断是否收到输入事件消息的通知。如果收到了通知,调用consume函数。
前面已将事件的信息写入了匿名内存,调用consume把事件读出来然后保存在inputEvent。
将封装在Connection的InputHandle对象取出来。
得到java层dispatchMotionEvent函数的Id,把事件类型转换为本地类型然后调用CallStaticVoidMethod函数执行java层的dispatchMotionEvent。
InputQueue.java下的dispatchMotionEvent方法:
将事件封装成一个消息,放入消息队列中进行处理( DISPATCH_POINTER)。在handleMessage方法中,若消息类型为DISPATCH_POINTER,则调用deliverPointerEvent方法进行处理。
接着调用dispatchPointerEvent将event事件传给当前view,然后调用该view的dispatchTouchEvent接口继续向上分发event事件。
最后调用view的onTouchEvent()接口里面处理event事件,至此,event的传输流程已经讲完了。