Android按鍵事件傳遞流程(二)


5    應用層如何從Framework層接收按鍵事件

由3.2和4.5.4節可知,當InputDispatcher通過服務端管道向socket文件描述符發送消息后,epoll機制監聽到了I/O事件,epoll_wait就會執行返回發生事件的個數給eventCount,主線程開始執行epoll_wait后面的代碼:

fd是客戶端socket文件描述符,不是mWakeReadPipeFd,因此if語句不成立,進入else子句。mRequests不為空(3.4.2.2節中已經把Request保存在了mRequests中),pushResponse函數把request取出來賦給response,再放到mResponses容器中保存。

mMessageEnvelopes被初始化為空,也沒有加入數據,依然為空,這句:

不成立,跳過while循環。

mResponses不為空,for循環取出里面的response對象,然后執行:

fd就是服務端socket文件描述符,events是發生的事件,data為空,response.request.callback就是addFd中的第4個實參NativeInputEventReceiver對象也是LooperCallback對象,這句話就是回調主線程中的NativeInputEventReceiver對象的handleEvent函數

 

5.1    NativeInputEventReceiver的handleEvent

先對events事件進程必要的檢查,如果包含ALOOPER_EVENT_ERROR或ALOOPER_EVENT_HANGUP表示管道關閉,這種情況下丟棄事件

如果為ALOOPER_EVENT_INPUT事件,調用consumeEvents繼續執行;如果為ALOOPER_EVENT_OUTPUT表示客戶端已經收到數據,需要發送一個完成信號給服務端

 

5.2    NativeInputEventReceiver的consumeEvents

在for循環中調用InputConsumer對象的consume函數從socket客戶端獲取InputDispatcher發送來的事件保存到inputEvent對象中,先分析consume方法,然后返回來再看后面的代碼

在consume函數中有這句:

receiveMessage的源碼中有這句:

在3.2節通過send向服務端socket發送了數據,那么在此處通過recv從客戶端socket上獲取的消息並保存到mMsg中,如果成功,再根據事件類型選擇case語句:

把消息保存到KeyEvent對象中,再付給outEvent,如果一切成功,返回OK到5.2節這句mInputConsumer.consume的后面繼續執行:

mReceiverWeakGlobal就是傳遞過來的WindowInputEventReceiver對象的本地引用

如果讀取所有事件成功,再根據事件的類型選擇相應執行語句,如果是按鍵事件,就調用android_view_KeyEvent_fromNative把按鍵事件傳遞給java層KeyEvent對象並初始化,然后返回該對象賦給變量inputEventObj,繼續執行到這句:

receiverObj.get()得到WindowInputEventReceiver對象,gInputEventReceiverClassInfo.dispatchInputEvent就是待調方法的id號,CallVoidMethod函數的作用就是調用第一個參數WindowInputEventReceiver對象的dispatchInputEvent方法,后面兩個實參會傳遞給該方法,如果這一切都成功,就把按鍵事件傳遞到java層處理。

由於WindowInputEventReceiver中沒有實現dispatchInputEvent,因此直接調用父類InputEventReceiver的dispatchInputEvent方法

 

5.3    InputEventReceiver的dispatchInputEvent

onInputEvent在WindowInputEventReceiver中已經實現,就調用WindowInputEventReceiver中的onInputEvent方法,onInputEvent調用了enqueueInputEvent

 

5.4    ViewRootImpl的enqueueInputEvent

obtainQueuedInputEvent方法把輸入事件封裝成QueuedInputEvent對象並放入到QueuedInputEvent對象池中,然后取出一個QueuedInputEvent事件后按照次序排列,再調用doProcessInputEvents方法從隊列中循環取出事件發送給輸入法或者應用程序等不同階段進行處理。

doProcessInputEvents的調用過程:

doProcessInputEvents  —-> deliverInputEvent  —-> stage.deliver(q)

