码迷,mamicode.com
首页 > 移动开发 > 详细

Android4.0 input事件输入流程详解(中间层到应用层)

时间:2014-07-23 21:35:56      阅读:371      评论:0      收藏:0      [点我收藏+]

标签:android   style   http   java   color   os   

Android系统中,类似于键盘按键、触摸屏等事件是由WindowManagerService服务来管理的,然后再以消息的形式来分发给应用程序进行处理。系统启动时,窗口管理服务也会启动,该服务启动过程中,会通过系统输入管理器InputManager来负责监控键盘消息。当某一个Activity激活时,会在该Service下注册一个接收消息的通道,表明可以处理具体的消息,然后当有消息时,InputManager就会分发给当前处于激活状态下的Activity进行处理。

bubuko.com,布布扣

 

InputManager的启动过程

         InputManager负责事件的监控以及分发,而其启动需要WindowManagerService的启动来完成。而系统启动Win  dowManagerService的时候,会执行WindowManagerService.java文件的main方法。

         main方法下会创建一个线程类的实例,并执行其start方法,即调用该线程类下的run方法。run方法里面首先创建一个Looper消息循环,而looperAndroid系统里面处理消息的一种机制,然后调用WindowManagerService的构造方法来启动WindowManagerService

       最后调用Looperloop方法,将该线程添加到一个消息循环里面去,这样系统就可以持续的等待接收并处理到来的消息。

bubuko.com,布布扣

          

        WindowManagerService的构造方法里,调用InputManager的构造方法创建一个InputManager对象的实例,并将WindowManagerService对象作为参数传入构造方法里面。在InputManager的构造方法里面得到前面创建的looper对象并将其内部的消息队列作为参数传给JNI层的nativeInit函数里面。

        nativeInit函数的实现位于JNI层的com_android_server_InputManager.cpp里面。该函数下主要做两件事情。

bubuko.com,布布扣

        ◆ 通过java层传入的looper消息队列得到和JNI层的looper对象,是其对应起来

       ◆同样为使和java层的InputManager对象对应起来,创建了本地的InputManager对象实例即NativeInputManager

bubuko.com,布布扣

        在创建NativeInputManager的过程中,会创建属于JNI层真正地InputManager对象,并将创建的EventHub对象实例当做参数传入到其构造函数里面。(注意这儿传入的EventHub实例)

bubuko.com,布布扣

       Initialize函数

bubuko.com,布布扣

        InputManager构造函数里面,会分别创建InputDispatcherInputReader对象的实例并将在创建InputDispatcherThreadInputReaderThread作为参数传入。此时需要注意传入InputReader构造函数里面的eventHubmDispatcher。并且在创建InputDispatcher的时候,创建了一个属于自己的Looper实例。

        此时,InputDispatcherInputReader会通过InputDispatcherThreadInputReaderThread两个线程类来具体的完成事件的分发和读取。到此,InputManager启动完成。

 

应用程序注册键盘消息接收通道

           Activity启动时,系统会为其创建一个ViewRoot实例,并通过其函数setView方法来将有关的view设置到ViewRoot中去,而Activity正是通过setView来注册消息接收通道的。

bubuko.com,布布扣

         setView方法中会创建一个输入通道InputChannel的对象实例,并作为参数传入到接下来调用的Session类中的add方法中。

bubuko.com,布布扣

        而该方法正是调用WindowManagerService类中的addWindow方法,且将InputChannel实例作为参数传入到该方法中。

bubuko.com,布布扣

        此处调用openInputChannelPair方法来创建一个InputChannel类型的数组,而该方法的具体实现是在InputTransport.cpp内完成的。

bubuko.com,布布扣

         在该方法中,先创建一个服务端的匿名共享内存,可读可写。并将复制一份用于客户端的匿名共享内存,然后通过调用InputChanel的构造函数对服务端和客户端的通道进行实例化。

       其中服务端为反向管道读端与正向管道写端,客户端正相反。这样就可以使客户端和服务端两通道交叉连接,进行消息的传递。

bubuko.com,布布扣

        继续回到WindowManagerServiceaddWindow方法里面,当创建了两个通道后,需要将各自的通道分别注册到客户端和服务端。

