Android的用戶輸入處理
Android的用戶輸入系統(tǒng)獲取用戶按鍵(或模擬按鍵)輸入,分發(fā)給特定的模塊(Framework或應(yīng)用程序)進(jìn)行處理,它涉及到以下一些模塊:
- Input Reader: 負(fù)責(zé)從硬件獲取輸入,轉(zhuǎn)換成事件(Event), 并分發(fā)給Input Dispatcher.
- Input Dispatcher: 將Input Reader傳送過來的Events 分發(fā)給合適的窗口,并監(jiān)控ANR。
- Input Manager Service: 負(fù)責(zé)Input Reader 和 Input Dispatchor的創(chuàng)建,并提供Policy 用于Events的預(yù)處理。
- Window Manager Service:管理Input Manager 與 View(Window) 以及 ActivityManager 之間的通信。
- View and Activity:接收按鍵并處理。
- ActivityManager Service:ANR 處理。
它們之間的關(guān)系如下圖所示(黑色箭頭代表控制信號(hào)傳遞方向,而紅色箭頭代表用戶輸入數(shù)據(jù)的傳遞方向)。

這塊代碼很多,但相對(duì)來說不難理解,按照慣例,我們先用一張大圖(點(diǎn)擊看大圖)鳥瞰一下全貌先。

四種不同顏色代表了四個(gè)不同的線程, InputReader Thread,InputDispatch Thread 和 Server Thread 存在于SystemServer進(jìn)程里。UI Thread則存在于Activity所在進(jìn)程。顏色較深部分是比較重要,需要重點(diǎn)分析的模塊。
初始化
整個(gè)輸入系統(tǒng)的初始化可以劃分為Java 和 Native兩個(gè)部分,可以用兩張時(shí)序圖分別描述,首先看Java端,

- 在SystemServer的初始化過程中,InputManagerService 被創(chuàng)建出來,它做的第一件事情就是初始化Native層,包括EventHub, InputReader 和 InputDispatcher,這一部分我們將在后面詳細(xì)介紹。
- 當(dāng)InputManager Service 以及其他的System Service 初始化完成之后,應(yīng)用程序就開始啟動(dòng)。如果一個(gè)應(yīng)用程序有Activity(只有Activit能夠接受用戶輸入),它要將自己的Window(ViewRoot)通過setView()注冊(cè)到Window Manager Service 中。(詳見圖解Android - Android GUI 系統(tǒng) (2) - 窗口管理 (View, Canvas, Window Manager))。
- 用戶輸入的捕捉和處理發(fā)生在不同的進(jìn)程里(生產(chǎn)者:Input Reader 和 Input Dispatcher 在System Server 進(jìn)程里,而消耗者,應(yīng)用程序運(yùn)行在自己的進(jìn)程里),因此用戶輸入事件(Event)的傳遞需要跨進(jìn)程。在這里,Android使用了Socket 而不是 Binder來完成。OpenInputChannelPair 生成了兩個(gè)Socket的FD, 代表一個(gè)雙向通道的兩端,向一端寫入數(shù)據(jù),另外一端便可以讀出,反之依然,如果一端沒有寫入數(shù)據(jù),另外一端去讀,則陷入阻塞等待。OpenInputChannelPair() 發(fā)生在WindowManager Service 內(nèi)部。為什么不用binder? 個(gè)人的分析是,Socket可以實(shí)現(xiàn)異步的通知,且只需要兩個(gè)線程參與(Pipe兩端各一個(gè)),假設(shè)系統(tǒng)有N個(gè)應(yīng)用程序,跟輸入處理相關(guān)的線程數(shù)目是 n+1 (1是發(fā)送(Input Dispatcher)線程)。然而,如果用Binder實(shí)現(xiàn)的話,為了實(shí)現(xiàn)異步接收,每個(gè)應(yīng)用程序需要兩個(gè)線程,一個(gè)Binder線程,一個(gè)后臺(tái)處理線程,(不能在Binder線程里處理輸入,因?yàn)檫@樣太耗時(shí),將會(huì)堵塞住發(fā)送端的調(diào)用線程)。在發(fā)送端,同樣需要兩個(gè)線程,一個(gè)發(fā)送線程,一個(gè)接收線程來接收應(yīng)用的完成通知,所以,N個(gè)應(yīng)用程序需要 2(N+1)個(gè)線程。相比之下,Socket還是高效多了。
- 通過RegisterInputChannel, Window Manager Service 將剛剛創(chuàng)建的一個(gè)Socket FD,封裝在InputWindowHandle(代表一個(gè)WindowState) 里傳給InputManagerService。
- InputManagerService 通過JNI(NativeInputManager)最終調(diào)用到了InputDispatchor 的 RegisterInputChannel()方法,這里,一個(gè)Connection 對(duì)象被創(chuàng)建出來,代表與遠(yuǎn)端某個(gè)窗口(InputWindowHandle)的一條用戶輸入數(shù)據(jù)通道。一個(gè)Dispatcher可能有多個(gè)Connection(多個(gè)Window)同時(shí)存在。為了監(jiān)聽來自于Window的消息,InputDispator 通過AddFd 將這些個(gè)FD 加入到Looper中,這樣,只要某個(gè)Window在Socket的另一端寫入數(shù)據(jù),Looper就會(huì)馬上從睡眠中醒來,進(jìn)行處理。
- 到這里,ViewRootImpl 的 AddWindow 返回,WMS 將SocketPair的另外一個(gè)FD 放在返回參數(shù) OutputChannel 里。
- 接著ViewRootImpl 創(chuàng)建了WindowInputEventReceiver 用于接受InputDispatchor 傳過來的事件,后者同樣通過AddFd() 將讀端的Socket FD 加入到Looper中,這樣一旦InputDispatchor發(fā)送Event,Looper就會(huì)立即醒來處理。
接下來看剛才沒有講完的NativeInit。