當前事件還沒有處理,因此不包含FLAG_FINISHED標致,if語句不成立;正常情況下不會丟棄當前事件,第一個else子句也不成立,執行最后一個else子句。apply的第二個參數是onProcess方法,ViewRootImpl中有8個onProcess方法,具體調用哪個?

在setView方法最后創建了很多InputStage對象:

InputStage是抽象基類,ViewPostImeInputStage,EarlyPostImeInputStage,ViewPreImeInputStage等對象都是為了處理輸入事件在不同階段而創建的,比如:ViewPostImeInputStage表示發送輸入事件給view樹進行處理,這些輸入事件都是在輸入法處理之后的。ViewPreImeInputStage表示輸入事件必須在輸入法處理之前發送給view樹處理。

ViewPreImeInputStage表示在輸入法之前處理,ImeInputStage表示進入輸入法處理,ViewPostImeInputStage表示發送給視圖。如果有輸入法窗口,就先傳輸給ViewPreImeInputStage處理,如果沒有,傳輸給ViewPostImeInputStage,一般情況下,都是傳給ViewPostImeInputStage。

此處會調用ViewPostImeInputStage的onProcess來處理:

if語句成立,調用processKeyEvent

 

5.5     ViewPostImeInputStage的processKeyEvent

processKeyEvent方法非常重要了,做了很多任務

mView是DecorView,所有view的根,這段話就是把按鍵事件傳給view處理,從此處開始就正式轉交給應用層,在第6節將進行詳細分析

處理Ctrl組合按鍵

處理所有回退按鍵事件,主要是一些還沒有處理的特殊按鍵。比如相機拍照、撥號按鍵等。如果特殊按鍵沒有在PhoneWindowManager、view樹、窗口中處理,就傳到此處

處理方向鍵和TAB鍵,找到獲得焦點的view並把這幾個按鍵傳遞過去,如果沒有view有焦點,就找一個最合適的view並把按鍵傳遞過去

小結:

應用程序客戶端通過NativeInputEventReceiver的InputConsumer方法從客戶端管道InputChannel中獲取事件消息,經過過濾,轉化成應用層按鍵類型,再把按鍵事件傳遞到輸入法窗口,應用層

 

6.    應用層接收到按鍵事件后如何傳遞

由5.5節這句:

可知,按鍵事件傳遞到了mView的dispatchKeyEvent方法,mView就是PhoneWindow內部類DecorView對象,因此,應用層按鍵事件就從DecorView的dispatchKeyEvent方法開始

也可以這樣理解,InputDispatcher先找到當前獲得焦點的窗口,把事件發送給該窗口,窗口在啟動activity時會創建,按鍵事件就傳遞到了獲得焦點的窗口對應的所有view的根類DecorView,也可以說傳遞給了獲得焦點的窗口對應的Activity對象。

Q10    mView是什么時候創建的?如何傳遞的?見6.6節

 

6.1    DecorView的dispatchKeyEvent

前面兩個if語句是快捷按鍵的處理

getCallback返回的是CallBack對象cb,cb對象代表一個Activity或Dialog對象,一般情況下不為空。本文主要討論Activity,不再使用“Activity或Dialog對象”這樣的術語;

mFeatureId:代表應用程序的特征標識或者整個屏幕的標識,如果是應用程序,就為-1,具體賦值過程為:

Activity的onCreate —-> setContentView —-> PhoneWindow的setContentView —-> installDecor() —->

generateDecor() —-> new DecorView(getContext(), -1)

如果Activity對象不為空,mFeatureId為-1,調用Activity對象的dispatchKeyEvent方法,將在6.2節分析;

如果為空,就調用super.dispatchKeyEvent(event)即父類ViewGroup的dispatchKeyEvent,將在6.2.1節分析。

如果返回結果為true,表明已經消耗,按鍵事件不再往后傳遞,否則執行到:

這一步傳遞到PhoneWindow的onKeyDown、onKeyUp方法,將在6.4節分析。

