Android 事件

事件分为按键事件分发,触摸事件分发,还有轨迹球事件,轨迹球已经被淘汰,按键事件分发主要是在TV上,使用遥控器做按键操作。触摸事件分发及pointerEvent则是触摸屏设备的触摸点分发,此处主要讨论keyEvent事件分发。

分发过程:

第一步,WindowManagerService(WMS) 中有一个KeyInputQueue的子类,该类内部有一个thread去调用native方法读取用户的按键,触摸消息,然后把消息保存到QueueEvent的消息队列中,然后WMS中有一个对应的hander来处理这个输入消息队列InputDispatcherThread,它内部有一个线程对这些消息就行处理,然后分发给对应的窗口(至于怎么匹配对应的窗口这里不说)。

第二步:wms通过IPC的Binder机制把消息转给对应窗口PhoneWindow,它是actiivty的成员变量mWindow,PhoneWindow是一个window的子类,mWindow里面有一个成员变量mWindowManager,而mWindowManager是WindowMangerImpl类实例的引用,另外WindowMangerImpl里面包含ViewRoot,这个Viewroot就是Actvity与通信的一个handler对象,ViewRoot对应一个ViewRootImpl,其实现了一个InputHandler,ViewRoot拿到消息后调用InputHandler去调用handleKey函数,然后该函数再调用ViewRott的dispatchKey函数,会发送一个DISPATCH_KEY消息,然后调用deliverKeyEvent函数然后分三步走:

1.dispatchKeyEventPreIme  (分发给输入法给一个机会去处理)

2.输入法去响应,如果输入法窗口处理了这个消息则直接返回,否则走第三步

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事件的操作,直接进入第三步deliverKeyEventPostIme,从里面进去有这么一段关键代码

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

2.finishKeyEvent(event, sendDone,true);

3.return;

4.}

解析:mView是PhoneWindow.DecorView对象,DecorView是PhoneWindow的子类,继承于FramwLayout,其实这个DecorView就是我们看到的activity上对应的界面,它里面有一个title对应状态栏,还有一个contentview,对应activity里面的setContentView方法。DecorView是定义在PhoneWindow里面,我们看这个内部类的dispatchKeyEvent对应的源码:

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的机会,比如按下菜单键,没消费的话最后还是调用getWindow.superDispatchKeyEvent回调了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);

}

}

源码注释的很清楚,首先处理系统快捷键,然后调用Window.callback的dispatchKeyEvent()(cb.dispatchKeyEvent(event),cb是一个window.callback的接口实现,这里就行接口回调,实现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对应的源码是:

@Override

public boolean superDispatchKeyEvent(KeyEvent event) {

return mDecor.superDispatchKeyEvent(event);

}

这里还是调用的mDecor.superDispatchKeyEvent(event)对应于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);

}

走到这里,我们发现它调用了super.dispatchKeyEvent(event),也就是FrameLayout,我追踪到源码并无重写,故方法会走到ViewGroup的dispatchKeyEvent中去,这个是我们日常接触的最多的了。至于ViewGroup的DispatchKeyEvent如果走,我们后面再说。我们回到mView.dispatchKeyEvent中去,我们回忆一下步骤的三步:a.处理系统快捷键dispatchKeyShortcutEvent(event);b.a没有消费,则final  cb.dispatchKeyEvent(event),此时进入actiivty的dispatchKeyevent中去处理,首先处理menu操作,如果没有消费,就调用phonwindow的superDispatchKeyEevent,最后走到的都是ViewGroup里面去递归, 如果我们看到的界面上的那些个view没有消费此事件,那么在在       return event.dispatch(this, decor != null

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

这段代码是actiivty里面dispatchKeyEvent最后的返回,传的是actiivty自己的引用,跟踪之后发现调用的是KeyEvent的OnkeyDown事件,也就是activity里面的onkeyDown.

综上所述,activity里面的onkeydown是后于view里面的Onkeydown调用的。如果view里面的onkeydown消费了此事件,那么activity的onKeyDown是走不到的。

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

1.ViewRoot里面的InputHandler的handleKey,然后再是ViewRoot的dispatchKey,然后再是deliverKeyEvent,如果View系统有输入法则被输入法窗口拦截InputMethodManager对象的dispatchKeyEvent。拦截之前有一次DecorView对象mView的dispatchKeyEventPreIme(event)的操作。然后如果没有拦截则RootViewImpl的deliverKeyEventPostIme方法。

源码对应于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)方法中调用

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

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

3.finishInputEvent(q,true);

4.return;

5.}

3.mView就是DecorView对象的dispatchKeyEvent操作做了三步,一个是处理快捷键,二个是调用Window.Callback对象的dispatchKeyEvent。

最后如果上面两步都没消费就调用phonewindow的onKeyDown,onKeyUp事件。其中第二步是actiivty的dispatchKeyEvent为入口,里面是则会给增加了一个菜单按钮的拦截,然后就又调用Window对象的superDispatchKeyEvent方法,其实就是PhoneWindow的方法拦截,这个方法最后还是调用的mDecorView.superDispatchKeyEvent方法,这个方法拦截了back按键事件之后又把事件转给了DecorView的super.dispatchKeyEvent,最后走到了viewTree里面去了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容