Android 事件

事件分為按鍵事件分發(fā),觸摸事件分發(fā),還有軌跡球事件,軌跡球已經(jīng)被淘汰,按鍵事件分發(fā)主要是在TV上,使用遙控器做按鍵操作。觸摸事件分發(fā)及pointerEvent則是觸摸屏設(shè)備的觸摸點(diǎn)分發(fā),此處主要討論keyEvent事件分發(fā)。

分發(fā)過程:

第一步,WindowManagerService(WMS) 中有一個(gè)KeyInputQueue的子類,該類內(nèi)部有一個(gè)thread去調(diào)用native方法讀取用戶的按鍵,觸摸消息,然后把消息保存到QueueEvent的消息隊(duì)列中,然后WMS中有一個(gè)對(duì)應(yīng)的hander來處理這個(gè)輸入消息隊(duì)列InputDispatcherThread,它內(nèi)部有一個(gè)線程對(duì)這些消息就行處理,然后分發(fā)給對(duì)應(yīng)的窗口(至于怎么匹配對(duì)應(yīng)的窗口這里不說)。

第二步:wms通過IPC的Binder機(jī)制把消息轉(zhuǎn)給對(duì)應(yīng)窗口PhoneWindow,它是actiivty的成員變量mWindow,PhoneWindow是一個(gè)window的子類,mWindow里面有一個(gè)成員變量mWindowManager,而mWindowManager是WindowMangerImpl類實(shí)例的引用,另外WindowMangerImpl里面包含ViewRoot,這個(gè)Viewroot就是Actvity與通信的一個(gè)handler對(duì)象,ViewRoot對(duì)應(yīng)一個(gè)ViewRootImpl,其實(shí)現(xiàn)了一個(gè)InputHandler,ViewRoot拿到消息后調(diào)用InputHandler去調(diào)用handleKey函數(shù),然后該函數(shù)再調(diào)用ViewRott的dispatchKey函數(shù),會(huì)發(fā)送一個(gè)DISPATCH_KEY消息,然后調(diào)用deliverKeyEvent函數(shù)然后分三步走:

1.dispatchKeyEventPreIme ?(分發(fā)給輸入法給一個(gè)機(jī)會(huì)去處理)

2.輸入法去響應(yīng),如果輸入法窗口處理了這個(gè)消息則直接返回,否則走第三步

3.deliverKeyEventPostIme(event,?sendDone);

下面是這三步的源碼:

1.privatevoiddeliverKeyEvent(KeyEvent?event,booleansendDone)?{

2.if(ViewDebug.DEBUG_LATENCY)?{

3.mInputEventDeliverTimeNanos?=?System.nanoTime();

4.}

5.

6.if(mInputEventConsistencyVerifier?!=null)?{

7.mInputEventConsistencyVerifier.onKeyEvent(event,0);

8.}

9.

10.//?If?there?is?no?view,?then?the?event?will?not?be?handled.

11.if(mView?==null||?!mAdded)?{

12.finishKeyEvent(event,?sendDone,false);

13.return;

14.}

15.

16.if(LOCAL_LOGV)?Log.v(TAG,"Dispatching?key?"+?event?+"?to?"+?mView);

17.

18.//?Perform?predispatching?before?the?IME.

19.if(mView.dispatchKeyEventPreIme(event))?{

20.finishKeyEvent(event,?sendDone,true);

21.return;

22.}

23.

24.//?Dispatch?to?the?IME?before?propagating?down?the?view?hierarchy.

25.//?The?IME?will?eventually?call?back?into?handleFinishedEvent.

26.if(mLastWasImTarget)?{

27.InputMethodManager?imm?=?InputMethodManager.peekInstance();

28.if(imm?!=null)?{

29.intseq?=?enqueuePendingEvent(event,?sendDone);

30.if(DEBUG_IMF)?Log.v(TAG,"Sending?key?event?to?IME:?seq="

31.+?seq?+"?event="+?event);

32.imm.dispatchKeyEvent(mView.getContext(),?seq,?event,?mInputMethodCallback);

33.return;

34.}

35.}

36.

37.//?Not?dispatching?to?IME,?continue?with?post?IME?actions.

38.deliverKeyEventPostIme(event,?sendDone);

39.}

第三步:我們不考慮輸入法窗口攔截key事件的操作,直接進(jìn)入第三步deliverKeyEventPostIme,從里面進(jìn)去有這么一段關(guān)鍵代碼

1.if(mView.dispatchKeyEvent(event))?{

2.finishKeyEvent(event,?sendDone,true);

3.return;

4.}