Q11    cb對象為什么是Activity?

在這篇文章:啟動Activity的流程(Launcher中點擊圖標啟動)

過程18中創建Activity對象后,調用了Activity對象的attach進行初始化,在attach中有:

PolicyManager類的靜態方法makeNewWindow —-> Policy的makeNewWindow —-> new PhoneWindow(context)

makeNewWindow 最終創建了一個PhoneWindow對象,setCallback方法把該this對象即Activity傳過去賦值給mCallback,然后getCallback返回mCallback即該Activity對象

通過這段話可知,當啟動某個應用的Activity時,系統會創建一個PhoneWindow對象與之對應並擁有一個該對象引用,在PhoneWindow對象中通過該對象引用回調Activity的方法,比如dispatchKeyEvent。

 

6.2    Activity的dispatchKeyEvent

Activity的dispatchKeyEvent起到攔截按鍵作用,如果這一步不處理,將分發給view或viewGroup處理。

如果有按鍵、觸摸、軌跡球事件分發給Activity時,在具體事件處理之前,會回調onUserInteraction,一般情況下,用戶需要自行實現該方法,與onUserLeaveHint一起配合使用輔助Activity管理狀態欄通知。在按鍵按下、抬起時都會觸發該方法回調;但在觸摸時,只有觸摸按下時被回調,觸摸移動、抬起時不會回調。

如果有Menu鍵且狀態欄ActionBar消耗了該鍵,就直接返回true;否則繼續往下處理

 

獲得當前Activity對應的Window對象,也就是PhoneWindow,把按鍵事件傳遞給PhoneWindow對象,主要對Back按鍵松開的特殊處理,如果沒有消耗,連同其它按鍵事件一起傳遞到super.dispatchKeyEvent即父類ViewGroup,具體處理過程在6.2.1節;

如果父類ViewGroup也沒有處理,傳遞到KeyEvent的dispatch方法,具體處理過程在6.3節。

 

6.2.1    ViewGroup的dispatchKeyEvent

對按鍵事件進行一致性檢查,這種檢查防止同樣的錯誤多次發生,如果有錯誤,日志會打印出來

PFLAG_FOCUSED表示獲得焦點,PFLAG_HAS_BOUNDS表示大小、邊界被確定了,如果ViewGroup本身有焦點且其大小已確定,就調用該ViewGroup的父類即View的dispatchKeyEvent來處理,具體在6.2.1.1中分析

mFocused是ViewGroup內部包含的獲得焦點的子view,如果該子view獲得焦點且大小、邊界已確定,就調用該子view的dispatchKeyEvent處理。子view既可以是ViewGroup、LinearLayout、RelativeLayout等布局也可以是view、TextView、Button、ListView等,第二個if語句就是遞歸傳遞到所有布局和view中

假如某Activity的布局是自定義一個LinearLayout,稱為A,其內部包含一個LinearLayout,稱為B,B中包含一個TextView C,C有焦點。dispatchKeyEvent傳遞流程是:ViewGroup —-> A —-> B —-> C

傳遞到view時的具體情況在6.2.2節

 

6.2.2    view的dispatchKeyEvent

ListenerInfo類專門描述所有view相關監聽器信息的類,比如OnFocusChangeListener、OnScrollChangeListener、OnClickListener、mOnTouchListener等。

當某個view比如button、imageView設置了某個監聽器時,在setOnXXXListener方法中就會調用getListenerInfo方法創建ListenerInfo對象並賦值給mListenerInfo變量,因此,該變量肯定不為空,如果沒有設置監聽器,那就為空,假設有監聽器,下面這條語句成立:

如果view設置了監聽器,且enable屬性為true,會優先調用OnKeyListener的onKey方法處理,如果沒有設置該監聽器或者該監聽器沒有消耗掉,按鍵繼續傳遞到KeyEvent的dispatch

 

