在Android系統中,相似於鍵盤按鍵、觸摸屏等事件是由WindowManagerService服務來管理的,而後再以消息的形式來分發給應用程序進行處理。系統啓動時,窗口管理服務也會啓動,該服務啓動過程當中,會經過系統輸入管理器InputManager來負責監控鍵盤消息。當某一個Activity激活時,會在該Service下注冊一個接收消息的通道,代表能夠處理具體的消息,而後當有消息時,InputManager就會分發給當前處於激活狀態下的Activity進行處理。java
InputManager的啓動過程android
InputManager負責事件的監控以及分發,而其啓動須要WindowManagerService的啓動來完成。而系統啓動Win dowManagerService的時候,會執行WindowManagerService.java文件的main方法。數組
main方法下會建立一個線程類的實例,並執行其start方法,即調用該線程類下的run方法。run方法裏面首先建立一個Looper消息循環,而looper是Android系統裏面處理消息的一種機制,而後調用WindowManagerService的構造方法來啓動WindowManagerService。app
最後調用Looper的loop方法,將該線程添加到一個消息循環裏面去,這樣系統就能夠持續的等待接收並處理到來的消息。函數
在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構造函數裏面,會分別建立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的傳輸流程已經講完了。