解析:mView是PhoneWindow.DecorView對(duì)象,DecorView是PhoneWindow的子類,繼承于FramwLayout,其實(shí)這個(gè)DecorView就是我們看到的activity上對(duì)應(yīng)的界面,它里面有一個(gè)title對(duì)應(yīng)狀態(tài)欄,還有一個(gè)contentview,對(duì)應(yīng)activity里面的setContentView方法。DecorView是定義在PhoneWindow里面,我們看這個(gè)內(nèi)部類的dispatchKeyEvent對(duì)應(yīng)的源碼:

public DecorView(Context context, int featureId) {

super(context);

mFeatureId = featureId;

}

public void setBackgroundFallback(int resId) {

mBackgroundFallback.setDrawable(resId != 0 ? getContext().getDrawable(resId) : null);

setWillNotDraw(getBackground() == null && !mBackgroundFallback.hasFallback());

}

@Override

public void onDraw(Canvas c) {

super.onDraw(c);

mBackgroundFallback.draw(mContentRoot, c, mContentParent);

}

@Override

public boolean dispatchKeyEvent(KeyEvent event) {

final int keyCode = event.getKeyCode();

final int action = event.getAction();

final boolean isDown = action == KeyEvent.ACTION_DOWN;

if (isDown && (event.getRepeatCount() == 0)) {

// First handle chording of panel key: if a panel key is held

// but not released, try to execute a shortcut in it.

if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {

boolean handled = dispatchKeyShortcutEvent(event);

if (handled) {

return true;

}

}

// If a panel is open, perform a shortcut on it without the

// chorded panel key

if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {

if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {

return true;

}

}

}

//給activity一次處理dispatchKeyEvent的機(jī)會(huì),比如按下菜單鍵,沒消費(fèi)的話最后還是調(diào)用getWindow.superDispatchKeyEvent回調(diào)了DecorView中super.dispatch方法中去了,也就是ViewGroup的dispatch方法

if (!isDestroyed()) {

final Callback cb = getCallback();

final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event);

super.dispatchKeyEvent(event);

if (handled) {

return true;

}

return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)

: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);

}

}

源碼注釋的很清楚,首先處理系統(tǒng)快捷鍵,然后調(diào)用Window.callback的dispatchKeyEvent()(cb.dispatchKeyEvent(event),cb是一個(gè)window.callback的接口實(shí)現(xiàn),這里就行接口回調(diào),實(shí)現(xiàn)window.Callback的主要是activity),所以就走到了Activity里面的dispatchKeyEvent方法里面去了,下面我們看看Activity里面改方法做了啥:

public boolean dispatchKeyEvent(KeyEvent event) {

onUserInteraction();

// Let action bars open menus in response to the menu key prioritized over

// the window handling it

if (event.getKeyCode() == KeyEvent.KEYCODE_MENU &&

mActionBar != null && mActionBar.onMenuKeyEvent(event)) {

return true;

}

Window win = getWindow();

if (win.superDispatchKeyEvent(event)) {

return true;

}

View decor = mDecor;

if (decor == null) decor = win.getDecorView();

return event.dispatch(this, decor != null

? decor.getKeyDispatcherState() : null, this);

}

首先是win.superDispatchKeyEvent(event),PhoneWindow對(duì)應(yīng)的源碼是:

@Override

public boolean superDispatchKeyEvent(KeyEvent event) {

return mDecor.superDispatchKeyEvent(event);

}

這里還是調(diào)用的mDecor.superDispatchKeyEvent(event)對(duì)應(yīng)于DecorView的源碼是:

public boolean superDispatchKeyEvent(KeyEvent event) {

// Give priority to closing action modes if applicable.

if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {

final int action = event.getAction();

// Back cancels action modes first.

if (mActionMode != null) {

if (action == KeyEvent.ACTION_UP) {

mActionMode.finish();

}

return true;

}

}

return super.dispatchKeyEvent(event);

}

走到這里,我們發(fā)現(xiàn)它調(diào)用了super.dispatchKeyEvent(event),也就是FrameLayout,我追蹤到源碼并無重寫,故方法會(huì)走到ViewGroup的dispatchKeyEvent中去,這個(gè)是我們?nèi)粘=佑|的最多的了。至于ViewGroup的DispatchKeyEvent如果走,我們后面再說。我們回到mView.dispatchKeyEvent中去,我們回憶一下步驟的三步:a.處理系統(tǒng)快捷鍵dispatchKeyShortcutEvent(event);b.a沒有消費(fèi),則final ?cb.dispatchKeyEvent(event),此時(shí)進(jìn)入actiivty的dispatchKeyevent中去處理,首先處理menu操作,如果沒有消費(fèi),就調(diào)用phonwindow的superDispatchKeyEevent,最后走到的都是ViewGroup里面去遞歸, 如果我們看到的界面上的那些個(gè)view沒有消費(fèi)此事件,那么在在? ? ? ?return event.dispatch(this, decor != null

? decor.getKeyDispatcherState() : null, this);