6.2.2.1    KeyEvent的dispatch

receiver:既可能是Activity對象,又可能是view對象,當前是view對象,因為是view的dispatchKeyEvent傳過來的,在6.3節,傳遞過來的是Activity對象

state:通過getKeyDispatcherState返回KeyEvent內部類DispatcherState對象,該對象用來進行高級別的按鍵事件處理,如長按事件等;在getKeyDispatcherState方法內部,mAttachInfo是AttachInfo對象,當view關聯到窗口時的一系列信息,AttachInfo類用來描述、跟蹤這些信息,一般情況下不為空

第三個參數target,實參是this,既是Activity或view對象,也是KeyEvent的內部類CallBack類型

case語句先處理按鍵按下ACTION_DOWN動作:

先清掉FLAG_START_TRACKING標記,再調用view的onKeyDown方法,此方法在6.2.2.2節進行分析

如果onKeyDown返回true,表明已經消耗,res為true

如果view的onKeyDown已經消耗掉,且是第一次按下,mFlags包含FLAG_START_TRACKING,就調用startTracking跟蹤按鍵事件,這樣做便於判斷是否是長按事件的條件,如果有長按事件,並且正在跟蹤當前按鍵,就調用view的onKeyLongPress處理:

源碼總是返回false,不作任何處理,用戶可以根據需求重寫該方法來實現長按

case語句處理按鍵松開ACTION_UP動作:

回調view的onKeyUp方法,mKeyCode是按鍵碼值,this是KeyEvent對象,此方法也在6.2.2.2節分析,此時,dispatch主要作用是回調view的onKeyDown、onKeyUp,注意與6.3節的區別。

 

6.2.2.2    view的onKeyDown、onKeyUp

view的onKeyDown:

主要針對KEYCODE_DPAD_CENTER、KEYCODE_ENTER事件進行處理:

如果當前按鍵事件包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,並且view設置了DISABLED屬性,直接返回true,說明該view已經被按下;

如果view設置了單擊CLICKABLE或長按狀態LONG_CLICKABLE,就調用setPressed把該view設置為PRESSED狀態,同時更新其繪制狀態(顯示狀態,比如更換了背景圖片、顏色等);如果僅是長按狀態,系統在500秒后執行下面幾種情形:

a. 如果有長按監聽器OnLongClickListener,就回調onLongClick,如果成功,系統會發出一個觸覺反饋;

b. 如果沒有長按監聽器,就顯示一個菜單。

如果按鍵事件不包含KEYCODE_DPAD_CENTER、KEYCODE_ENTER,onKeyDown不作任何處理,直接return false

 

view的onKeyUp:

在onKeyUp方法中,setPressed(false)去除view的pressed狀態,同時更新其繪制狀態(顯示狀態)

如果在onKeyDown中沒有處理長按事件,那么mHasPerformedLongPress為false,就把長按事件對象從消息的隊列中移除。最后,調用performClick:

如果設置了OnClickListener監聽器,就回調onClick方法。

由此可知,當遙控器按鍵(KEYCODE_DPAD_CENTER、KEYCODE_ENTER)松開時,如果設置了OnClickListener監聽器,會調用onClick方法。

 

6.3    keyEvent的dispatch

該方法已經在6.2.2節分析過,只不過receiver對象已經不再是view,而是Activity,因為是從Activity的dispatchKeyEvent傳遞而來,此時,dispatch主要作用是回調Activity的onKeyDown、onKeyUp

 

6.3.1    Activity的onKeyDown,onKeyUp

如果Activity里面的任何view、布局都沒有處理按鍵,就會傳遞到Activity的onKeyDown,onKeyUp。比如,當在EditText中輸入文字時,Activity的onKeyDown,onKeyUp不會接收到按鍵事件,因為EditText有自己的處理按鍵事件的方法,如果此時把焦點從EditText移走,onKeyDown,onKeyUp就會接收到按鍵事件。