- NativeInit 是 NativeInputManager類的一個(gè)方法,在InputManagerService的構(gòu)造函數(shù)中被調(diào)用。代碼在 frameworks/base/services/jni/com_android_server_input_inputManagerService.cpp.
- 首先創(chuàng)建一個(gè)EventHub, 用來監(jiān)聽所有的event輸入。
- 創(chuàng)建一個(gè)InputDispatchor對(duì)象。
- 創(chuàng)建一個(gè)InputReader對(duì)象,他的輸入是EventHub, 輸出是InputDispatchor。
- 然后分別為InputReader 和 InputDispatchor 創(chuàng)建各自的線程。注意,當(dāng)前運(yùn)行在System Server 的 WMThread線程里。
- 接著,InputManagerService 調(diào)用NativeStart 通知InputReader 和 InputDispatchor 開始工作。
- InputDispatchor是InputReader的消費(fèi)者,它的線程首先啟動(dòng),進(jìn)入Looper等待狀態(tài)。
- 接著 InputReader 線程啟動(dòng),等待用戶輸入的發(fā)生。
至此,一切準(zhǔn)備工作就緒,萬事具備,之欠用戶一擊了。
Eventhub 和 Input Reader
Android設(shè)備可以同時(shí)連接多個(gè)輸入設(shè)備,比如說觸摸屏,鍵盤,鼠標(biāo)等等。用戶在任何一個(gè)設(shè)備上的輸入就會(huì)產(chǎn)生一個(gè)中斷,經(jīng)由Linux內(nèi)核的中斷處理以及設(shè)備驅(qū)動(dòng)轉(zhuǎn)換成一個(gè)Event,并傳遞給用戶空間的應(yīng)用程序進(jìn)行處理。每個(gè)輸入設(shè)備都有自己的驅(qū)動(dòng)程序,數(shù)據(jù)接口也不盡相同,如何在一個(gè)線程里(上面說過只有一個(gè)InputReader Thread)把所有的用戶輸入都給捕捉到? 這首先要?dú)w功于Linux 內(nèi)核的輸入子系統(tǒng)(Input Subsystem), 它在各種各樣的設(shè)備驅(qū)動(dòng)程序上加了一個(gè)抽象層,只要底層的設(shè)備驅(qū)動(dòng)程序按照這層抽象接口來實(shí)現(xiàn),上層應(yīng)用就可以通過統(tǒng)一的接口來訪問所有的輸入設(shè)備。這個(gè)抽象層有三個(gè)重要的概念,input handler, input handle 和 input_dev,它們的關(guān)系如下圖所示:

- input_dev 代表底層的設(shè)備,比如圖中的“USB keyboard" 或 "Power Button" (PC的電源鍵),所有設(shè)備的input_dev 對(duì)象保存在一個(gè)全局的input_dev 隊(duì)列里。
- input_handler 代表某類輸入設(shè)備的處理方法,比如說 evdev就是專門處理輸入設(shè)備產(chǎn)成的Event(事件),而“sysrq" 是專門處理鍵盤上“sysrq"與其他按鍵組合產(chǎn)生的系統(tǒng)請(qǐng)求,比如“ALT+SysRq+p"(先Ctrl+ALT+F1切換到虛擬終端)可以打印當(dāng)前CPU的寄存器值。所有的input_handler 存放在 input_handler隊(duì)列里。
- 一個(gè)input_dev 可以有多個(gè)input_handler, 比如下圖中“USB Mouse" 設(shè)備可以由”evdev" 和 “mousedev" 來分別處理它產(chǎn)生的輸入。
- 同樣,一個(gè)input_handler 可以用于多種輸入設(shè)備,比如“USB Keyboard", "Power Button" 都可以產(chǎn)成Event,所以,這些Event都可以交由evdev進(jìn)行處理。
- Input handle 用來關(guān)聯(lián)某個(gè)input_dev 和 某個(gè) input_handler, 它對(duì)應(yīng)于下圖中的紫色的原點(diǎn)。每個(gè)input handle 都會(huì)生成一個(gè)文件節(jié)點(diǎn),比如圖中4個(gè) evdev的handle就對(duì)應(yīng)與 /dev/input/下的四個(gè)文件"event0~3". 通過input handle, 可以找到對(duì)應(yīng)的input_handler 和 input_dev.
簡(jiǎn)單說來,input_dev對(duì)應(yīng)于底層驅(qū)動(dòng),而input_handler是個(gè)上層驅(qū)動(dòng),而input_handle 提供給應(yīng)用程序標(biāo)準(zhǔn)的文件訪問接口來打通這條上下通道。通過Linux input system獲取用戶輸入的流程簡(jiǎn)單如下:
- 設(shè)備通過input_register_dev 將自己的驅(qū)動(dòng)注冊(cè)到Input 系統(tǒng)。
- 各種Handler 通過 input_register_handler將自己注冊(cè)到Input系統(tǒng)中。
- 每一個(gè)注冊(cè)進(jìn)來的input_dev 或 Input_handler 都會(huì)通過input_connect() 尋找對(duì)方,生成對(duì)應(yīng)的 input_handle,并在/dev/input/下產(chǎn)成一個(gè)設(shè)備節(jié)點(diǎn)文件.
- 應(yīng)用程序通過打開(Open)Input_handle對(duì)應(yīng)的文件節(jié)點(diǎn),打開其對(duì)應(yīng)的input_dev 和 input_handler的驅(qū)動(dòng)。這樣,當(dāng)用戶按鍵時(shí),底層驅(qū)動(dòng)就能捕捉到,并交給對(duì)應(yīng)的上次驅(qū)動(dòng)(handler)進(jìn)行處理,然后返回給應(yīng)用程序,流程如下圖中紅色箭頭所示。
上圖中的深色點(diǎn)就是 Input Handle, 左邊垂直方向是Input Handler, 而水平方向是Input Dev。 下面是更為詳細(xì)的一個(gè)流程圖,感興趣的同學(xué)可以點(diǎn)擊大圖看看。