這段代碼是actiivty里面dispatchKeyEvent最后的返回,傳的是actiivty自己的引用,跟蹤之后發(fā)現(xiàn)調(diào)用的是KeyEvent的OnkeyDown事件,也就是activity里面的onkeyDown.

綜上所述,activity里面的onkeydown是后于view里面的Onkeydown調(diào)用的。如果view里面的onkeydown消費(fèi)了此事件,那么activity的onKeyDown是走不到的。

最后來整理一下思路,理一下流程:

1.ViewRoot里面的InputHandler的handleKey,然后再是ViewRoot的dispatchKey,然后再是deliverKeyEvent,如果View系統(tǒng)有輸入法則被輸入法窗口攔截InputMethodManager對(duì)象的dispatchKeyEvent。攔截之前有一次DecorView對(duì)象mView的dispatchKeyEventPreIme(event)的操作。然后如果沒有攔截則RootViewImpl的deliverKeyEventPostIme方法。

源碼對(duì)應(yīng)于ViewRootImpl中:

1.privatevoiddeliverKeyEvent(QueuedInputEvent?q)?{

2.finalKeyEvent?event?=?(KeyEvent)q.mEvent;s

3.if(mInputEventConsistencyVerifier?!=null)?{

4.mInputEventConsistencyVerifier.onKeyEvent(event,0);

5.}

6.

7.if((q.mFlags?&?QueuedInputEvent.FLAG_DELIVER_POST_IME)?==0)?{

8.//?If?there?is?no?view,?then?the?event?will?not?be?handled.

9.if(mView?==null||?!mAdded)?{

10.finishInputEvent(q,false);

11.return;

12.}

13.

14.if(LOCAL_LOGV)?Log.v(TAG,"Dispatching?key?"+?event?+"?to?"+?mView);

15.

16.//?Perform?predispatching?before?the?IME.

17.if(mView.dispatchKeyEventPreIme(event))?{

18.finishInputEvent(q,true);

19.return;

20.}

21.

22.//?Dispatch?to?the?IME?before?propagating?down?the?view?hierarchy.

23.//?The?IME?will?eventually?call?back?into?handleImeFinishedEvent.

24.if(mLastWasImTarget)?{

25.InputMethodManager?imm?=?InputMethodManager.peekInstance();

26.if(imm?!=null)?{

27.finalintseq?=?event.getSequenceNumber();

28.if(DEBUG_IMF)?Log.v(TAG,"Sending?key?event?to?IME:?seq="

29.+?seq?+"?event="+?event);

30.imm.dispatchKeyEvent(mView.getContext(),?seq,?event,?mInputMethodCallback);

31.return;

32.}

33.}

34.}

35.

36.//?Not?dispatching?to?IME,?continue?with?post?IME?actions.

37.deliverKeyEventPostIme(q);

38.}

2.在ViewRootImpl的deliverKeyEventPostIme(q)方法中調(diào)用

1.//?Deliver?the?key?to?the?view?hierarchy.

2.if(mView.dispatchKeyEvent(event))?{

3.finishInputEvent(q,true);

4.return;

5.}

3.mView就是DecorView對(duì)象的dispatchKeyEvent操作做了三步,一個(gè)是處理快捷鍵,二個(gè)是調(diào)用Window.Callback對(duì)象的dispatchKeyEvent。

最后如果上面兩步都沒消費(fèi)就調(diào)用phonewindow的onKeyDown,onKeyUp事件。其中第二步是actiivty的dispatchKeyEvent為入口,里面是則會(huì)給增加了一個(gè)菜單按鈕的攔截,然后就又調(diào)用Window對(duì)象的superDispatchKeyEvent方法,其實(shí)就是PhoneWindow的方法攔截,這個(gè)方法最后還是調(diào)用的mDecorView.superDispatchKeyEvent方法,這個(gè)方法攔截了back按鍵事件之后又把事件轉(zhuǎn)給了DecorView的super.dispatchKeyEvent,最后走到了viewTree里面去了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,763評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,238評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,823評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,604評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,339評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,713評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,712評(píng)論 3 445
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,893評(píng)論 0 289
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,448評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,201評(píng)論 3 357
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,397評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,944評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,631評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,033評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,321評(píng)論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,128評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,347評(píng)論 2 377

推薦閱讀更多精彩內(nèi)容