onKeyDown源碼:

 

這段話需要結合onKeyUp來看:

如果在Android2.1之前的版本(不包含),按下BACK鍵后調用onBackPressed直接退出Activity;

如果在Android 2.1(包含)之后的版本,先調用startTracking方法把mFlags置為FLAG_START_TRACKING,系統會跟蹤按鍵傳遞過程,直到松開按鍵進入到onKeyUp方法時才會調用onBackPressed,可以重寫該方法退出Activity,也就是說,只有松開BACK按鍵時才會退出Activity,如果不松開不會退出Activity。

隨后根據按鍵模式mDefaultKeyMode決定做哪些事情,此處不是重點,本文不作分析

onKeyUp源碼

if語句就是配合onKeyDown使用的,如果在Android 2.1(包含)之后的版本,松開按鍵時才會退出Activity;其他按鍵直接返回false,一般重寫onKeyUp實現自己的需求。

如果onKeyDown,onKeyUp沒有消耗掉按鍵事件,就逆向返回到KeyEvent的dispatch中處理,如果dispatch也沒有消耗掉,就返回到Activity —-> DecorView —-> PhoneWindow,進入到 PhoneWindow中處理。

 

6.4    PhoneWindow的onKeyDown、onKeyUp

經過上述方法處理,如果返回false,說明所有應用程序層的view、viewGroup、Activity都沒有消耗掉,按鍵事件傳遞到了當前窗口window中進行處理

onKeyDown源碼:

onKeyDown/onKeyUp方法主要針對當前獲得焦點的窗口對一些特殊按鍵進行處理,包括音量+/-,多媒體控制按鍵,MENU,BACK

注意:PhoneFallbackEventHandler中也是對特殊按鍵進行處理,但是那是針對所有所有的窗口,包括當前獲得焦點的窗口,而PhoneWindow只針對當前獲得焦點的窗口

 

6.5    小結

應用層按鍵事件傳遞時涉及到很多情況,大概傳遞流程:
a. 應用層按鍵事件傳遞到view樹根DecorView后分為兩步:view樹內部;view樹外部(獲得焦點的窗口)
如果view樹內部沒有消耗,就傳遞到view樹外部,即傳遞給獲得焦點的窗口的onKeyDown/onKeyUp;

b. view樹內部一般先傳遞到當前Activity對象,如果沒有消耗,傳遞到Activity的onKeyDown/onKeyUp;

c. Activity對象內部先分發給ViewGroup,viewGroup如果本身有焦點就傳遞給其父類view;
如果viewGroup本身沒有焦點,就傳遞給其獲得焦點的子view。子view分為兩種情況:
如果子view是LinearLayout等常見布局,就遞歸傳遞過去,最后傳遞給獲得焦點的view視圖;
如果子view是純粹的view視圖,就傳遞給該視圖;

d. view視圖內部,如果設置了OnKeyListener監聽器,就傳遞給OnKey;
如果沒有OnKeyListener監聽器,就分發給KeyEvent的dispatch,dispatch主要回調view的onKeyDown/onKeyUp;

e. 在view的onKeyDown/onKeyUp中,如果是DPAD_CENTER,KEYCODE_ENTER,直接處理;
否則,更新繪制狀態、執行長按處理、執行onClick方法等。

該小結沒有考慮所有條件,只是大概給出傳遞流程,因為很多時候重寫某個方法返回true不再傳遞下去,因此也就沒有過多步驟。

 

6.6    mView的創建過程

啟動Activity的流程(Launcher中點擊圖標啟動)這篇文章中的過程18的handleResumeActivity方法中,有這段語句:

r.activity就是新啟動的目標Activity對象,getWindow返回mWindow對象,mWindow的創建過程:

PolicyManager對象的makeNewWindow —-> sPolicy.makeNewWindow(context)

sPolicy是Policy對象,程序調到了Policy的makeNewWindow:

