注:如下內容基於Android API Version 27(Android 8.1)Linux Kernel 3.18.0android
Android的input事件接收和派發發生在WMS
(WindowManagerService
)所在進程,也就是system_server
進程,input系統在WMS
端有兩個關鍵線程:讀取線程和派發線程。讀取線程掃描輸入設備並從設備中主動讀取輸入事件,而後將事件交給派發線程。App進程向WMS
添加窗口時WMS
會建立一個事件通道(InputChannel
),通道的一頭返回給App進程,一頭註冊到input派發線程,同時WMS
會將當前的全部窗口的層級和大小等信息傳給input派發線程,input派發線程收到事件後查看當前focused
窗口或者能夠處理當前事件的窗口,而後經過事件通道直接將事件派發給窗口,App進程將從WMS
返回的事件通道的FD(文件描述符)添加到本身主線程Looper
中,收到輸入事件後由主線程直接處理事件。api
input系統在Java層表明是IMS
(InputManagerService
),IMS
隨着system_server
進程啓動,IMS
啓動過程當中會啓動兩個線程,一個事件讀取線程(InputReaderThread
),一個事件派發線程(InputDispatcherThread
)。緩存
WMS
直接使用IMS
,WMS
經過IMS
向事件派發線程傳遞窗口信息並設置當前Focused
的window
,WMS
同時也負責建立和註冊system_server
進程與App進程傳遞事件的通道:InputChannel
。WMS
向IMS
註冊事件發送端InputChannel
,IMS
最終調用到了Native層的InputManager
。markdown
應用程序經過Java層的InputManager
訪問IMS
,Java層的InputManager
包裝了IMS
的binder
代理對象。併發
IMS
持有Native層的NativeInputManager
,NativeInputManager
持有Native的InputManger
。socket
Native層的InputManager
包含了一個InputDispatcher
和一個InputReader
,InputReader
包含了一個EventHub
。ide
InputDispatcher
繼承了InputListenerInterface
,實現了notifyKey
和notifyMotion
等方法。建立InputReader
時將InputDispatcher
傳給了InputReader
。InputReader
以InputListenerInterface
類型持有InputDispatcher
。函數
InputReader
有一個對應的InputReaderThread
線程。在InputReaderThread
裏循環調用EventHub
獲取事件。工具
EventHub
負責打開/dev/input
目錄下的全部設備,和經過inotify
機制監聽/dev/input
下面的設備文件變化,並負責讀取全部的FD
。oop
EventHub
使用epoll
系統調用監控inotify
和設備文件FD的可讀事件,當某個設備可讀時,從epoll_wait
返回,緊接着讀取有數據的設備的事件數據並將數據返回給InputReader
,InputReader
將原始的生事件加工成熟事件後交給InputDispather
。
InputDispatcher
對應了一個InputDispatcherThread
線程,在InputDispatcherThread
使用Native層的Looper
等待派發出去的事件的迴應,同時若是InputReader
有事件會調用InputDispatcher
將事件添加到一個隊列中(mInboundQueue
)並喚醒InputDispatcherThread
,InputDispatcherThread
被喚醒後調用InputDispatcher
開始派發事件隊列中的事件。
事件派發過程是先找到當前的window
,而後根據window
找到Connection
,而後將事件加到Connection
的outboundQueue
, 而後從outboundQueue
隊頭取一個消息,調用Connection
的InputPublisher
發送事件,InputPublisher
最終會調用InputChannel
,InputChannel
用本身保存的FD
調用socketpair
的senMsg
函數將事件發出。
一個window
對應一個InputChannel
對應一個Connection
。
發完事件後,將這個消息記錄到Connection
的waitQueue
的隊尾。InputDispatcherThread
再次等待在Looper
上,等App窗口消費完事件併發送finish事件後,InputDispatcherThread
就會被喚醒,而後根據發生消息的FD
(一個窗口對應一個FD
)找到Connection
,再根據事件的序列號(seq
)找到事件而後將事件從waitQueue
移除,並繼續派發屬於這個Connction
的消息。
App窗口在調用WMS
的addWindow
時,WMS
會爲App窗口創建一對InputChannel
,InputChannel
基於socketpair
(socketpair
的兩端是對等的,沒有server和client之分),一端給InputDispatcher
使用,一端返回給App進程用來接收事件,WMS
會將服務端的InputChannel
註冊到InputDispatcher
,這樣InputDispather
就能夠用來給窗口發送事件並接收窗口事件了,接收事件的原理是將InputChannel
的FD
加入到InputDispather
的Looper
中,由於InputDispatherThread
阻塞在Looper.pollOnce
上,當InputDispather
收到窗口發來的finish事件後InputDispatherThread
會被喚醒,而後由InputDistather
處理finish消息。
App進程獲取到InputChannel
後將以內部的socketpair
的FD
加入到main looper的FD
監聽列表中去,後續若是收到事件,事件的處理會直接發生在主線程,main looper監聽到FD
上有數據後回調FD
綁定的回調函數,回調函數將事件讀出來封裝成對應的Event對象,而後層層傳遞到ViewRootImpl
。ViewRootImpl
經過一個責任鏈決定事件的處理順序和方式,某些事件可能會先派發給輸入法窗口進行消費,若是輸入法窗口不消費就繼續派發給view tree消費,派發給view tree是直接派發的,由於這時已經在主線程了,流程大體是: ViewRootImpl -> DecorView -> Activity -> View(DecorView) -> DecorView的子View
若是App進程沒有消費事件,也就是Activity
、View
等都沒有處理這個事件,App進程發送給InputDispather
的finish事件會標誌這個事件的handled
爲false
。 InputDispatcher
收到handled
爲false
的事件後會詢問IMS
是否備選(fallback
)事件,IMS
最終會通過WMS
到PhoneWindowManager
詢問是否有備選事件,若是有就將PhoneWindowManager
返回的備選事件加入到窗口對應的connection
的outboundQueue
的隊頭,在下一次窗口派發循環(注意InputDispather
的mInboundQueue
隊列對應的大循環和connection
的outboundQueue
對應的窗口事件小循環)中將這個事件發給窗口。
備選事件:系統能夠爲某些事件配置回滾,好比一個按鍵App沒有處理,系統能夠派發一個與這個按鈕功能相似的一個按鍵事件嘗試讓App處理。
每個事件都至少在一次線程循環中被派發。 對於當前正在派發事件的窗口,事件是發送一個收到反饋再發下一個,若是本次發送沒有收到反饋,不會發下一個。 若是用戶按HOME鍵或觸發了目標爲其餘窗口的事件,此時InputDispatcher
發現當前窗口正在等待上一個事件的反饋,就會將排在當前窗口上的全部事件都丟棄,而後將HOME事件或屬於其餘窗口的事件發送給對應的目標。
/dev/input
目錄下建立event0~eventN
個設備節點。inotify
監聽輸入設備的添加和移除。epoll
機制監聽輸入設備的數據變化。InputReader
。IMS
提供的配置信息,好比鍵盤佈局。IMS
提供的配置信息(包括鍵盤佈局,顯示屏信息)對原始事件實施一次轉換。ACTION_DOWN
事件) 。IMS
提供的派發前策略過濾和攔截事件(好比HOME鍵)500ms
,重複的間隔是50ms
。WMS
會將當前的全部窗口和窗口信息傳給InputDispatcher
,以供InputDispatcher
尋找派發窗口, 找到當前能夠接收事件的窗口(好比key事件尋找focus的窗口,motion事件尋找包含這個事件座標的窗口) 將事件派發給窗口。InputChannel
支持跨進程傳輸。socketpair
的FD
,App進程持有一端,WMS
進程持有一端。InputChannel
負責事件最終的讀寫。InputChannel
,負責將InputChannel
的FD
加入到main looper並負責讀寫InputChannel
。ViewRootImpl
。ANR
InputDispatcher
根據事件找到目標窗口好要看目標窗口是否可以接受事件,能不可以接受事件是根據目標窗口如今有沒有正在派發的事件,若是有,本次不派發,記錄一個ANR
起始時間,並將這個待派發的事件掛起,若是後續又有新事件入列致使派發線程被喚醒,再次派發剛纔掛起的事件,一樣檢查當前窗口是否能接受事件並更新ANR
時間,當ANR
時間達到5s
,通知IMS
處理ANR
事件。(若是新加入一個屬於其餘窗口的事件或者HOME鍵按下事件,會將當前窗口等待派發的事件都丟棄掉,除了已經加入到窗口本身的派發隊列中的事件,這些時間會在其前面等待響應的事件響應後挨個發給窗口)
對於key
事件和motion
事件有所區別,key
事件對於同一個窗口必須是發完一個,下一個事件要從新查找窗口再派發,由於有可能上一個事件會致使焦點窗口改變,好比遙控器,用戶點了一個按鈕,彈出一個彈窗,而下一個事件應該發給新的彈窗,用戶的預期是一個按鍵處理完了再處理另外一個按鍵。
而motion
事件,若是當前窗口有正在處理的事件,後續事件只要是在第一個未響應事件發出的0.5s
以內發生都仍是加入到這個窗口本身的派發隊列,等前面的事件派發完了接着將隊列中其餘事件派發掉,若是超過0.5s
則不加入當前窗口派發隊列,而是等待下一次派發週期從新查找窗口,並記錄ANR
起始時間。這樣的作法是考慮到motion
事件通常不考慮焦點,用戶當前看到的是哪一個window
,預期時間就應該給哪一個window
,即使正在處理的事件會致使window
切換,只要仍是用戶如今看到的window
,0.5s
內的事件都仍是給這個window
。超過0.5s
的事件就依照新查找到的窗口而定。
ANR
是個dialog
,是AMS
彈的,事件的源頭是InputDispatcher
,通過InputDispatcher -> NativeInputManager -> InputManagerService -> AMS
事件注入能夠協助咱們實現UI自動化測試,Android上可使用如下幾種方法進行事件注入。
1,使用Instrumentation
類,調用其好比sendPointerSync
方法。
2,使用adb getevent/putevent
命令行工具
3,經過IMS
的injectInputEvent
方法(App進程能夠經過InputManager
訪問IMS
),因爲injectInputEvent
是api hide
的方法,所以只能經過hack的方式訪問。
ANR
時AMS
會發出一個android.intent.action.ANR
廣播,經過監聽這個廣播能夠收集App的ANR
事件並上報給服務端。HOME
鍵在派發前會被IMS
攔截,由於IMS
是InputDispatcher
的派發前polocy
,IMS
會將事件轉交給PhoneWindowManager
,由PhoneWindowManager
啓動HOME
桌面並消費掉事件。