所以,只要打開 /dev/input/ 下的所有 event* 設(shè)備文件,我們就可以有辦法獲取所有輸入設(shè)備的輸入事件,不管它是觸摸屏,還是一個(gè)USB 設(shè)備,還是一個(gè)紅外遙控器。Android中完成這個(gè)工作的就是EventHub。
EventHub實(shí)現(xiàn)在 framework/base/services/input/EventHub.cpp, 它和InputReader 的工作流程如下圖所示:
- NativeInputManager的構(gòu)造函數(shù)里第一件事情就是創(chuàng)建一個(gè)EventHub對(duì)象,它的構(gòu)造函數(shù)里主要生成并初始化幾個(gè)控制的FD:
- mINotifyFd: 用來監(jiān)控""/dev/input"目錄下是否有文件生成,有的話說明有新的輸入設(shè)備接入,EventHub將從epool_wait中喚醒,來打開新加入的設(shè)備。
- mWakeReaderFD, mWakeWriterFD: 一個(gè)Pipe的兩端,當(dāng)往mWakeWriteFD 寫入數(shù)據(jù)的時(shí)候,等待在mWakeReaderFD的線程被喚醒,這里用來給上層應(yīng)用提供喚醒等待線程,比如說,當(dāng)上層應(yīng)用改變輸入屬性需要EventHub進(jìn)行相應(yīng)更新時(shí)。
- mEpollFD,用于epoll_wait()的阻塞等待,這里通過epoll_ctrl(EPOLL_ADD_FD, fd) 可以等待多個(gè)fd的事件,包括上面提到的mINotifyFD, mWakeReaderFD, 以及輸入設(shè)備的FD。
- 緊接著,InputManagerService啟動(dòng)InputReader 線程,進(jìn)入無限的循環(huán),每次循環(huán)調(diào)用loopOnce(). 第一次循環(huán),會(huì)主動(dòng)掃描 "/dev/input/" 目錄,并打開下面的所有文件,通過ioctl()從底層驅(qū)動(dòng)獲取設(shè)備信息,并判斷它的設(shè)備類型。這里處理的設(shè)備類型有:INPUT_DEVICE_CLASS_KEYBOARD, INPUT_DEVICE_CLASS_TOUCH, INPUT_DEVICE_CLASS_DPAD,INPUT_DEVICE_CLASS_JOYSTICK 等。
- 找到每個(gè)設(shè)備對(duì)應(yīng)的鍵值映射文件,讀取并生產(chǎn)一個(gè)KeyMap 對(duì)象。一般來說,設(shè)備對(duì)應(yīng)的鍵值映射文件是 "/system/usr/keylayout/Vendor_%04x_Product_%04x".
- 將剛才掃描到的/dev/input 下所有文件的FD 加到epool等待隊(duì)列中,調(diào)用epool_wait() 開始等待事件的發(fā)生。
- 某個(gè)時(shí)間發(fā)生,可能是用戶按鍵輸入,也可能是某個(gè)設(shè)備插入,亦或用戶調(diào)整了設(shè)備屬性,epoll_wait() 返回,將發(fā)生的Event 存放在mPendingEventItems 里。如果這是一個(gè)用戶輸入,系統(tǒng)調(diào)用Read() 從驅(qū)動(dòng)讀到這個(gè)按鍵的信息,存放在rawEvents里。
- getEvents() 返回,進(jìn)入InputReader的processEventLocked函數(shù)。
- 通過rawEvent 找到產(chǎn)生時(shí)間的Device,再找到這個(gè)Device對(duì)應(yīng)的InputMapper對(duì)象,最終生成一個(gè)NotifyArgs對(duì)象,將其放到NotifyArgs的隊(duì)列中。
- 第一次循環(huán),或者后面發(fā)生設(shè)備變化的時(shí)候(比如說設(shè)備拔插),調(diào)用 NativeInputManager 提供的回調(diào),通過JNI通知Java 層的Input Manager Service 做設(shè)備變化的相應(yīng)處理,比如彈出一個(gè)提示框提示新設(shè)備插入。這部分細(xì)節(jié)會(huì)在后面介紹。
- 調(diào)用NotifyArgs里面的Notify()方法,最終調(diào)用到InputDispatchor 對(duì)應(yīng)的Notify接口(比如NotifyKey) 將接下來的處理交給InputDispatchor,EventHub 和 InputReader 工作結(jié)束,但馬上又開始新的一輪等待,重復(fù)6~9的循環(huán)。
Input Dispatcher
接下來看看目前為止最長(zhǎng)一張時(shí)序圖,通過下面18個(gè)步驟,事件將發(fā)送到應(yīng)用程序進(jìn)行處理。

- 接上節(jié)的最后一步,NotifyKey() 的實(shí)現(xiàn)在Input Dispatcher 內(nèi)部,他首先做簡(jiǎn)單的校驗(yàn),對(duì)于按鍵事件,只有Action 是 AKEY_EVENT_ACTION_DOWN 和 AKEY_EVENT_ACTION_UP,即按下和彈起這兩個(gè)Event別接受。
- Input Reader 傳給Input Dispather的數(shù)據(jù)類型是 NotifyKeyArgs, 后者在這里將其轉(zhuǎn)換為 KeyEvent, 然后交由 Policy 來進(jìn)行第一步的解析和過濾,interceptKeyBeforeQueuing, 對(duì)于手機(jī)產(chǎn)品,這個(gè)工作是在PhoneWindowManager 里完成,(不同類型的產(chǎn)品可以定義不同的WindowManager, 比如GoogleTV 里用到的是TVWindowManager)。KeyEvent 在這里將會(huì)被分為三類:
- System Key: 比如說 音量鍵,Power鍵,電話鍵,以及一些特殊的組合鍵,如用于截屏的音量+Power,等等。部分System Key 會(huì)在這里立即處理,比如說電話鍵,但有一些會(huì)放到后面去做處理,比如說音量鍵,但不管怎樣,這些鍵不會(huì)傳給應(yīng)用程序,所以稱為系統(tǒng)鍵。
- Global Key:最終產(chǎn)品中可能會(huì)有一些特殊的按鍵,它不屬于某個(gè)特定的應(yīng)用,在所有應(yīng)用中的行為都是一樣,但也不包含在Andrioid的系統(tǒng)鍵中,比如說GoogleTV 里會(huì)有一個(gè)“TV” 按鍵,按它會(huì)直接呼起“TV”應(yīng)用然后收看電視直播,這類按鍵在Android定義為Global Key.
- User Key:除此之外的按鍵就是User Key, 它最終會(huì)傳遞到當(dāng)前的應(yīng)用窗口。
- phoneWindowManager的interceptKeyBeforeQueuing() 最后返回了wmActiions,里面包含若干個(gè)flags,NativeInputManager在handleInterceptActions(), 假如用戶按了Power鍵,這里會(huì)通知Android睡眠或喚醒。最后,返回一個(gè) policyFlags,結(jié)束第一次的intercept 過程。
- 接下來,按鍵馬上進(jìn)入第二輪處理。如果用戶在Setting->Accessibility 中選擇打開某些功能,比如說手勢(shì)識(shí)別,Android的AccessbilityManagerService(輔助功能服務(wù)) 會(huì)創(chuàng)建一個(gè) InputFilter 對(duì)象,它會(huì)檢查輸入的事件,根據(jù)需要可能會(huì)轉(zhuǎn)換成新的Event,比如說兩根手指頭捏動(dòng)的手勢(shì)最終會(huì)變成ZOOM的event. 目前,InputManagerService 只支持一個(gè)InputFilter, 新注冊(cè)的InputFilter會(huì)把老的覆蓋。InputFilter 運(yùn)行在SystemServer 的 ServerThread 線程里(除了繪制,窗口管理和Binder調(diào)用外,大部分的System Service 都運(yùn)行在這個(gè)線程里)。而filterInput() 的調(diào)用是發(fā)生在Input Reader線程里,通過InputManagerService 里的 InputFilterHost 對(duì)象通知另外一個(gè)線程里的InputFilter 開始真正的解析工作。所以,InputReader 線程從這里結(jié)束一輪的工作,重新進(jìn)入epoll_wait() 等待新的用戶輸入。InputFilter 的工作也分為兩個(gè)步驟,首先由InputEventConsistencyVerifier 對(duì)象(InputEventConsistencyVerifier.java)對(duì)輸入事件的完整性做一個(gè)檢查,檢查事件的ACTION_DOWN 和 ACTION_UP 是否一一配對(duì)。很多同學(xué)可能在Android Logcat 里看到過以下一些類似的打?。?ACTION_UP but key was not down." 就出自此處。接下來,進(jìn)入到AccessibilityInputFilter 的 onInputEvent(),這里將把輸入事件(主要是MotionEvent)進(jìn)行處理,根據(jù)需要變成另外一個(gè)Event,然后通過sendInputEvent()將事件發(fā)回給InputDispatcher。最終調(diào)用到injectInputEvent() 將這個(gè)事件送入 mInBoundQueue.
- 這個(gè)時(shí)候,InputDispather 還在Looper中睡眠等待,injectInputEvent()通過wake() 將其喚醒。這是進(jìn)入Input Dispatcher 線程。
- InputDispatcher 大部分的工作在 dispatcherOnce 里完成。首先從mInBoundQueue 中讀出隊(duì)列頭部的事件 mPendingEvent, 然后調(diào)用 pokeUserActivity(). poke的英文意思是"搓一下, 捅一下“, 這個(gè)函數(shù)的目的也就是”捅一下“PowerManagerService 提醒它”別睡眠啊,我還活著呢“,最終調(diào)用到PowerManagerService 的 updatePowerStateLocked(),防止手機(jī)進(jìn)入休眠狀態(tài)。需要注意的是,上述動(dòng)作不會(huì)馬上執(zhí)行,而是存儲(chǔ)在命令隊(duì)列,mCommandQueue里,這里面的命令會(huì)在后面依次被執(zhí)行。
- 接下來是dispatchKeyLocked(), 第一次進(jìn)去這個(gè)函數(shù)的時(shí)候,先檢查Event是否已經(jīng)過處理(interceptBeforeDispatching), 如果沒有,則生成一個(gè)命令,同樣放入mCommandQueue里。
- runCommandsLockedInterruptible() 依次執(zhí)行mCommandQueue 里的命令,前面說過,pokeUserActivity 會(huì)調(diào)用PowerManagerService 的 updatePowerStateLocked(), 而 interceptKeyBeforeDispatching() 則最終調(diào)用到PhoneWindowManager的同名函數(shù)。我們?cè)趇nterceptBeforeQueuing 里面提到的一些系統(tǒng)按鍵在這個(gè)被執(zhí)行,比如 HOME/MENU/SEARCH 等。
- 接下來,處理前面提過GlobalKey,GlobalKeyManager 通過broadcast將這些全局的Event發(fā)送給感興趣的應(yīng)用。最終,interceptKeyBeforeDispatching 將返回一個(gè)Int值,-1 代表Skip,這個(gè)Event將不會(huì)發(fā)送給應(yīng)用程序。0 代表 Continue, 將進(jìn)入下一步的處理。1 則表明還需要后續(xù)的Event才能做出決定。
- 命令運(yùn)行完之后,退出 dispatchOnce, 然后調(diào)用pollOnce 進(jìn)入下一輪等待。但這里不會(huì)被阻塞,因?yàn)閠imeout值被設(shè)成了0.
- 第二次進(jìn)入dispatchKeyLocked(), 這是Event的狀態(tài)已經(jīng)設(shè)為”已處理“,這時(shí)候才真正進(jìn)入了發(fā)射階段。
- 接下來調(diào)用 findFocusedWindowTargetLocked() 獲取當(dāng)前的焦點(diǎn)窗口,這里面會(huì)做一件非常重要的事情,就是檢測(cè)目標(biāo)應(yīng)用是否有ANR發(fā)生,如果下訴條件滿足,則說明可能發(fā)生了ANR:
- 目標(biāo)應(yīng)用不會(huì)空,而目標(biāo)窗口為空。說明應(yīng)用程序在啟動(dòng)過程中出現(xiàn)了問題。
- 目標(biāo) Activity 的狀態(tài)是Pause,即不再是Focused的應(yīng)用。
- 目標(biāo)窗口還在處理上一個(gè)事件。這個(gè)我們下面會(huì)說到。
- 如果目標(biāo)窗口處于正常狀態(tài),調(diào)用dispatchEventLocked() 進(jìn)入真正的發(fā)送程序。
- 這里,事件又換了一件馬甲,從EventEntry 變成 DispatchEntry, 并送人mOutBoundQueue。然后調(diào)用startDispatchCycle() 開始發(fā)送。
- 最終的發(fā)送發(fā)生在InputPublish的sendMessage()。這里就用到了我們前面提到的SocketPair, 一旦sendMessage() 執(zhí)行,目標(biāo)窗口所在進(jìn)程的Looper線程就會(huì)被喚醒,然后讀取鍵值并進(jìn)行處理,這個(gè)過程我們下面馬上就會(huì)談到。
- 乖乖,還沒走完?。渴堑?,工作還差最后一步,Input Dispatcher給這個(gè)窗口發(fā)送下一個(gè)命令之前,必須等待該窗口的回復(fù),如果超過5s沒有收到,就會(huì)通過Input Manager Service 向Activity Manager 匯報(bào),后者會(huì)彈出我們熟知的 "Application No Response" 窗口。所以,事件會(huì)放入mWaitQueue進(jìn)行暫存。如果窗口一切正常,完成按鍵處理后它會(huì)調(diào)用InputConsumer的sendFinishedSignal() 往SocketPair 里寫入完成信號(hào),Input Dispatcher 從 Loop中醒來,并從Socket中讀取該信號(hào),然后從mWaitQueue 里清除該事件標(biāo)志其處理完畢。
- 并非所有的事件應(yīng)用程序都會(huì)處理,如果沒有處理,窗口程序返回的完成消息里的 msg.body.finished.handled 會(huì)等于false,InputDispatcher 會(huì)調(diào)用dispatchKeyUnhandled() 將其交給PhoneWindowManager。Android 在這里提供了一個(gè)Fallback機(jī)制,如果在 /system/usr/keychars/ 下面的kcm文件里定義了 fallback關(guān)鍵字,Android就識(shí)別它為一個(gè)Fallback Keycode。當(dāng)它的Parent Keycode沒有被應(yīng)用程序處理,InputDispatcher 會(huì)把 Fallback Keycode 當(dāng)成一個(gè)新的Event,重新發(fā)給應(yīng)用程序。下面是一個(gè)定義Fallback Key 的例子。如果按了小鍵盤的0且應(yīng)用程序不受理它,InputDispatcher 會(huì)再發(fā)送一個(gè)'INSERT' event 給應(yīng)用程序。
#/system/usr/keychars/generic.kcm
...
key NUMPAD_0 {
label: '0' //打印字符
base: fallback INSERT //behavior
numlock: '0' //在一個(gè)textView里輸出的字符
}
- 經(jīng)歷了重重關(guān)卡,一個(gè)按鍵發(fā)送的流程終于完成了,不管有沒有Fallback Key存在,調(diào)用startDispatcherCycle() 開始下一輪征程。。。
史上最長(zhǎng)的流程圖終于介紹完了,有點(diǎn)迷糊了?好吧,再看看下面這張圖總結(jié)一下:
- InputDispatcher 是一個(gè)異步系統(tǒng),里面用到3個(gè)Queue(隊(duì)列)來保存中間任務(wù)和事件,分別是 mInBoundQueue, mOutBoundQueue,mWaitQueue不同隊(duì)列的進(jìn)出劃分了按鍵的不同處理階段。
- InputReader 采集的輸入實(shí)現(xiàn)首先經(jīng)過InterceptBeforeQueuing處理,Android 系統(tǒng)會(huì)將這些按鍵分類(System/Global/User), 這個(gè)過程是在InputReader線程里完成。
- 如果是Motion Event, filterEvent()可能會(huì)將其轉(zhuǎn)換成其他的Event。然后通過InjectKeyEvent 將這個(gè)按鍵發(fā)給InputDispatcher。這個(gè)過程是在System Process的ServerThread里完成。
- 在進(jìn)入mOutBoundQueue 之前,首先要經(jīng)過 interceptBeforeDispatching() 的處理,System 和 Global 事件會(huì)在這個(gè)處理,而不會(huì)發(fā)送給用戶程序。
- 通過之前生成的Socket Pair, InputPublish 將 Event發(fā)送給當(dāng)前焦點(diǎn)窗口,然后InputDispatcher將Event放入mWaitQueue 等待窗口的回復(fù)。
- 如果窗口回復(fù),該對(duì)象被移出mWaitQueue, 一輪事件處理結(jié)束。如果窗口沒有處理該事件,從kcm文件里搜尋Fallback 按鍵,如果有,則重新發(fā)送一個(gè)新的事件給用戶。
- 如果超過5s沒有收到用戶回復(fù),則說明用戶窗口出現(xiàn)阻塞,InputDispather 會(huì)通過Input Manager Service發(fā)送ANR給ActivityManager。
Key processing
前面我們說過,NativeInputEventReceiver() 通過addFd() 將SocketPair的一個(gè)FD 加入到UI線程的loop里,這樣,當(dāng)Input Dispatcher在Socket的另外一端寫入Event數(shù)據(jù),應(yīng)用程序的UI線程就會(huì)從睡眠中醒來,開始事件的處理流程。時(shí)序圖如下所示:

- 收到的時(shí)間首先會(huì)送到隊(duì)列中,ViewRootImpl 通過 deliverInputEvent() 向InputStage傳遞消息。
- InputStage 是 Android 4.3 新推出的實(shí)現(xiàn),它將輸入事件的處理分成若干個(gè)階段(Stage), 如果當(dāng)前有輸入法窗口,則事件處理從 NativePreIme 開始,否則的話,從EarlyPostIme 開始。事件會(huì)依次經(jīng)過每個(gè)Stage,如果該事件沒有被標(biāo)識(shí)為 “Finished”, 該Stage就會(huì)處理它,然后返回處理結(jié)果,F(xiàn)orward 或 Finish, Forward 運(yùn)行下一Stage繼續(xù)處理,而Finished事件將會(huì)簡(jiǎn)單的Forward到下一級(jí),直到最后一級(jí) Synthetic InputStage。流程圖和每個(gè)階段完成的事情如下圖所示。

- 最后 通過finishInputEvent() 回復(fù)InputDispatcher。