創建了一個PhoneWindow對象並返回給mWindow,再賦值給r.window

r.window.getDecorView()方法調用PhoneWindow對象的getDecorView方法

如果mDecor為空,就調用installDecor方法新創建一個DecorView對象,否則,直接返回該對象。

r.window.getDecorView() —-> PhoneWindow的installDecor方法 —->  mDecor = generateDecor() —->

return new DecorView(getContext(), -1)

在PhoneWindow中創建了一個DecorView對象並返回給decor變量

通過這兩句可知,啟動一個新的Activity時,系統會創建一個對應的widow窗口對象(實際是PhoneWindow對象),這是一對一關系;同時,如果已經有了DecorView就復用之,否則,新創建一個DecorView對象(DecorView最終繼承於View,也是一個View對象),這是多對一關系。

getWindowManager返回mWindowManager變量,mWindowManager的賦值語句為:

已經可知mWindow是一個PhoneWindow對象,這樣就調到了PhoneWindow的getWindowManager方法,PhoneWindow中沒有實現getWindowManager,直接調用父類Window的getWindowManager:

WindowManagerImpl繼承了WindowManager,createLocalWindowManager方法源碼:

創建了一個與Activity對應的WindowManagerImpl對象。

調用WindowManagerImpl對象的addView方法,源碼:

mGlobal是WindowManagerGlobal的單例對象,addView方法中有:

root是ViewRootImpl對象,調用其setView方法把DecorView對象傳遞過去並賦值給mView

小結:mView就是與Activity對應的DecorView對象,在創建PhoneWindow對象時創建的。

 

7    特殊按鍵如何處理

特殊按鍵處理方法主要有:

interceptKeyBeforeQueueing

interceptKeyBeforeDispatching

PhoneWindow的onKeyDown/onKeyUp

PhoneFallbackEventHandler的dispatchKeyEvent

每個方法用在不同的時刻

 

7.1    interceptKeyBeforeQueueing

在2.2節提到,NativeInputManager傳遞過來后賦給了mPolicy變量,interceptKeyBeforeQueueing在NativeInputManager中也實現了,interceptKeyBeforeQueueing用以處理系統級按鍵,比如HOME、TVSOURCE等

 

7.1.1    com_android_server_input_InputManagerService.cpp的interceptKeyBeforeQueueing

設置按鍵標志為活動狀態

android_view_KeyEvent_fromNative方法中,通過調用JNI接口的CallStaticObjectMethod方法獲得第二個參數gKeyEventClassInfo.obtain返回的值並把值賦給eventObj,gKeyEventClassInfo.obtain是什么?在register_android_view_KeyEvent函數中通過findclass得知,gKeyEventClassInfo.clazz就是java層KeyEvent類的類本地引用,gKeyEventClassInfo.obtain就是KeyEvent中的obtain方法在本地的method id號;CallStaticObjectMethod函數調用KeyEvent中obtain方法,返回值是java層KeyEvent對象,因此,android_view_KeyEvent_fromNative返回值為java層的KeyEvent對象在本地的引用,賦給keyEventObj

由1.1.1和1.1.2節可知,mServiceObj就是傳遞過來的InputManagerService對象,gServiceClassInfo.interceptKeyBeforeQueueing保存了InputManagerService對象中interceptKeyBeforeQueueing方法的method id號,CallIntMethod方法調用InputManagerService中interceptKeyBeforeQueueing方法並把返回值賦給wmActions變量

根據返回值wmActions決定特殊按鍵的走向,如果wmActions為WM_ACTION_PASS_TO_USER即1,那么把policyFlags設為POLICY_FLAG_PASS_TO_USER,意思是說該按鍵應該傳輸給應用程序處理,不在framework層處理,比如,HOME按鍵不應該傳給應用程序,而應在framework層處理,不針對某一個應用程序,針對的整個系統,在任何應用程序界面下按下HOME都能起作用。

 

