輸入事件系統的相關組件
Linux內核
接受輸入設備的中斷,并將原始事件的輸入寫入設備節點中;
設備節點
作為內核和IMS的橋梁,將原始事件的數據暴露給用戶空間,以便IMS可以從中讀取事件;
InputManagerService
Android系統服務,它分為java層和native層兩部分;java層負責與WMS通信,native層則是InputReader和InputDispatcher兩個輸入系統關鍵組件的運行容器;
EventHub
使用inotify監聽輸入設備的添加和移除。
使用epoll機制監聽輸入設備的數據變化。
讀取設備文件的數據。
將原始數據(生事件)返回給InputReader。
InputReader
IMS中的關鍵組件之一,它運行于一個獨立的線程中,負責管理輸入設備的列表與配置,以及進行輸入事件的加工處理。它通過其線程循環不斷地通過getEvents()函數從EventHub中將事件取出并進行處理。對于設備節點的增刪事件,它會更新輸入設備列表與配置。對于原始輸入事件,InputReader對其進行翻譯、組裝、封裝為包含更多信息、更具可讀性的輸入事件,然后交給InputDispatcher進行派發;
InputDispatcher
IMS中的另一個關鍵組件,它也運行于一個獨立的線程中。InputDispatcher中保管了來自WMS的所有窗口的信息,其收到來自InputReader的輸入事件后,會在其保管的窗口中尋找合適的窗口,并將事件派發給此窗口;
InputChannel
InputChannel支持跨進程傳輸。
保存socketpair的FD,App進程持有一端,WMS進程持有一端。
InputChannel負責事件最終的讀寫。
InputEventReceiver
包裝了InputChannel,負責將InputChannel的FD加入到main looper并負責讀寫InputChannel。
將事件封裝成Java層的事件對象向上派發給ViewRootImpl。
WMS
不是輸入系統的一員,但它對InputDispatcher的正常工作起到重要作用。當新建窗口時,WMS為新窗口和IMS創建了事件傳遞所用的通道。另外,WMS還將所有窗口的信息,包括窗口的可點擊區域,焦點窗口等信息,實時的更新到IMS的InputDispatcher中,使得InputDispatcher可以正確地將事件派發到指定的窗口;
ViewRootImpl
對某些窗口,如壁紙窗口、SurfaceView的窗口來說,窗口就是輸入事件派發的終點。而對其他的activity、對話框等使用了Android控件系統的窗口來說,輸入事件的終點是控件View。ViewRootImpl將窗口所接收的輸入事件沿著控件樹將事件派發給感興趣的控件;
inotify機制 與epoll機制
inotify機制
INotify是一個Linux內核所提供的一種文件系統變化通知機制。它可以為應用程序監控文件系統的變化,如文件的新建、刪除、讀寫等。INotify機制有兩個基本對象,分別為inotify對象與watch對象,都使用文件描述符表示。
inotify對象對應了一個隊列,應用程序可以向inotify對象添加多個監聽。當被監聽的事件發生時,可以通過read()函數從inotify對象中將事件信息讀取出來。Inotify對象可以通過以下方式創建:
int inotifyFd = inotify_init();
而watch對象則用來描述文件系統的變化事件的監聽。它是一個二元組,包括監聽目標和事件掩碼兩個元素。
int wd = inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE | IN_DELETE);
當沒有監聽事件發生時,可以通過如下方式將一個或多個未讀取的事件信息讀取出來:
size_t len = read (inotifyFd, events_buf,BUF_LEN);
總結一下INotify機制的使用過程:
通過inotify_init()創建一個inotify對象。
通過inotify_add_watch將一個或多個監聽添加到inotify對象中。
通過read()函數從inotify對象中讀取監聽事件。當沒有新事件發生時,inotify對象中無任何可讀數據。
epoll機制
Epoll可以使用一次等待監聽多個描述符的可讀/可寫狀態。等待返回時攜帶了可讀的描述符或自定義的數據,使用者可以據此讀取所需的數據后可以再次進入等待。因此不需要為每個描述符創建獨立的線程進行阻塞讀取,避免了資源浪費的同時又可以獲得較快的響應速度。
Epoll機制的接口只有三個函數,十分簡單。
epoll_create(int max_fds):創建一個epoll對象的描述符,之后對epoll的操作均使用這個描述符完成。max_fds參數表示了此epoll對象可以監聽的描述符的最大數量。
epoll_ctl (int epfd, int op,int fd, struct epoll_event *event):用于管理注冊事件的函數。這個函數可以增加/刪除/修改事件的注冊。
int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout):用于等待事件的到來。當此函數返回時,events數組參數中將會包含產生事件的文件描述符。
Epoll的使用步驟總結如下:
通過epoll_create()創建一個epoll對象。
為需要監聽的描述符填充epoll_events結構體,并使用epoll_ctl()注冊到epoll對象中。
使用epoll_wait()等待事件的發生。
根據epoll_wait()返回的epoll_events結構體數組判斷事件的類型與來源并進行處理。
繼續使用epoll_wait()等待新事件的發生。
IMS的構成
IMS在SystemServer中的ServerThread線程中啟動,在InputManagerService的構造函數中調用nativeInit方法,nativeInit方法創建了一個類型為NativeInputManager的對象,它是Java層與Native層互相通信的橋梁。NativeInputManager位于IMS的jni層,負責native層的組件與java層的IMS的相互通信,同時它為主要工作是為InputReader和InputDispatcher提供策略請求接口InputReaderPolicyInterface和InputDispatcherPolicyInterface,策略請求被它轉發為Java層的IMS,由IMS最終確定。在NativeInputManager構造函數中創建了EventHub和InputManager。在InputManager中創建了四個對象,分別為InputDispatcher,InputReader,InputReaderThread和InputDispatcherThread。
####### InputReader總體流程
1、首先從EventHub中抽取未處理的事件列表,這些事件分為兩類,一類是從設備節點讀取的原始輸入事件,另一類是設備事件。
2、對原始輸入事件進行封裝與加工將結果暫存到mQueuedListener中。
3.所有事件處理完畢之后,調用mQueuedListener.flush()將所有暫存的輸入事件一次性的交付給InputDispatcher.
EventHub中抽取未處理的事件列表主要是調用getEvents函數,getEvents函數的本質是通過epoll_wait()獲取Epoll事件到事件池,并對事件池中的事件進行消費的過程。從epoll_wait()的調用開始到事件池的最后一個時間被消費完畢的過程稱為EventHub的一個監聽周期。由于buffer參數的額尺寸限制,一個監聽周期可能包含多個getEvents調用。
InputReader經過加工之后,輸出的事件分為三種基本類型,分別為:按鍵類型,手勢類型和開關類型。三種類型分別由NotifyKeyArgs,NotifyMotionArgs,NotifySwitchArgs三個結構體描述。可以說EventHub的EawEvent是InputReader的輸入,而上述三個結構體是InputReader的輸出。InputDispatcher繼承了InputListenerInterface,實現了notifyKey和notifyMotion和notifySwitch等方法。創建InputReader時將InputDispatcher傳給了InputReader。InputReader以InputListenerInterface類型持有InputDispatcher。然后調用mQueuedListener.flush()將三種類型事件傳給InputDispatcher。
在這有個問題,為什么不直接使用InputDispatcher作為事件的接受者,而是用QueuedInputListener這個中間人?QueuedInputListener是使用mArgsQueue隊列將信息保存起來,當InputReader處理完自EventHub的所有原始輸入事件之后,調用flush()函數將緩存的事件信息取出,這樣做的目的是,減少InputDispatcher的休眠與喚醒次數,因為InputDispatcher派發的速度快于InputReader加工一個原始輸入事件的速度,就會導致InputDispatcher多次休眠與喚醒。
InputDispatcher總體流程
InputReader將處理好的事件提交給InputDispatcher之后,會將輸入事件放進派發隊列,但是在放進派發隊列之前,需要先過濾。過濾之后將事件封裝成EventEntry的子類,然后調用enqueueInboundEventLocked()將事件注入mInboundQueue的隊尾,并且根據mInboundQueue是否為空來是否喚醒派發線程。
真正的派發是調用dispatchOnceInnerLocked()函數,如果派發隊列為空,則會使派發線程陷入無限期休眠狀態,即將被派發的事件從派發隊列中取出,事件也有可能某些原因被丟棄,被丟棄的原因保存在dropReason中,然后去尋找合適的窗口,目標窗口分為兩種:普通窗口和監聽窗口。普通窗口通過按點查找與按焦點查找兩種方式獲得,而監聽窗口則無條件監聽所有輸入事件。
Motion事件派發與按鍵事件派發的區別:
按鍵事件在正式派發給窗口之前,進行一次額外的派發策略查詢,這個查詢結果決定此事件是正常派發、稍后派發還是丟棄。
按鍵事件的派發目標僅通過焦點方式進行查找。
派發找到對應的窗口之后,然后根據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的消息。
InputChannel
InputChannel本質是一對SocketPair(非網絡套接字)。SocketPair用來實現在本機內進行進程間的通信。一對SocketPair通過socketpair()函數創建,其使用者可以因此而得到兩個相互連接的文件描述符。這兩個描述符可以通過套接字接口send()和recv()進行寫入和讀取,并且向其中一個文件描述符寫入的數據,可以從另一個描述符中讀取。同pipe()所創建的管道不同,SocketPair的兩個文件描述符是雙通的,因此非常適合用來進行進程間的交互式通信;
1.事件發送主要是通過InputChannel來完成;
2.在wms 執行addView()時,調用openInputChannel來從native層獲取inputchannels數組,一個通過ims
registerInputChannel來連接InputDispatcher,另外一個通過InputEventReceiver來連接窗口;
3.InputDispatcher經過Connection最終通過InputPublisher將事件發送到目標窗口;
4.NativeInputEventListener監聽到事件到來時通過InputConsumer處理InputMessage后回調Java層接口;
Connection
Connection包含兩個隊列,分別為outboundQueue和waitQueue。還持有InputPublisher對象。
InputPublisher封裝InputChannel并直接對齊進行寫入和讀取,也負責InputMessage結構體的封裝和解析。
outboundQueue保存等待Connection進行發送事件的隊列。
waitQueue已發送等待反饋的隊列,得到反饋后則從隊列中刪除。
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對應的窗口事件小循環)中將這個事件發給窗口。
派發循環和事件發送循環
派發循環是InputDiapatcher不斷的從派發隊列取出事件,尋找合適的窗口進行發送的過程,主要是InputDispatcherThread線程主要的工作。
事件發送循環是InputDispatcher通過Connection對象將事件發送到窗口,并接受反饋的過程