Android是linux內核的,因此它的事件處理系統也在linux的基礎上完成的。java
Linux內核提供了一個Input子系統來實現的,Input子系統會在/dev/input/路徑下建立咱們硬件輸入設備的節點,通常狀況下在咱們的手機中這些節點是以eventX來命名的,如event0,event1等等,可是若是是虛擬機的話,咱們能夠看到一個mice,這個mice表明鼠標設備,這是因爲PC須要使用鼠標來模擬觸屏。linux
因爲這些設備節點是硬件相關的,因此每款設備都是不盡相同的。android
看到了這些輸入的設備節點,咱們可能比較困惑這些eventX到底表明什麼含義呢,也就是說究竟是什麼樣的設備建立了這個節點呢?架構
咱們能夠從/proc/bus/input/devices中讀出eventX相關的硬件設備,具體的就很少說了,咱們知道android讀取事件信息就是從/dev/input/目錄下的設備節點中讀取出來的,算是android事件處理的起源。app
Android事件傳遞的流程,按鍵,觸屏等事件是經由WindowManagerService獲取,並經過共享內存和管道的方式傳遞給ViewRoot,ViewRoot再dispatch給Application的View。當有事件從硬件設備輸入時,system_server端在檢測到事件發生時,經過管道(pipe)通知ViewRoot事件發生,此時ViewRoot再去的內存中讀取這個事件信息。ide
至於android在事件處理上爲何使用共享內存而不是直接使用Binder機制,猜想應該是google爲了保證事件響應的實時性,所以在選擇進程間傳遞事件的方式中,選擇了高的共享內存的方式,因爲共享內存在數據管理過程當中基本不涉及到內存的數據拷貝,只是在進程讀寫時涉及到2次數據拷貝,這個是不可避免的數據拷貝,所以這種方式可以很好的保證系統對事件的響應,可是僅僅是共享內存是不夠的,由於共享內存的通訊方式並不可以通知對方有數據更新,所以android在事件處理過程當中加入了另外一種進程間通訊方式管道(pipe),管道的效率不如共享內存高,會不會影響事件處理的實時性?不要緊,每次system_serve通知ViewRoot只是向其傳遞一個字符,即輕巧又簡單。函數
瞭解了一些基本知識後,如今從底層往上層來分析事件的傳遞過程。oop
首先列出整個事件處理的結構圖性能
Android事件傳遞系統是以共享內存和管道的進程間通訊方式來實現傳遞的,爲了便於理解它的傳遞機制,事件傳遞系統的初始化工做的理解則會顯得很是的重要。ui
件傳遞系統中的管道的主要做用是在有事件被存儲到共享內存中時,system_server端通知ViewRoot去讀取事件的通訊機制。
既然是ViewRoot和system_server之間創建管道通訊,那麼ViewRoot和WindowManagerService(負責事件傳遞,運行在system_server進程中)各需維護管道的一個文件描述符,其實ViewRoot和WindowManagerService不是各維護了一個管道的文件描述符,而是兩個,固然了這兩個描述符不屬於同一管道,實際上也就是ViewRoot和WindowManagerService之間實現了全雙工的管道通訊。
ViewRoot和WindowManagerService的管道的文件描述符都是被存儲在一個名爲InputChannel的類中,這個InputChannel類是管道通訊的載體。
ViewRoot端的管道的創建
//setView()@ViewRoot.java requestLayout(); mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, getHostVisibility(), mAttachInfo.mContentInsets, mInputChannel); } catch (RemoteException e) {
在ViewRoot和WMS(WindowManagerService)創建起鏈接以前首先會建立一個InputChannel對象,一樣的WMS端也會建立一個InputChannel對象,不過WMS的建立過程是在ViewRoot調用add()方法時調用的。InputChannel的構造不作任何操做,因此在ViewRoot中建立InputChannel時還沒有初始化,它的初始化過程是在調用WMS方法add()時進行的,看到上面代碼中將mInputChannel做爲參數傳遞給WMS,目的就是爲了初始化。
下面轉到WMS代碼看看InputChannel的初始化過程
//addWindow()@WindowManagerService.java if (outInputChannel != null) { String name = win.makeInputChannelName(); InputChannel[] inputChannels = InputChannel.openInputChannelPair(name); win.mInputChannel = inputChannels[0]; inputChannels[1].transferToBinderOutParameter(outInputChannel); mInputManager.registerInputChannel(win.mInputChannel); }
OutInputChannel爲ViewRoot傳遞來的InputChannel對象,上述代碼主要工做其實就是建立一對InputChannel,這一對InputChannel中實現了一組全雙工管道。
在建立InputChannel對的同時,會申請共享內存,並向2個InputChannel對象中各自保存一個共享內存的文件描述符。
InputChannel建立完成後,會將其中一個的native InputChannel 賦值給outInputChannel,也就是對ViewRoot端InputChannel對象的初始化,這樣隨着ViewRoot和WMS兩端的InputChannel對象的建立,事件傳輸系統的管道通訊也就創建了起來。
建立InputChannel pair的過程以及管道創建,共享內存申請的過程就再也不列出它的代碼了,請參考openInputChannelPair()@InputTransport.cpp。
下圖爲ViewRoot和WMS兩端建立InputChannel pair以後的結構
上面介紹了InputChannel對象的建立過程,這個過程將管道通訊創建了起來,可是咱們須要清楚的一點是,一個管道通訊只是對應一個Activity的事件處理,也就是當前系統中有多少個Activity就會有多少個全雙工管道,那麼系統須要一個管理者來管理以及調度每個管道通訊,所以咱們在建立完InputChannel對象後,須要將其註冊到這個管理者中去。
明白了InputChannel對象須要註冊的緣由以後,再看ViewRoot和WMS端的InputChannel對象各自須要註冊到哪裏?
其實也很好理解,兩個InputChannel對象WMS端的是管道通訊的sender, ViewRoot端的是Receiver(儘管建立的全雙工,可是目前只使用到了它的一貫的通訊,另外一方向的通訊還沒有使用),那麼着兩個InputChannel對象確定須要被兩個不一樣的管理者來管理。
ViewRoot端的通常狀況下會註冊到一個NativeInputQueue對象中;WMS端註冊在InputManager對象中。其實從NativeInputQueue和InputManager的名字中也就能知道各自的功能了。
ViewRoot端InputChannel對象在向NativeInputQueue註冊時,須要註冊3個參數:
Android在實現事件傳輸時,很大程度上借用了線程Looper和MessageQueue的輪詢(poll)機制,經過它的輪詢機制來檢測管道上是否有消息通知事件發生,借用Looper機制可以很大限度的保證事件可以第一時間被Application知曉。
在註冊過程當中,android會將InputChannel對象中保存的管道的文件描述符交給MessageQueue的native looper去監聽,同時向native looper指示一個回調函數,一旦有事件發生,native looper就會檢測到管道上的數據,同時會去調用指示的回調函數。這個回調函數爲handleReceiveCallback()@android_view_InputQueue.cpp.
固然了,NativeInputQueue對象,整個系統中只有一個,它爲了負責管理這麼多的Application的事件傳遞,android在NativeInputQueue類中定義了一個子類Connection,每一個InputChannel對象在註冊時都會建立一個本身的Connection對象。
這一塊的代碼在registerInputChannel()@android_view_InputQueue.cpp
因爲WMS端的對linux Input 系統的檢測和ViewRoot對管道接收端的檢測機制不一樣,ViewRoot端很好的複用了Application 主線程的Looper輪詢機制來實現對事件響應的實時性,而WMS儘管也有本身的Looper,WMS卻沒像ViewRoot同樣複用本身的Looper機制,至於緣由android的code上沒有明確說明,猜想應該是WMS是整個系統的,不像ViewRoot同樣每一個Activity都有一套,爲了避免影響系統的總體性能,儘可能不要去影響WMS。
不採用Looper來輪詢是否有事件發生,InputManager啓動了2個進程來管理事件發生與傳遞,InputReaderThread和InputDispatcherThread,InputReaderThread進程負責輪詢事件發生; InputDispatcherThread負責dispatch事件。
爲何須要2個進程來管理,用一個會出現什麼問題?很明顯,若是用一個話,在輪詢input系統event的時間間隔會變長,有可能丟失事件。
雖然沒有使用Looper來輪詢事件的發生,可是InputDispatcher使用了native looper來輪詢檢查管道通訊,這個管道通訊表示InputQueue是否消化完成dispatch過去的事件。
注意的是這個native looper並非WMS線程的,而是線程InputDispatcher自定定義的,所以全部的輪詢過程,須要InputDispatcher主動去調用,如
mLooper->pollOnce(timeoutMillis);或者mLooper->wake();。而不像NativeInputQueue同樣,徹底不用操心對looper的操做。
WMS在初始化時會建立這麼一個InputManager實例,固然了,它也是系統惟一的。
JAVA層的InputManager實例並無實現太多的業務,真正實現Input Manager業務是Native的NativeInputManager實例,它在被建立時,創建起了整個WMS端事件傳遞系統的靜態邏輯。
整個WMS端事件傳遞系統的靜態邏輯:
NativeInputManager的整個業務的核心實際上是InputReader和InputDispatcher兩個模塊,下面簡單介紹一下這兩個模塊。
InputReader從名稱就能夠看出主要任務是讀事件,基本上它全部的業務都包含在了process()的函數中
void InputReader::process(const RawEvent* rawEvent) { switch (rawEvent->type) { case EventHubInterface::DEVICE_ADDED: addDevice(rawEvent->deviceId); break; case EventHubInterface::DEVICE_REMOVED: removeDevice(rawEvent->deviceId); break; case EventHubInterface::FINISHED_DEVICE_SCAN: handleConfigurationChanged(rawEvent->when); break; default: consumeEvent(rawEvent); break; } }
process()函數的輸入參數時EventHub模塊提供的,
- 當EventHub還沒有打開input系統eventX設備時,InputReader去向EventHub獲取事件時,EventHub會首先去打開全部的設備,並將每一個設備信息以RawEvent的形式返給InputReader,也就是process()中處理的EventHubInterface::DEVICE_ADDED類型,該過程會根據每一個設備的deviceId去建立InputDevice,並根據設備的classes來建立對應的InputMapper。如上圖所示。
- 當全部的設備均被打開以後,InputReader去向EventHub獲取事件時,EventHub回去輪詢event節點,若是有事件,InputReader則會消化該事件consumeEvent(rawEvent);
數據傳輸管理的核心業務是在InputDispatcher中完成的,所以最終WMS端InputChannel對象會註冊到InputDispatcher中,一樣的因爲整個系統中InputDispatcher實例只有一個,而WMS端InputChannel對象是和ViewRoot一一對應的。所以InputDispatcher類中也定義了一個內部類Connect來管理各自的InputChannel對象。不一樣於NativeInputQueue類中的Connect類,InputDispatcher中的Connect類的核心業務是由InputPublisher對象來實現的,該對象負責將發生的事件信息寫入到共享內存。
相關代碼在registerInputChannel()@InputDispatcher.cpp
通過分析事件處理系統的初始化過程以後,咱們已經對事件處理系統的總體架構有了必定程度的理解,那麼下面看看事件傳遞過程。
當input系統有事件發生時,會被InputReaderThread線程輪詢到,InputReader會根據事件的device id來選擇的InputDevice,而後再根據事件的類型來選擇InputDevice中的InputMapper,InputMapper會將事件信息通知給InputDispatcher;
目前adroid在InputReader中實現了5種設備類型的InputMapper,分別爲滑蓋/翻蓋SwitchInputMapper、鍵盤KeyboardInputMapper、軌跡球TrackballInputMapper、多點觸屏MultiTouchInputMapper以及單點觸屏SingleTouchInputMapper。
設備類型 |
InputManager |
EventType |
Notify InputDispatcher |
滑蓋/翻蓋 |
SwitchInputMapper |
EV_SW |
notifySwitch() |
鍵盤 |
KeyboardInputMapper |
EV_KEY |
notifyKey() |
軌跡球 |
TrackballInputMapper |
EV_KEY, EV_REL, EV_SYN |
notifyMotion() |
單點觸屏 |
SingleTouchInputMapper |
EV_KEY, EV_ABS, EV_SYN |
notifyMotion() |
多點觸屏 |
MultiTouchInputMapper |
EV_ABS, EV_SYN |
notifyMotion()
|
其中事件類型表明:
Notify InputDispatcher表示不一樣的事件通知InputDispatcher的函數調用,這幾個函數雖然是被InputReaderThread調用的,單倒是在InputDispatcher定義的。
InputDispatcherThread線程的輪詢過程dispatchOnce()-->dispatchOnceInnerLocked()。
InputDispatcherThread線程不停的執行該操做,以達到輪詢的目的,重點也就是這2個函數處理了。
InputDispatcherThread的主要操做是分兩塊同時進行的,
一部分是對InputReader傳遞過來的事件進行dispatch前處理,好比肯定focus window,特殊按鍵處理如HOME/ENDCALL等,在預處理完成 後,InputDispatcher會將事件存儲到對應的focus window的outBoundQueue,這個outBoundQueue隊列是InputDispatcher::Connection的成員函數,所以它是和ViewRoot相關的。
一部分是對looper的輪詢,這個輪詢過程是檢查NativeInputQueue是否處理完成上一個事件,若是NativeInputQueue處理完成事件,它就會向經過管道向InputDispatcher發送消息指示consume完成,只有NativeInputQueue consume完成一個事件,InputDispatcher纔會向共享內存寫入另外一個事件。
首先從Kernel傳遞上來的鍵值由EventHub進行轉碼,以後由InputReader將其解釋成各個事件,再由InputDispatcher分發。
InputManager是InputReader和InputDispatcher線程的建立者,它只有一個職責,就是被WindowManagerService使用,從Native層獲取按鍵事件。
WindowManagerService則負責與窗口對接,分發按鍵消息。
事件分發給最前面的窗口:
/frameworks/base/services/java/com/android/server/WindowManagerService.java
攔截消息的處理類:
/frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
按鍵事件定義:
/frameworks/base/core/java/android/view/KeyEvent.java
Java層輸入管理:
/frameworks/base/services/java/com/android/server/InputManager.Java
native層輸入管理:
/frameworks/base/libs/ui/InputManager.cpp
事件讀取線程:
/frameworks/base/libs/ui/InputReader.cpp
事件分發線程:
/frameworks/base/libs/ui/InputDispatcher.cpp
鍵碼與鍵值轉換:/frameworks/base/libs/ui/EventHub.cpp