7.1.2    InputManagerService的interceptKeyBeforeQueueing

mWindowManagerCallbacks是InputMonitor對象,在1.4節提到過,該對象在InputManagerService對象創建后通過其setWindowManagerCallbacks傳遞過去,便於回調InputMonitor對象中的方法

 

7.3    InputMonitor的interceptKeyBeforeQueueing

mPolicy是WindowManageService對象在初始化時創建的PhoneWindowManager對象,因此,最終調到了PhoneWindowManager的interceptKeyBeforeQueueing

interceptKeyBeforeQueueing作用:當按鍵事件從設備中讀取后,對按鍵進行最早期攔截預處理,因為某些特殊按鍵直接影響設備狀態,比如,電源鍵、喚醒鍵,此外,還包括撥號鍵、掛號鍵、音量鍵等

 

7.2    interceptKeyBeforeDispatching

在3.1節提到過doInterceptKeyBeforeDispatchingLockedInterruptible函數,這也是攔截按鍵方法

 

7.2.1    InputDispatcher的doInterceptKeyBeforeDispatchingLockedInterruptible

在1.1.5節提到,NativeInputManager傳遞過來后賦給了mPolicy變量,所以:

調到了NativeInputManager中的interceptKeyBeforeDispatching,經過與7.1節interceptKeyBeforeQueueing類似的調用過程,interceptKeyBeforeDispatching函數最終會調用到PhoneWindowManager中同名方法。

interceptKeyBeforeDispatching作用:在input dispatcher thread把按鍵分發給窗口之前攔截,根據某種策略決定如何處理按鍵事件。

具體策略為:

如果返回值delay為-1,interceptKeyResult設置為INTERCEPT_KEY_RESULT_SKIP,表示按鍵事件已經消耗掉,不需要傳給應用程序;

如果delay等於0,interceptKeyResult設置為INTERCEPT_KEY_RESULT_CONTINUE,表示按鍵事件沒有特殊處理,繼續傳遞給應用程序;

如果delay大於0,interceptKeyResult設置為INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,表示按鍵事件需要重新執行一次,同時設置按鍵下一次執行時間為now() + delay

interceptKeyBeforeDispatching中攔截的特殊按鍵有:HOME, MENU, SEARCH, SOURCE通道, 亮度調節等

 

7.3    PhoneWindow的onKeyDown/onKeyUp

在6.4節已經分析過,針對當前獲得焦點的窗口進行處理

 

7.4    PhoneFallbackEventHandler的dispatchKeyEvent

在5.5節,有這樣一句:

mFallbackEventHandler就是PhoneFallbackEventHandler對象,這是最后攔截特殊按鍵進行處理的方法,與 PhoneWindow區別的是,PhoneFallbackEventHandler針對所有窗口的

 

7.5    小結

按鍵事件的特殊處理可用邏輯執行順序簡單表達:

PhoneWindowManager的interceptKeyBeforeQueueing —->PhoneWindowManager的interceptKeyBeforeDispatching —-> PhoneWindow的onKeyDown/onKeyUp —-> PhoneFallbackEventHandler的dispatchKeyEvent

這個邏輯不是必須的,主要看按鍵是否消耗、按鍵的具體功能需求,有時候只需要在PhoneWindowManager中處理即可

 

8    總結

 

8.1    按鍵事件傳遞流程總結

a. InputReaderThread獲取輸入設備事件后過濾,保存到InboundQueue隊列中

b. InputDispatcherThread從InboundQueue取出數據,先進行特殊處理,然后找到獲得焦點的窗口,
再把數據臨時放到OutboundQueue中,然后取出數據打包后發送到服務端socket

c. 應用程序主線程中的WindowInputEventReceiver從客戶端socket上讀取按鍵數據,再傳遞給應用層

d. 應用層獲取到事件后先分發給view樹處理,再處理回退事件

 

