一、整體流程
系統Input事件傳遞主要經過如下幾個部分:
1.1輸入系統部分
輸入子系統
手機的輸入設備(包括屏幕、鍵盤、鼠標等),當前可用,會在文件系統/dev/input中創建對應的設備節點,用戶操作輸入設備會產生輸入事件(按鍵事件、觸摸事件、鼠標事件)等。
/dev/input/event0
/dev/input/event1
/dev/input/event2
...
InputManagerService
IMS初始化過程主要構造了如下結構:
從結構看,IMS核心功能實現在Native層:
IMS由SystemServer創建。
在system_server進程中包含兩個重要的線程InputReaderThread和InputDispatcherThread,其內部分別對應InputReader和InputDispatcher兩個工作類。
InputReader負責讀取底層收集的input事件:從EventHub讀InputEvent并且傳給InputDispatcher來進行分發。
InputDispatcher負責分發input事件到應用層。WindowManagerService在app端setView的時候就創建了一對Socket連接,InputDispatcher利用這個Socket連接和app端通信。
1.2 WMS處理部分
WMS的職責之一就是輸入系統的中轉站,WMS作為Window的管理者,會配合IMS將輸入事件交由合適的Window來處理。
1.3 View處理部分
app端的ViewRootImpl里面的InputEventReceiver會接到從Socket得到的InputEvent。最終走APP的事件傳遞,消費事件。
二、InputManagerService初始化過程
通過流程圖可以看出,這部分主要是做了一系列的初始化工作:
startOtherServices中,創建了IMS以及WMS,并將WMS中的monitor傳給了IMS,作為回調,最后啟動IMS。
IMS的初始化中執行了nativeInit,該方法中創建了一個NativeInputManager實例,并且和java層使用的是同一個looper。
在NativeInputManager的初始化中創建了一個Eventhub,同時將這個Eventhub傳給新建的Inputmanager,Eventhub就是將數據從硬件驅動上讀出來然后傳遞上來的通道。
InputManager初始化時創建了兩個重要線程:InputReaderThread和InputDispatcherThread。
InputManager的start方法,讓兩個線程開啟了循環執行操作。
三、InputReader處理InputEvent流程
簡單總結:
InputReader啟動后執行loopOnce,它是一個可阻塞循環。
loopOnce循環中會通過Eventhub調用getEvents,來獲取底層input事件,getEvents其實分成了三部分,首先是進行device的讀取和處理,掃描/dev/input/目錄來生成device數據。二是看有沒有需要處理的時間,如果有那么就處理了返回。最后是進行等待,等待對應事件的發生。
讀到了事件就會調用processEventsLocked處理事件:循環獲取EventHub給過來的事件,這里事件包括來自Kernel的input事件和對Input事件的插入和刪除操作(這個不管),針對Kernel的input事件,交給processEventsForDeviceLocked處理。
processEventsForDeviceLocked 調用對應的InputDevice處理Input事件,而InputDevice又會去匹配上對應的InputMapper來處理對應事件。(在InputDevice中,存儲著許多InputMapper,每種InputMapper對應一類Device,例如:Touch、Keyboard、Vibrator等等……)而調用InputDevice的process函數,就是將Input事件傳遞給每一個InputMapper,匹配的InputMapper就會對Input事件進行處理,不匹配的則會忽略。
InputMapper將數據綜合打包成三種數據封裝:NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,分別對應key、Motion和Swich事件。
最后調用mQueuedListener->flush(),將事件隊列中的所有事件交給在InputReader中注冊過的InputDispatcher。InputDispatcher先于InputReader被創建,InputDispatcher沒有輸入事件處理時會進入睡眠狀態,等待InputReader通知喚醒。InputDispatcher的notifyKey函數中會根據按鍵數據來判斷InputDispatcher是否要被喚醒,InputDispatcher被喚醒后,會重新調用dispatchOnceInnerLocked函數將輸入事件分發給合適的Window。
InputReader從EventHub獲取input event,將input event打包成Args放到InputDispacher的mInboundQueue,然后通過notifyKey喚醒InputDispacher。
四、InputDispatch分發流程
簡單總結:
上節InputReader把input event放入了mInboundQueue(NotifyMotionArgs轉換為MotionEntry,添加到隊尾)。InputDispatcherThread被喚醒后,通過InputDispatcher主要任務是找到對應的window,并建立進程間通信,把input event 傳遞過去。
- InputDispatcher中,由dispatchOnceInnerLocked處理input event:
1)從mInboundQueue取出事件
2)通過EventEntry的類型,對不同事件進行不同處理,下面以TYPE_KEY為例
3)TYPE_KEY對應會執行dispatchKeyLocked,將事件分發出去
- dispatchKeyLocked中做三件事情:
1)postCommandLocked 讓policy處理Home、Menu等系統按鍵,policy對應的是NativeInputManager
2)findFocusedWindowTargetsLocked 判斷發生按鍵事件的Window并得到對應的inputTargets
3)dispatchEventLocked 通過InputTarget獲取對應的Connection,每個焦點窗口在InputDispacher里都有一個對應的Connection,通過這個Connection可以跟InputDispacher通信。然后發送事件EventEntry,先是將eventEntry放入Connection的outboundQueue,再通過InputPublisher將Entry發送給窗口,再將Entry從outboundQueue移到waitQueue里,最后由InputPublisher調用InputChanel的SendMessage(),SendMessage()再動用socket的send()函數,將打包好的Message發送給窗口。
- InputChannel封裝了窗口與InputDispatcher間的跨進程通信
應用在ViewRootImpl的setView(),最終會調用IWindowSession的addToDisplay()函數,該函數帶上了mInputChannel參數,向WMS注冊Channel。
五、App端處理流程
簡單總結:
WindowInputEventReceiver中的onInputEvent回調執行enqueueInputEvent,從隊列中獲取一個QueuedInputEvent,判斷是立刻執行還是延遲執行,但是最終都會走doProcessInputEvents。
doProcessInputEvents中主要通過deliverInputEvent進行事件分發。這里核心是InputStage體系,責任鏈模式。最終會匹配上對應Stage來進行事件分發處理。
- 以Activity,View的按鍵分發流程相關的InputStage:ViewPostImeInputStage為例,執行ProcessKeyEvent
第一步是調用PhoneWindow.DecorView的dispatchKeyEvent函數,DecorView是View層次結構的根節點,按鍵從根節點開始按View的事件傳遞流程走。
第二步是判斷按鍵是否是四向鍵,或者是TAB鍵,如果是則需要移動焦點。
本文只是參考了網上的文章,針對input系統總結了一個模糊的流程,input總體來看還是比較復雜的,想要深入學習還是需要針對源碼進行詳細分析。
參考
https://zhuanlan.zhihu.com/p/29152319
https://blog.csdn.net/urdfmqcul2/article/details/78146424
https://blog.csdn.net/xingchenxuanfeng/article/details/79208005
https://blog.csdn.net/chenweiaiyanyan/article/details/72884141