Android 設備輸入事件(input)派發原理總結

注:如下內容基於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

事件傳遞過程

IMS端

input系統在Java層表明是IMS(InputManagerService),IMS隨着system_server進程啓動,IMS啓動過程當中會啓動兩個線程,一個事件讀取線程(InputReaderThread),一個事件派發線程(InputDispatcherThread)。緩存

WMS直接使用IMSWMS經過IMS向事件派發線程傳遞窗口信息並設置當前FocusedwindowWMS同時也負責建立和註冊system_server進程與App進程傳遞事件的通道:InputChannelWMSIMS註冊事件發送端InputChannelIMS最終調用到了Native層的InputManagermarkdown

應用程序經過Java層的InputManager訪問IMS,Java層的InputManager包裝了IMSbinder代理對象。併發

IMS持有Native層的NativeInputManagerNativeInputManager持有Native的InputMangersocket

Native層的InputManager包含了一個InputDispatcher和一個InputReaderInputReader包含了一個EventHubide

InputDispatcher繼承了InputListenerInterface,實現了notifyKeynotifyMotion等方法。建立InputReader時將InputDispatcher傳給了InputReaderInputReaderInputListenerInterface類型持有InputDispatcher函數

InputReader有一個對應的InputReaderThread線程。在InputReaderThread裏循環調用EventHub獲取事件。工具

EventHub負責打開/dev/input目錄下的全部設備,和經過inotify機制監聽/dev/input下面的設備文件變化,並負責讀取全部的FDoop

EventHub使用epoll系統調用監控inotify和設備文件FD的可讀事件,當某個設備可讀時,從epoll_wait返回,緊接着讀取有數據的設備的事件數據並將數據返回給InputReaderInputReader將原始的生事件加工成熟事件後交給InputDispather

InputDispatcher對應了一個InputDispatcherThread線程,在InputDispatcherThread使用Native層的Looper等待派發出去的事件的迴應,同時若是InputReader有事件會調用InputDispatcher將事件添加到一個隊列中(mInboundQueue)並喚醒InputDispatcherThreadInputDispatcherThread被喚醒後調用InputDispatcher開始派發事件隊列中的事件。

事件派發過程是先找到當前的window,而後根據window找到Connection,而後將事件加到ConnectionoutboundQueue, 而後從outboundQueue隊頭取一個消息,調用ConnectionInputPublisher發送事件,InputPublisher最終會調用InputChannelInputChannel用本身保存的FD調用socketpairsenMsg函數將事件發出。

一個window對應一個InputChannel對應一個Connection

發完事件後,將這個消息記錄到ConnectionwaitQueue的隊尾。InputDispatcherThread再次等待在Looper上,等App窗口消費完事件併發送finish事件後,InputDispatcherThread就會被喚醒,而後根據發生消息的FD(一個窗口對應一個FD)找到Connection,再根據事件的序列號(seq)找到事件而後將事件從waitQueue移除,並繼續派發屬於這個Connction的消息。

App端

App窗口在調用WMSaddWindow時,WMS會爲App窗口創建一對InputChannelInputChannel基於socketpair(socketpair的兩端是對等的,沒有server和client之分),一端給InputDispatcher使用,一端返回給App進程用來接收事件,WMS會將服務端的InputChannel註冊到InputDispatcher,這樣InputDispather就能夠用來給窗口發送事件並接收窗口事件了,接收事件的原理是將InputChannelFD加入到InputDispatherLooper中,由於InputDispatherThread阻塞在Looper.pollOnce上,當InputDispather收到窗口發來的finish事件後InputDispatherThread會被喚醒,而後由InputDistather處理finish消息。

App進程獲取到InputChannel後將以內部的socketpairFD加入到main looper的FD監聽列表中去,後續若是收到事件,事件的處理會直接發生在主線程,main looper監聽到FD上有數據後回調FD綁定的回調函數,回調函數將事件讀出來封裝成對應的Event對象,而後層層傳遞到ViewRootImplViewRootImpl經過一個責任鏈決定事件的處理順序和方式,某些事件可能會先派發給輸入法窗口進行消費,若是輸入法窗口不消費就繼續派發給view tree消費,派發給view tree是直接派發的,由於這時已經在主線程了,流程大體是: ViewRootImpl -> DecorView -> Activity -> View(DecorView) -> DecorView的子View

若是App進程沒有消費事件,也就是ActivityView等都沒有處理這個事件,App進程發送給InputDispather的finish事件會標誌這個事件的handledfalseInputDispatcher收到handledfalse的事件後會詢問IMS是否備選(fallback)事件,IMS最終會通過WMSPhoneWindowManager詢問是否有備選事件,若是有就將PhoneWindowManager返回的備選事件加入到窗口對應的connectionoutboundQueue的隊頭,在下一次窗口派發循環(注意InputDispathermInboundQueue隊列對應的大循環和connectionoutboundQueue對應的窗口事件小循環)中將這個事件發給窗口。

備選事件:系統能夠爲某些事件配置回滾,好比一個按鍵App沒有處理,系統能夠派發一個與這個按鈕功能相似的一個按鍵事件嘗試讓App處理。

每個事件都至少在一次線程循環中被派發。 對於當前正在派發事件的窗口,事件是發送一個收到反饋再發下一個,若是本次發送沒有收到反饋,不會發下一個。 若是用戶按HOME鍵或觸發了目標爲其餘窗口的事件,此時InputDispatcher發現當前窗口正在等待上一個事件的反饋,就會將排在當前窗口上的全部事件都丟棄,而後將HOME事件或屬於其餘窗口的事件發送給對應的目標。

事件系統關鍵組件

Linux Kenel和設備驅動

  • 根據插入的設備在/dev/input目錄下建立event0~eventN個設備節點。
  • 監聽設備輸入產生的硬件中斷。
  • 將數據緩存起來,喚醒在設備文件上等待數據的進程。

EventHub(管理輸入設備和讀取輸入事件)

  • 使用inotify監聽輸入設備的添加和移除。
  • 使用epoll機制監聽輸入設備的數據變化。
  • 讀取設備文件的數據。
  • 將原始數據(生事件)返回給InputReader

InputReader (將生事件加工成熟事件)

  • 讀取IMS提供的配置信息,好比鍵盤佈局。
  • 根據IMS提供的配置信息(包括鍵盤佈局,顯示屏信息)對原始事件實施一次轉換。
  • 將多個事件組合成一個可供上層消費的事件(好比將一組觸摸屏的原始事件合併成一個ACTION_DOWN事件) 。

InputDispatcher (分發事件)

  • 根據IMS提供的派發前策略過濾和攔截事件(好比HOME鍵)
  • 對於按鍵事件產生模擬按下重複事件,開始重複延遲是500ms,重複的間隔是50ms
  • WMS會將當前的全部窗口和窗口信息傳給InputDispatcher,以供InputDispatcher尋找派發窗口, 找到當前能夠接收事件的窗口(好比key事件尋找focus的窗口,motion事件尋找包含這個事件座標的窗口) 將事件派發給窗口。

InputChannel

  • InputChannel支持跨進程傳輸。
  • 保存socketpairFD,App進程持有一端,WMS進程持有一端。
  • InputChannel負責事件最終的讀寫。

InputEventReceiver

  • 包裝了InputChannel,負責將InputChannelFD加入到main looper並負責讀寫InputChannel
  • 將事件封裝成Java層的事件對象向上派發給ViewRootImpl

ViewRootImpl

  • 收到事件後按照必定的策略派發給view tree

關於ANR

InputDispatcher根據事件找到目標窗口好要看目標窗口是否可以接受事件,能不可以接受事件是根據目標窗口如今有沒有正在派發的事件,若是有,本次不派發,記錄一個ANR起始時間,並將這個待派發的事件掛起,若是後續又有新事件入列致使派發線程被喚醒,再次派發剛纔掛起的事件,一樣檢查當前窗口是否能接受事件並更新ANR時間,當ANR時間達到5s,通知IMS處理ANR事件。(若是新加入一個屬於其餘窗口的事件或者HOME鍵按下事件,會將當前窗口等待派發的事件都丟棄掉,除了已經加入到窗口本身的派發隊列中的事件,這些時間會在其前面等待響應的事件響應後挨個發給窗口)

對於key事件和motion事件有所區別,key事件對於同一個窗口必須是發完一個,下一個事件要從新查找窗口再派發,由於有可能上一個事件會致使焦點窗口改變,好比遙控器,用戶點了一個按鈕,彈出一個彈窗,而下一個事件應該發給新的彈窗,用戶的預期是一個按鍵處理完了再處理另外一個按鍵。

motion事件,若是當前窗口有正在處理的事件,後續事件只要是在第一個未響應事件發出的0.5s以內發生都仍是加入到這個窗口本身的派發隊列,等前面的事件派發完了接着將隊列中其餘事件派發掉,若是超過0.5s則不加入當前窗口派發隊列,而是等待下一次派發週期從新查找窗口,並記錄ANR起始時間。這樣的作法是考慮到motion事件通常不考慮焦點,用戶當前看到的是哪一個window,預期時間就應該給哪一個window,即使正在處理的事件會致使window切換,只要仍是用戶如今看到的window0.5s內的事件都仍是給這個window。超過0.5s的事件就依照新查找到的窗口而定。

ANR是個dialog,是AMS彈的,事件的源頭是InputDispatcher,通過InputDispatcher -> NativeInputManager -> InputManagerService -> AMS

事件注入的方式

事件注入能夠協助咱們實現UI自動化測試,Android上可使用如下幾種方法進行事件注入。

1,使用Instrumentation類,調用其好比sendPointerSync方法。

2,使用adb getevent/putevent命令行工具

3,經過IMSinjectInputEvent方法(App進程能夠經過InputManager訪問IMS),因爲injectInputEventapi hide的方法,所以只能經過hack的方式訪問。

注意

  • 由於ANRAMS會發出一個android.intent.action.ANR廣播,經過監聽這個廣播能夠收集App的ANR事件並上報給服務端。
  • HOME鍵在派發前會被IMS攔截,由於IMSInputDispatcher的派發前polocyIMS會將事件轉交給PhoneWindowManager,由PhoneWindowManager啓動HOME桌面並消費掉事件。
相關文章
相關標籤/搜索