8.2    輸入事件核心組件

InputManagerService:輸入事件的服務端核心組件,直接創建看門狗Watchdog、NativeInputManager對象,間接創建EventHub、InputManager以及InputDispatcherThread、InputReaderThread線程,通過InputManager方法間接啟動接收器線程、分發器線程;對系統特殊按鍵的處理;注冊服務端管道

NativeInputManager:本地InputManager,創建c++層InputManager、EventHub對象

C++ 層InputManager:創建InputDispatcher、InputReader對象,創建並啟動InputDispatcherThread、InputReaderThread線程

Java層InputManager:提供輸入設備信息、按鍵布局等

InputReader:輸入事件接收者,從EventHub中獲得原始輸入事件信息

InputDispatcher:分發事件給應用層,發送事件的服務端

EventHub:事件的中心樞紐,收集了所有輸入設備的事件,包括虛擬仿真設備事件。

InputEventReceiver:接收輸入事件的客戶端

InboundQueue:InputReaderThread讀取事件后保存到InboundQueue中等到InputReaderThread接收

outboundQueue:InputDispatcherThread從InboundQueue中取出數據放到outboundQueue中等待發送,然后從outboundQueue取出數據發送到服務端InputChannel等待應用層(或者稱為客戶端)接收

c++層InputChannel:一個輸入通道,包含一對本地unix socket對象,服務端InputDispatcherThread向socket對象寫入數據,客戶端InputEventReceiver從客戶端socket讀取數據

 

8.3    按鍵分類

一般按鍵:一般會傳遞到應用程序中進行處理,比如,在app中經常調用菜單鍵彈出菜單,按下方向鍵使得焦點移動等;主要有數字鍵,方向鍵,確認OK鍵,返回BACK鍵,菜單MENU鍵等;

特殊按鍵:一般不傳遞到應用程序中,直接在framework層處理,不與某個應用有直接的關聯,應用范圍適用整個系統,比如,HOME鍵,按下HOME就返回到桌面,不限制於某個應用;音量鍵,控制系統音量,不特別針對某個應用;主要有電源POWER鍵,HOME鍵,音量VOLUME鍵,靜音MUTE鍵等;

TV一般按鍵:在TV的應用中處理的鍵,比如,在DTV的Activity中通過EPG調用節目信息,通過SUBTITLE調用字幕界面等;包含INFO信息鍵,喜愛節目FAV鍵,EPG鍵,字幕SUBTITLE,音軌TRACK鍵,多媒體播放鍵等;

TV特殊按鍵:不傳遞到TV的應用中,在framework層進行處理,比如SOURCE通道鍵,在任何界面下按下SOURCE都能夠調出通道界面切換通道。

在Android手機上,常見的一般按鍵是MENU、BACK,特殊按鍵包括電源鍵,HOME鍵,音量鍵等

在TV上,特殊按鍵主要指HOME鍵,SOURCE鍵,電源鍵,音量鍵等

 

 

8.4    工作記錄

a.  如果主界面包括epg,channellist, TV多個窗口,需要在這些小界面上跳動焦點,如何實現

重寫Activity的dispatchkeyEvent和onKeyDown或onKeyUp,控制某個小界面獲得焦點

b.  如果app中按鍵不響應,通過getEvent命令檢查是否有輸入設備,是否能夠獲得按鍵事件,如果能,可以確保底層沒問題,問題出現在應用層或Framework層,檢查PhoneWindowManager, dispatchKeyEvent;如果getEvent檢查不到設備,檢查驅動(前提是確保硬件等正常);如果有設備,但getEvent獲取不到事件,檢查系統能否接收到紅外信號,同時確定kl文件是否配對。

c.  如要在任何app下都可以啟動、殺死某個應用,可在PhoneWindowManager或PhoneFallbackEventHandler中對按鍵進行特殊處理,采用該應用的包名、類名,直接控制該應用

轉自:feeyan


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM