Android4.0 input事件輸入流程詳解(中間層到應用層)

Android系統中,相似於鍵盤按鍵、觸摸屏等事件是由WindowManagerService服務來管理的,而後再以消息的形式來分發給應用程序進行處理。系統啓動時,窗口管理服務也會啓動,該服務啓動過程當中,會經過系統輸入管理器InputManager來負責監控鍵盤消息。當某一個Activity激活時,會在該Service下注冊一個接收消息的通道,代表能夠處理具體的消息,而後當有消息時,InputManager就會分發給當前處於激活狀態下的Activity進行處理。java

 

InputManager的啓動過程android

         InputManager負責事件的監控以及分發,而其啓動須要WindowManagerService的啓動來完成。而系統啓動Win  dowManagerService的時候,會執行WindowManagerService.java文件的main方法。數組

         main方法下會建立一個線程類的實例,並執行其start方法,即調用該線程類下的run方法。run方法裏面首先建立一個Looper消息循環,而looperAndroid系統裏面處理消息的一種機制,而後調用WindowManagerService的構造方法來啓動WindowManagerServiceapp

       最後調用Looperloop方法,將該線程添加到一個消息循環裏面去,這樣系統就能夠持續的等待接收並處理到來的消息。函數

          

        WindowManagerService的構造方法裏,調用InputManager的構造方法建立一個InputManager對象的實例,並將WindowManagerService對象做爲參數傳入構造方法裏面。在InputManager的構造方法裏面獲得前面建立的looper對象並將其內部的消息隊列做爲參數傳給JNI層的nativeInit函數裏面。oop

        nativeInit函數的實現位於JNI層的com_android_server_InputManager.cpp裏面。該函數下主要作兩件事情。spa

        ◆ 經過java層傳入的looper消息隊列獲得和JNI層的looper對象,是其對應起來.net

       ◆一樣爲使和java層的InputManager對象對應起來,建立了本地的InputManager對象實例即NativeInputManager線程

        在建立NativeInputManager的過程當中,會建立屬於JNI層真正地InputManager對象,並將建立的EventHub對象實例當作參數傳入到其構造函數裏面。(注意這兒傳入的EventHub實例)server

       Initialize函數

        InputManager構造函數裏面,會分別建立InputDispatcherInputReader對象的實例並將在建立InputDispatcherThreadInputReaderThread做爲參數傳入。此時須要注意傳入InputReader構造函數裏面的eventHubmDispatcher。而且在建立InputDispatcher的時候,建立了一個屬於本身的Looper實例。

        此時,InputDispatcherInputReader會經過InputDispatcherThreadInputReaderThread兩個線程類來具體的完成事件的分發和讀取。到此,InputManager啓動完成。

 

應用程序註冊鍵盤消息接收通道

           Activity啓動時,系統會爲其建立一個ViewRoot實例,並經過其函數setView方法來將有關的view設置到ViewRoot中去,而Activity正是經過setView來註冊消息接收通道的。

         setView方法中會建立一個輸入通道InputChannel的對象實例,並做爲參數傳入到接下來調用的Session類中的add方法中。

        而該方法正是調用WindowManagerService類中的addWindow方法,且將InputChannel實例做爲參數傳入到該方法中。

        此處調用openInputChannelPair方法來建立一個InputChannel類型的數組,而該方法的具體實現是在InputTransport.cpp內完成的。

         在該方法中,先建立一個服務端的匿名共享內存,可讀可寫。並將複製一份用於客戶端的匿名共享內存,而後經過調用InputChanel的構造函數對服務端和客戶端的通道進行實例化。

       其中服務端爲反向管道讀端與正向管道寫端,客戶端正相反。這樣就可使客戶端和服務端兩通道交叉鏈接,進行消息的傳遞。

        繼續回到WindowManagerServiceaddWindow方法裏面,當建立了兩個通道後,須要將各自的通道分別註冊到客戶端和服務端。

       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方法。

         最後會調用InputReaderThreadInputDispatcherThread線程類的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事件爲例,因此會執行類型爲TouchInputMapperprocess函數處理相應的事件。

         在對應的process裏面調用sync,在sync函數下會對touch事件作進一步的處理,而後調用dispatchTouches將觸摸事件往上分發。在dispatchTouches裏面會對touch事件進行判斷,分爲三種事件類型,包括DownMoveUp三種,不一樣的事件類型將以不一樣的參數調用dispatchMotion繼續向上傳遞。

         dispatchMotion裏面, touch事件封裝在args裏面並做爲參數傳給notifyMotion函數。

           這 裏getListener獲得的即爲mQueuedListener,而在建立該實例時所傳入的參數listener即爲在建立InputReader對 象實例時,傳入的InputDispatcher實例。所以此處調用notifyMotion函數,即調用InputDispatcher.cpp下的 notifyMotion函數。

       InputDispatcher下的notifyMotion函數:

         將封裝事件的args裏面的內容讀出來,並從新封裝到一個類型爲newEntry的對象實例中,並經過調用enqueueInboundEventLocked將事件放入mInboundQueue隊列裏面。

         InputDispatcherThread啓動時,調用的是InputDispatcherdispatchOnce,而此函數調用LooperpollOnce函數,而當沒有輸入事件發生時,線程會一直睡在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對象取出來。

         獲得javadispatchMotionEvent函數的Id,把事件類型轉換爲本地類型而後調用CallStaticVoidMethod函數執行java層的dispatchMotionEvent

        InputQueue.java下的dispatchMotionEvent方法:

         將事件封裝成一個消息,放入消息隊列中進行處理( DISPATCH_POINTER)。在handleMessage方法中,若消息類型爲DISPATCH_POINTER,則調用deliverPointerEvent方法進行處理。

         接着調用dispatchPointerEventevent事件傳給當前view,而後調用該viewdispatchTouchEvent接口繼續向上分發event事件。

         最後調用viewonTouchEvent()接口裏面處理event事件,至此,event的傳輸流程已經講完了。

相關文章
相關標籤/搜索