編輯:關於Android編程
整個輸入系統包括服務端和客戶端兩部分,服務端部分主要完成輸入設備事件的讀取、事件的映射、事件的插入、事件的過濾、事件的攔截等功能;客戶端部分主要完成事件向焦點窗口和焦點視圖的派發。
輸入系統的整個架構采用的是管道過濾器模式(Pipe and Filter)架構模式。服務端的InputReader和InputDispatcher對象及客戶端的InputEventReceiver對象及InputConsumer對象對應著過濾器構件,具有各自的輸入、處理、輸出單元,兩者通過管道連接到一起。
下圖是ANDROD4.4 版本的服務端的系統類圖,4.4 版本的輸入系統相對於4.0版本作了如下較大改動:輸入管理服務從窗口管理服務中獨立了出來, C++ 實現部分結構也作了優化,其各個模塊部分之間采用了接口進行交互,包括輸入事件讀取接口InputReaderInterface、事件監聽和提交接口InputListenerInterface和InputDispatcherInterface,以及原始事件接收接口EventHubInterface。
圖中上面部分為JAVA部分對應的類,主要是一個輸入管理服務InputManagerService和客戶端的事件接收和事件派發類,下面部分為C++本地實現的部分,C++部分主要由InputReader、InputDispatcher兩個過濾器構件和線程及輔助對象構成,兩個過濾器構件的任務在對應的兩個線程InputReaderThread和InputDispatcherThread內運行。
InputReader類是InputReaderInterface接口的實現,用來實現事件的讀取功能。 InputDispatcher類是InputDispatcherInterface接口的實現類, 而InputDispatcherInterface 接口又是InputListenerInterface接口的派生接口,用來實現輸入事件的監聽和提交。
整個流程包括事件讀取流程和事件提交兩個流程,分別運行在InputReaderThread和InputDispatcherThread兩個線程內。在輸入讀取線程內,InputReaderInterface接口對象通過EventHubInterface接口從驅動讀取原始輸入事件,經過事件映射後通過InputListenerInterface接口提交給InputDispatcher對象,在InputDispatcherThread線程內由對象InputDispatcher對象、InputPublisher
對象共同負責事件的提交,最後通過InputChannel對象發送輸入事件到服務端的管道內,由客戶端通過InputConsumer從管道的另一端讀取事件。
InputReader、InputDispatcher對象以及InputReaderThread和InputDispatcherThread兩個線程對象都由本地InputManager類創建和啟動,而本地InputManager類則有上面框架中的InputManagerService通過JNI進行實例化,在其nativeInit JNI接口中創建一個NativeInputManager對象作為JAVA層與本地層的交互對象,NativeInputManager對象實例化時創建本地InputManager對象和EventHub對象。
輸入系統管道的打開及服務端事件提交管道的注冊則在WindowManagerService調用addWindow函數新建窗口時創建和注冊;而客戶端事件輸入管道的注冊由客戶端ViewRootImpl對象調用SetView時注冊。因此輸入系統服務端運行在輸入管理服務進程內,客戶端運行在應用程序所在進程內。
一、 輸入系統服務端事件讀取和派發流程(正常情況)
整個流程由InputReaderThread線程觸發,在線程中調用InputReader對象的loopOnce函數,在loopOnce函數中首先通過EventHubInterface接口的getEvents函數讀取輸入設備事件,經過處理後從事件中獲得對應的deviceId,根據deviceId在mDevices數組中找到對應的輸入設備對象或者構造新的InputDevice對象,然後調用InputDevice的process函數。
在InputDevice的process函數中對每個事件由InputDevice對象的mMappers數組中登記的每個InputMapper對象依次處理,分別調用其process函數,對原始輸入事件進行映射。
不同的事件類型采用了不同的具體InputMapper對象進行映射,如按鍵事件對應的是KeyboadInputMapper對象,另外還有TouchInputMapper、CursorInputMapper、VibratorInputMapper、SwitchInputMapper、JoystickInputMapper等映射對象,這些對象的類都是InputMapper虛擬類的具體類。
一個支持多種類型的事件輸入的輸入設備,需要使用多個不同的輸入映射對象分別進行映射。
在InputMapper對象的process函數中把事件封裝成NotifyArgs對象,然後調用InputMapper的監聽對象QueedInputListener的相關事件監聽接口函數,如notifyKey函數。
在QueedInputListener的監聽接口函數中作為參數傳進來的NotifyArgs對象放入QueedInputListener的事件隊列ArgsQueue中,然後返回並在loopOnce函數中調用QueuedListener對象的flush函數。
在flush函數中依次調用ArgsQueue隊列中由事件封裝成的NotifyArgs對象的notify函數,notify函數的參數也是一個監聽對象,在QueuedInputListener對象實例化時賦值,對應的是一個InputDispatcher對象。NotifyArgs對象的notify函數調用其參數引用對象(InputDispatcher)的事件通知函數,以NotifyArgs對象為參數,如按鍵事件對應的notifyKey。
InputDispatcher對象的事件通知函數根據作為參數傳進來的事件構造一個EventEntry對象,然後調用enqueueInboundEventLocked函數把構造的EventEntry事件對象放入內部事件隊列中(mInboundQueue),最後調用Looper的wake函數,喚醒事件提交線程即InputDispatcherThread線程來進行事件向管道中的提交。
下面是InputDispatcherThread線程喚醒後的輸入事件提交流程圖:
InputReader讀取的輸入事件作為參數通過InputListenerInterface的接口函數被InputDispatcher接收。接收的事件構造為EventEntry對象放入InputDispatcher對象的EventEntry類型的隊列mInboundQueue中,並喚醒InputDispatcherThread線程。
InputDispatcherThread線程不斷從該隊列中讀取輸入事件。首先調用InputDispatcher對象的dispatchOnce函數;dispatchOnce函數又調用dispatchOnceInnerLocked函數,在dispatchOnceInnerLocked函數中進行一系列判斷後若是按鍵事件則調用dispatchKeyLocked函數;
在dispatchKeyLocked函數中使用findFocusedWindowTargetsLocked函數尋找焦點窗口,並把找到的焦點窗口通過調用函數addWindowTargetLocked放入mCurrentInputTargets數組中,然後調用addMonitoringTargetsLocked為剛才找到的焦點窗口綁定一個inputChannel通道,接著調用dispatchEventLocked函數;
在dispatchEventLocked函數中首先根據剛才焦點窗口綁定的inputChannel找到對應的一個Connection對象,然後調用prepareDispatchCycleLocked;
在prepareDispatchCycleLocked函數中調用enqueueDispatchEntryLocked函數,在enqueueDispatchEntryLocked函數中根據傳進來的事件對應的對象eventEntry構造一個 DispatchEntry對象,DispatchEntry對象中包含inputTarget的信息,並放入connection對象outboundQueue隊列;接著又調用startDispatchCycleLocked;
在startDispatchCycleLocked函數中通過connection對象中的inputPublisher對象引用調用inputPublisher對象的publishKeyEvent函數或publishMotionEvent函數向客戶端發送事件,到這裡就完成了服務端的輸入事件處理。
服務端的輸入事件處理流程中主要采用了Observer模式(也可稱為監聽者模式)和命令模式以及策略模式,如QueuedListener對象是InputMapper對象的監聽對象,兩個類之間的關系構成Observer模式;
InputMapper對象本身就是設備事件映射策略的實現,InputMapper是一個虛擬類,共有六個派生類,在InputReader對象的createDeviceLocked函數中根據設備的類型實例化不同的InputMapper的派生類,如KEYBOARD_TYPE對應的是KeyboardInputMapper;
而在InputMapper對象向InputDispatcher對象轉發事件過程中采用了命令模式,模式類圖如下:
二、輸入系統客戶端事件派發過程
相對於4.0以前的版本,4.4 版本的客戶端向視圖的事件派發機制也作了大的改動,如上圖所示:客戶端事件的派發任務主要由ViewRootImpl類承擔,在ViewRootImpl類中添加了幾個內部事件處理對象(InputStage的派生類),事件接收機制也更加有效。整個事件接收和派發流程如下:
客戶端的事件接收通過InputEventReceiver對象(具體為ViewRootImpl在SetView函數中實例化的一個WindowInputEventReceiver類型的對象)和對應的本地NativeInputEventReceiver對象共同完成。
NativeInputEventReceiver對象在收到輸入系統產生的輸入事件時,通過jni 調用InputEventReceiver對象的dispatchInputEvent接口向JAVA傳送底層來的輸入事件,InputEventReceiver對象的dispatchInputEvent接口又調用其onInputEvent接口,在onInputEvent接口函數中調用ViewRootImpl對象的enqueueInputEvent函數放入ViewRootImpl對象的事件隊列(QueuedInputEvent)中。然後調用doProcessInputEvents函數立即對隊列中的事件進行事件處理或者調用scheduleProcessInputEvents來異步處理事件。
在 doProcessInputEvents函數調用deliverInputEvent函數來提交隊列中的事件,在deliverInputEvent函數中調用InputStage對象的deliver函數在一個事件處理責任鏈中傳遞和處理事件。
如類圖所示,NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage、EarlyPostImeInputStage、NativePostImeInputStage、ViewPostImeInputStage、SyntheticInputStage構成一個輸入事件責任處理鏈,如果本階段對事件沒有處理,則傳遞到下一個對象進行處理,直至事件被處理。NativePreImeInputStage、ViewPreImeInputStage、ImeInputStage三個類用來實現輸入法的按鍵派發和處理,如果事件不傳遞到輸入法服務中,這三個類可以跳過,直接從EarlyPostImeInputStage對象開始處理,在ViewPostImeInputStage對象處理階段調用了主View 對象(對應PhoneWindow中的DecorView對象)的事件提交函數如(dispatchKeyEvent)函數向視圖對象提交輸入事件,在當前窗口的視圖樹中派發事件。派發完後返回進行其它處理如聚焦切換等;
主視圖的dispatchKeyEvent函數首先調用dispatchKeyShortcutEvent及performPanelShortcut函數進行快捷鍵處理,然後調用getCallback返回視圖的回調對象,ACTIVITY實現了 Window.Callback,因此如果主視圖綁定有ACTIVITY,則getCallback返回主視圖邦定的ACTIVITY對象,則主視圖的dispatchKeyEvent函數繼續調用ACTIVITY對象的dispatchKeyEvent函數進行事件派發,否則調用其父類的dispatchKeyEvent函數直接在視圖樹上向上傳遞事件。
在ACTIVITY的dispatchKeyEvent函數中首先通過getWindow函數獲得ACTIVITY對象的Window,然後調用Window的superDispatchKeyEvent在視圖樹中派發事件,派發到各個焦點ViewGroup和Focuse View;如果視圖樹沒有處理事件則調用event.dispatch函數派發事件由ACTIVITY對象本身處理,ACTIVITY對象的事件回調接口在這裡被調用;
一步造成PhoneWindow的superDispatchKeyEvent函數被調用,其實際又調用的是主視圖DecorView的superDispatchKeyEvent函數;DecorView的superDispatchKeyEvent函數調用其父類的dispatchKeyEvent函數,由於DecorView繼承於ViewGroup,因此最終ViewGroup的dispatchKeyEvent函數被調用;
ViewGroup的dispatchKeyEvent函數首先調用其父類的dispatchKeyEvent函數向父類視圖派發事件;父類的視圖沒有處理事件,再通過ViewGroup對象的mFocused(焦點子視圖)成員向下一級焦點視圖派發事件。這樣一級級向各個焦點ViewGroup和Focuse View視圖派發事件,完成整個視圖樹的事件派發。
如果ACTIVITY的dispatchKeyEvent函數和當前窗口的視圖樹都沒有處理事件,則根據按鍵狀態調用PhoneWindow的onKeyDown函數或onKeyUp函數。
以上事件在視圖樹的派發也是職責鏈模式(Chain of Responsibility)的采用,根據構成窗口的視圖樹完成事件的派發。
由服務端的inputPublisher對象和客戶端的inputConsumer對象還共同構成了生產/消費者模式,通過inputPublisher對象發布事件, inputConsumer對象消費和讀取事件。
三、事件的過濾和插入及事件的攔截流程
事件的過濾和攔截采用了策略模式機制進行不同的事件處理,由類圖中的InputManagerService、InputFilter、PhoneWindowManager、WindowManagerCallback、InputMonitor等JAVA對象共同完成事件的過濾和攔截;WindowManagerCallback、IInputMonitor主要負責回調事件的轉發,IInputFilter接口是事件過濾請求的處理接口;應用可以通過InputManager對象的injectInputEvent函數完成事件的插入;PhoneWindowManager對象最終負責派發事件的攔截。事件的過濾請求和攔截請求流程相似,只是目的地不一樣。
事件的插入流程如下:InputManager對象的injectInputEvent函數調用InputManagerService服務的同名injectInputEvent函數,InputManagerService服務的injectInputEvent函數通過JNI調用InputDispatcher的injectInputEvent函數,在InputDispatcher的injectInputEvent函數中首先進行一些權限判斷,然後根據注入的事件類型構造一個插入事件,如按鍵對應的KeyEntry對象,然後通過enqueueInboundEventLocked函數把插入事件注入mInboundQueue隊列,然後喚醒派發流程,事件注入完成。
過濾請求和攔截請求流程都由InputDispatche對象發起,並通過InputDispatcherPolicyInterface接口函數向JAVA層傳遞請求。
InputDispatcherPolicyInterface接口有幾個輸入事件攔截回調接口函數(如按鍵事件對應的 interceptKeyBeforeQueueing、interceptKeyBeforeDispatching事件攔截接口),用來向java層傳遞事件隊列前攔截請求和事件提交前攔截請求。InputDispatcherPolicyInterface接口還有一個事件過濾接口filterInputEvent 函數用來傳遞向java 層過濾請求。
NativeInputManager對象是InputDispatcherPolicyInterface接口的實現對象,而在InputDispatcher對象中則有一個指向NativeInputManager對象的引用字段mPolicy,用來在事件提交線程中通過InputDispatcherPolicyInterface接口並通過NativeInputManager對象向java層傳送事件攔截和過濾請求。
事件監聽請求在InputDispatche對象的函數notifyKey中發送,在事件輸入線程接收到事件並調用InputDispatcher對象的函數notifyKey向其傳送事件時,notifyKey函數根據過濾選項是否打開調用InputDispatcherPolicyInterface的接口函數filterInputEvent來發送事件過濾請求。
事件的攔截請求包括隊列前的事件攔截和提交前的攔截,下面以按鍵事件的攔截為例進行說明。
對於隊列前的按鍵事件攔截請求,在InputDispatche對象的notifyKey函數和injectInputEvent函數提交輸入按鍵事件到隊列之前,通過調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeQueueing函數發送事件的隊列之前攔截請求;
而對於事件提交前的按鍵事件攔截通過調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeDispatching回調函數來發送其攔截請求,其流程如下:
在InputDispatche對象的事件提交函數dispatchKeyLocked中判斷事件的policyFlags是否含有POLICY_FLAG_PASS_TO_USER,如果含有POLICY_FLAG_PASS_TO_USER就調用InputDispatche對象的postCommandLocked函數,postCommandLocked的參數為doInterceptKeyBeforeDispatchingLockedInterruptible函數指針。在postCommandLocked函數中構造一個CommandEntry對象,其command方法為參數傳進來的函數指針,並放入mCommandQueue隊列,然後函數dispatchKeyLocked返回,事件在事件提交之前被攔截不再繼續派發。
在函數dispatchKeyLocked返回到dispatchOnce,繼續調用runCommandsLockedInterruptible函數對mCommandQueue隊列中的命令對象進行處理,對於mCommandQueue隊列中的每一個命令對象執行命令對象的command方法,對於剛才放入的命令實際調用的是作為postCommandLocke參數的InputDispatch:doInterceptKeyBeforeDispatchingLockedInterruptible函數指針;因此doInterceptKeyBeforeDispatchingLockedInterruptible函數被調用,在doInterceptKeyBeforeDispatchingLockedInterruptible函數中調用InputDispatcherPolicyInterface接口對象的interceptKeyBeforeDispatching事件攔截回調函數發送事件提交前的攔截請求。
InputDispatcherPolicyInterface接口對象在InputDispatcher對象構造時把對NativeInputManager對象的引用作為參數傳入賦值給InputDispatcher對象的InputDispatcherPolicyInterface接口類型的字段mPolicy,因此上述的對InputDispatcherPolicyInterface接口事件的調用實際調用的是NativeInputManager對象的相應函數。
而NativeInputManage對象的攔截和過濾回調函數又通過JNI調用JAVA層InputManagerService服務的相應函數,如interceptKeyBeforeDispatching、interceptKeyBeforeQueueing和filterInputEvent函數。
InputManagerService服務的事件攔截函數實際調用的是WindowManagerCallbacks接口的相應接口函數,而InputMonitor對象是WindowManagerCallbacks接口的實現,因此對WindowManagerCallbacks接口函數的調用就是對InputMonitor對象的相應函數的調用。InputMonitor對象的對應函數又通過WindowManagerService窗口管理服務中的PhoneWindowManager對象引用調用PhoneWindowManager對象的對應函數,最終完成事件的攔截處理。
而InputManagerService服務的filterInputEvent函數調用IInputFilter接口對象的相應函數進行事件過濾處理。
InputFilter對象由InputManagerService服務的setInputFilter函數賦值,並通過JNI打開InputDispatche對象的FilterEnabled標志,完成過濾後的事件又在InputFilterHost對象中采用InputManager對象的injectInputEvent函數一樣的流程重新注入InputDispatch對象的mInboundQueue隊列。
版權所有,轉載時請尊重原創顯要處注明鏈接,謝謝!
Volley 是一個 HTTP 庫,它能夠幫助 Android app 更方便地執行網絡操作,最重要的是,它更快速高效。我們可以通過開源的 AOSP 倉庫獲取到 Voll
側滑菜單在很多應用中都會見到,最近QQ5.0側滑還玩了點花樣~~對於側滑菜單,一般大家都會自定義ViewGroup,然後隱藏菜單欄,當手指滑動時,通過Scroller或者
寫在前面的話對於TextView,我想大家都已經熟的不能再熟了。但是它的用法我們真的熟麼?為了避免總是一言不合就去翻官方文檔,在這裡我總結一下我也可能是你容易忽視的一些細
介紹 做項目到一定龐大的時候就會發現方法數太多,安裝包根本就裝不上去了,這個也不足為奇,我們都知道當方法數目超過65536這個數目限制的時候,擋在2.x的系統上面就會出現