bubuko.com,布布扣

       registerInputChannel方法即完成对服务端通道的注册。经过层层函数调用,最后是在InputDispatcher.cpp下的registerInputChannel函数完成具体的注册操作的。

bubuko.com,布布扣

        首先将通道封装在一个Connection对象中,然后获得该通道的读端与Connection一起保存在InputDispatcher中,然后将获得的读端加入到Looper中。

       在Looper类内部,会创建一个管道,然后Looper会睡眠在这个管道的读端,等待另外一个线程来往这个管道的写端写入新的内容,从而唤醒等待在这个管道读端的线程,除此之外,Looper还可以同时睡眠等待在其它的文件描述符上,因为它是通过Linux系统的epoll机制来批量等待指定的文件有新的内容可读的。这些其它的文件描述符就是通过Looper类的addFd成函数添加进去的了,在添加的时候,还可以指定回调函数,即当这个文件描述符所指向的文件有新的内容可读时,Looper就会调用这个hanldeReceiveCallback函数。

bubuko.com,布布扣

          在对服务端通道注册前,已经将在应用程序层创建的通道与客户端的通道关联起来。回到ViewRoot下的setView来注册客户端的通道。

bubuko.com,布布扣

        具 体的注册实现是在android_view_InputQueue.cpp的registerInputChannel来完成的,和服务端注册大致相同。 此时的looper是客户端应用层的looper。应用层创建Looper的时候,会用到一个线程局部变量,sThreadLocal.set(new  Looper()),意在保证每个线程内都有一个独立的Looper对象。因此此处的Looper.myQueue即为应用层的消息队列。       

bubuko.com,布布扣

           到此,服务端和客户端通道已注册完成。

 

 InputManager分发touch消息给应用程序的过程

           在前面的分析中,当InputManager启动完成以后,会在WindowManagerService的构造方法里面执行InputManager对象的start方法。

bubuko.com,布布扣

         最后会调用InputReaderThreadInputDispatcherThread线程类的run函数来启动两个线程。分别调用其threadLoop函数。

         InputReaderThread->threadLoop调用InputReader->loopOnce函数

         InputDispatcherThread->threadLoop调用InputDispatcher->dispatchOnce函数

         InputReader->loopOnce

bubuko.com,布布扣

         此处的mEventHub即创建InputManager对象的时候,传入的EventHub实例,而EventHub是一个手机设备的文件描述。当手机设备有事件发生时如有键盘按下时,可以通过其函数getEvents函数获得该事件的具体描述和详细信息。

bubuko.com,布布扣

         当有事件发生时,会调用processEventsLocked函数来处理,并将对事件信息的详细描述作为参数传入。

bubuko.com,布布扣

        首先得到所触发事件的类型以及触发此事件的设备Id,然后分别作为参数传入到processEventsForDeviceLocked函数中。

         processEventsForDeviceLocked

bubuko.com,布布扣

        通过deviceId来得到具体的描述当前设备的InputDevice的对象实例并调用其process函数。

bubuko.com,布布扣

         手机设备中都有一个和其相匹配的mapper,如果添加一个新的设备,同时会给该设备添加一个mapper的描述,这样当某个设备发生事件时,可以根据与其相匹配的mapper来调用相应的process来处理,在此是以touch事件为例,所以会执行类型为TouchInputMapperprocess函数处理相应的事件。

bubuko.com,布布扣

         在对应的process里面调用sync,在sync函数下会对touch事件做进一步的处理,然后调用dispatchTouches将触摸事件往上分发。在dispatchTouches里面会对touch事件进行判断,分为三种事件类型,包括DownMoveUp三种,不同的事件类型将以不同的参数调用dispatchMotion继续向上传递。

bubuko.com,布布扣

bubuko.com,布布扣

         dispatchMotion里面, touch事件封装在args里面并作为参数传给notifyMotion函数。

bubuko.com,布布扣

           这 里getListener得到的即为mQueuedListener,而在创建该实例时所传入的参数listener即为在创建InputReader对 象实例时,传入的InputDispatcher实例。因此此处调用notifyMotion函数,即调用InputDispatcher.cpp下的 notifyMotion函数。

       InputDispatcher下的notifyMotion函数:

bubuko.com,布布扣

         将封装事件的args里面的内容读出来,并重新封装到一个类型为newEntry的对象实例中,并通过调用enqueueInboundEventLocked将事件放入mInboundQueue队列里面。

bubuko.com,布布扣

         InputDispatcherThread启动时,调用的是InputDispatcherdispatchOnce,而此函数调用LooperpollOnce函数,而当没有输入事件发生时,线程会一直睡在Looper所创管道的读端,此时唤醒即唤醒InputDispatcherThead线程。唤醒后,可以继续执行dispatchOnce函数,

bubuko.com,布布扣

        调用该函数来完成进一步的操作,

bubuko.com,布布扣

         把事件从队列mInboundQueue中取出来,然后赋给mPendingEvent,即将事件封装在了mPendingEvent中,并根据其事件类型(以touch事件为例)来调用相应的函数进行处理。

bubuko.com,布布扣

        首先将mPendingEvent的类型转换,即将事件重新封装在一个类型为MotionEntry的对象实例中,并作为参数传入dispatchMotionLocked函数中去。

bubuko.com,布布扣

        首先判断当前激活窗口是否存在,若不存在,需要先找出激活窗口,然后调用dispatchEventToCurrentInputTargetsLocked该函数将事件分发给当前激活窗口。

bubuko.com,布布扣

        首先是得到当前的激活窗口,然后通过窗口所注册的inputChannel得到封装在Server端通道的Connection对象,最后调用prepareDispatchCycleLocked函数继续进行进一步处理,接着调用equeueDispatchEntriesLocked函数。

bubuko.com,布布扣

         将事件封装成一个DispatchEntry对象,并将其添加到connection的outboundQueue中,表明当前有一个待处理的touch事件。最后调用startDispatchCycleLocked函数来继续分发事件。

bubuko.com,布布扣

bubuko.com,布布扣

        首先将事件由队列中取出,将其封装到inputPublisher中,然后调用sendDispatchSignal函数来通知关联的Activity有事件需要处理。publishMotionEvent实际上是把事件的信息放入到一个共享内存中(mSharedMessage)这样该管道的反向读端和正向写端以及匿名内存信息都清楚了。

bubuko.com,布布扣

        前面在创建管道的时候,Server端和Client端是公用一个匿名内存,管道的前向和反向只是通知相互通知有事件发生了,而真正的事件内容需要去匿名内存里面读取。

bubuko.com,布布扣

bubuko.com,布布扣

         Server端通道写端有东西写入,则唤醒主线程进行读取。此时调用handleReceiveCallback函数。

         handleReceiveCallback

bubuko.com,布布扣

         首先得到Client端封装通道的Connection对象,然后得到InputConsumer对象并调用receiveDispatchSignal判断是否收到输入事件消息的通知。如果收到了通知,调用consume函数。

bubuko.com,布布扣

 前面已将事件的信息写入了匿名内存,调用consume把事件读出来然后保存在inputEvent

bubuko.com,布布扣

            将封装在Connection的InputHandle对象取出来。

bubuko.com,布布扣

         得到javadispatchMotionEvent函数的Id,把事件类型转换为本地类型然后调用CallStaticVoidMethod函数执行java层的dispatchMotionEvent

bubuko.com,布布扣

        InputQueue.java下的dispatchMotionEvent方法:

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

         将事件封装成一个消息,放入消息队列中进行处理( DISPATCH_POINTER)。在handleMessage方法中,若消息类型为DISPATCH_POINTER,则调用deliverPointerEvent方法进行处理。

bubuko.com,布布扣

         接着调用dispatchPointerEventevent事件传给当前view,然后调用该viewdispatchTouchEvent接口继续向上分发event事件。

bubuko.com,布布扣

bubuko.com,布布扣

bubuko.com,布布扣

         最后调用viewonTouchEvent()接口里面处理event事件,至此,event的传输流程已经讲完了。


Android4.0 input事件输入流程详解(中间层到应用层),布布扣,bubuko.com

Android4.0 input事件输入流程详解(中间层到应用层)

标签:android   style   http   java   color   os   

原文地址:http://my.oschina.net/u/994235/blog/294227

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!