Android中按键消息的派发过程及源码分析
来源:互联网 发布:手机网络不能看视频 编辑:程序博客网 时间:2024/05/21 21:03
Android中消息的整体派发过程:接收消息——消息处理前端——窗口管理系统派发消息——窗口进行消息处理
以上过程中前三步都在WmS中完成,按键消息直接发送给当前窗口,而触摸消息则根据触摸坐标位置来匹配所有窗口,并判断坐标落到哪个窗口区域中,然后把消息发送给相应的窗口。对于按键消息还会涉及到“生理长按”的检测,比如一直按住某个键,那么会产生一些列的按键消息,然而第1个和第2个消息之间往往会间隔较长的时间,这种设计是人类本身的生理特点决定的,因为从按下到弹起的过程中,如果CPU处理太快,会导致产生多次该消息,这往往不是用户所期望的,因此Android把这种消息处理延迟加入到了消息处理前端中,应用程序不需要关心第一次的延迟,只需按普通的DOWN消息处理。
下面具体分析Android中按键消息的派发流程:
每个窗口定义了一个ViewRoot(4.0中是ViewRootImpl)对象,而ViewRoot对象中定义了一个inputHandler,窗口管理系统(WmS)派发消息的过程中会调用inputHandler的handlekey(),该函数再调用ViewRoot中的dispatchKey()函数
private final InputHandler mInputHandler = new InputHandler() { public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) { startInputEvent(finishedCallback); dispatchKey(event, true); } public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) { startInputEvent(finishedCallback); dispatchMotion(event, true); } };
dispatchKey()函数内部发送一个DISPATCH_KEY消息,消息的处理函数为deliverKeyEvent():
private void dispatchKey(KeyEvent event, boolean sendDone) { //noinspection ConstantConditions if (false && event.getAction() == KeyEvent.ACTION_DOWN) { if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { if (DBG) Log.d("keydisp", "==================================================="); if (DBG) Log.d("keydisp", "Focused view Hierarchy is:"); debug(); if (DBG) Log.d("keydisp", "==================================================="); } } Message msg = obtainMessage(DISPATCH_KEY); msg.obj = event; msg.arg1 = sendDone ? 1 : 0; if (LOCAL_LOGV) Log.v( TAG, "sending key " + event + " to " + mView); enqueueInputEvent(msg, event.getEventTime()); } @Override public void handleMessage(Message msg) { switch (msg.what) { ... case FINISHED_EVENT: handleFinishedEvent(msg.arg1, msg.arg2 != 0); break; case DISPATCH_KEY: deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0); break; case DISPATCH_POINTER: deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0); break;deliverKeyEvent()函数的执行流程如下:
1、调用mView.dispatchKeyEventPreIme(),如果有输入法存在,那么按键消息首先会被派发到输入法窗口,如果想在输入法截获消息之前处理该消息,那么可以重载该函数。
2、imm.dispatchKeyEvent()将消息派发到输入法窗口
3、调用deliverKeyEventPostIme()继而调用到mView.dispatchKeyEvent()
private void deliverKeyEvent(KeyEvent event, boolean sendDone) { if (ViewDebug.DEBUG_LATENCY) { mInputEventDeliverTimeNanos = System.nanoTime(); } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } // If there is no view, then the event will not be handled. if (mView == null || !mAdded) { finishKeyEvent(event, sendDone, false); return; } if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView); // Perform predispatching before the IME. if (mView.dispatchKeyEventPreIme(event)) { finishKeyEvent(event, sendDone, true); return; } // Dispatch to the IME before propagating down the view hierarchy. // The IME will eventually call back into handleFinishedEvent. if (mLastWasImTarget) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { int seq = enqueuePendingEvent(event, sendDone); if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq=" + seq + " event=" + event); imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback); return; } } // Not dispatching to IME, continue with post IME actions. deliverKeyEventPostIme(event, sendDone); }
private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) { ... // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { finishKeyEvent(event, sendDone, true); return; } ...}
mView对于应用窗口而言就是PhoneWindow.DecorView,否则就是普通的ViewGroup,我们只讨论DecorView中dispatchKeyEvent的实现:
1、处理系统快捷键
2、调用View中Callback对象的dispatchKeyEvent(),即调用Activity的dispatchKeyEvent()
2、如果Activity没有消耗该消息,则调用PhoneWindow的OnKeyEvent()对消息做最后的处理
@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; } } } 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); }
下面来具体看下Activity中dispatchKeyEvent的执行过程,首先来看源码:
public boolean dispatchKeyEvent(KeyEvent event) { onUserInteraction(); 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); }主要过程如下:
1、调用onUserInteraction(),可重载该函数在消息派发前做一些处理
2、回调Activity包含的Window对象的superDispatchKeyEvent,该函数继而调用mDecor.superDispatchKveyEent,该函数继而又调用super.dispatchKeyEvent,DecorView的父类是FrameLayout,而FrameLayout未重载dispatchKeyEvent,因此最终调用ViewGroup的dispatchKeyEvent
3、如果DecorView未消耗消息,则调用event的dispatch()函数,这里的第一个参数receiver是Activity对象
@Override public boolean superDispatchKeyEvent(KeyEvent event) { return mDecor.superDispatchKeyEvent(event); }
public boolean superDispatchKeyEvent(KeyEvent event) { if (super.dispatchKeyEvent(event)) { return true; } // Not handled by the view hierarchy, does the action bar want it // to cancel out of something special? 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; } // Next collapse any expanded action views. if (mActionBar != null && mActionBar.hasExpandedActionView()) { if (action == KeyEvent.ACTION_UP) { mActionBar.collapseActionView(); } return true; } } return false; }
下面分析ViewGroup中dispatchKeyEvent的执行流程:如果ViewGroup本身拥有焦点,则调用super.dispatchKeyEvent把该消息派发到ViewGroup自身,如果其子视图拥有焦点,则调用mFocused.dispatchKeyEvent将消息派发给子视图,假如子视图也是ViewGroup,并且焦点是其子视图,则继续递归调用ViewGroup的dispatchKeyEvent
@Override public boolean dispatchKeyEvent(KeyEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 1); } if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) { if (super.dispatchKeyEvent(event)) { return true; } } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) { if (mFocused.dispatchKeyEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 1); } return false; }在View类的dispatchKeyEvent中首先回调onKey()函数,应用程序可重载该函数以实现自定义消息处理,如果onKey函数未消耗该消息,则调用event的dispatch函数,在调用该函数是,第一个参数receiver是View对象本身
public boolean dispatchKeyEvent(KeyEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onKeyEvent(event, 0); } // Give any attached key listener a first crack at the event. //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { return true; } if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) { return true; } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; }
如果拥有焦点的View没有处理该按键消息,则继续调用event.dispatch()函数:
/** * Deliver this key event to a {@link Callback} interface. If this is * an ACTION_MULTIPLE event and it is not handled, then an attempt will * be made to deliver a single normal event. * * @param receiver The Callback that will be given the event. * @param state State information retained across events. * @param target The target of the dispatch, for use in tracking. * * @return The return value from the Callback method that was called. */ public final boolean dispatch(Callback receiver, DispatcherState state, Object target) { switch (mAction) { case ACTION_DOWN: { mFlags &= ~FLAG_START_TRACKING; if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state + ": " + this); boolean res = receiver.onKeyDown(mKeyCode, this); if (state != null) { if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) { if (DEBUG) Log.v(TAG, " Start tracking!"); state.startTracking(this, target); } else if (isLongPress() && state.isTracking(this)) { try { if (receiver.onKeyLongPress(mKeyCode, this)) { if (DEBUG) Log.v(TAG, " Clear from long press!"); state.performedLongPress(this); res = true; } } catch (AbstractMethodError e) { } } } return res; } case ACTION_UP: if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state + ": " + this); if (state != null) { state.handleUpEvent(this); } return receiver.onKeyUp(mKeyCode, this); case ACTION_MULTIPLE: final int count = mRepeatCount; final int code = mKeyCode; if (receiver.onKeyMultiple(code, count, this)) { return true; } if (code != KeyEvent.KEYCODE_UNKNOWN) { mAction = ACTION_DOWN; mRepeatCount = 0; boolean handled = receiver.onKeyDown(code, this); if (handled) { mAction = ACTION_UP; receiver.onKeyUp(code, this); } mAction = ACTION_MULTIPLE; mRepeatCount = count; return handled; } return false; } return false; }
该函数中主要根据相应的逻辑回调了receiver中的onKeyDown, onKeyUp,OnKeyLongPress, OnKeyMultiple函数。View中onKeyDown和onKeyUp有自己默认的处理,主要处理presse状态,长按检测,onCick回调。而OnKeyLongPress和OnKeyMultiple为空实现。对于Activity的OnKeyDown和onKeyUp函数主要实现按数字启动打电话程序( onKeyDown)以及back键的onBackPressed回调(onKeyUp)
如果按键消息在View树内部和Activity中没有被处理,就会调用到PhoneWindow的OnKeyDown和OnKeyUp函数,这是按键消息的最后处理机会,在PhoneWindow的OnKeyDown和OnKeyUp函数中主要处理了一些系统按键,例如音量键、音乐播放控制按键、照相机键、菜单键、拨号键、Search键等,具体代码就不再贴了。
以上博文主要根据《Android内核剖析》第13章以及Android源码总结而成
- Android中按键消息的派发过程及源码分析
- Android中按键消息的派发过程及源码分析
- Android中按键消息的派发过程及源码分析
- Android中按键消息的派发过程及源码分析
- 系统入门(15):Android中按键消息的派发过程及源码分析
- Android 触摸消息派发之ViewGroup的派发过程
- android触摸消息的派发过程
- Android View系统源码分析(三)—— 根View内部消息派发过程&ViewGroup.dispatchTouchEvent()
- 按键消息派发流程
- View工作原理之按键消息派发过程
- Android-消息机制(一):过程及源码分析
- AndRoid源码分析----------Handler消息机制(2)消息队列的建立过程及Loop循环的工作原理
- Android源码分析-点击事件派发机制
- Android源码分析-点击事件派发机制
- Android源码分析-点击事件派发机制
- Android源码分析-点击事件派发机制
- Android源码分析-点击事件派发机制
- Android源码分析-点击事件派发机制
- 51汇编程序之点亮全部led
- vim 分屏显示
- CSS控制来实现去除超链接的下划线
- IDR--Integer ID Management
- 最简单的方式实现文件下载
- Android中按键消息的派发过程及源码分析
- JUnit单元测试实例说明
- Struts2的Result Type
- 有名管道FIFO的读写
- Could not bind factory to JNDI
- UIView的各种动画详解
- JS操作struts标签问题
- HDU 1052 Tian Ji -- The Horse Racing
- 百度编程大赛的9宫图代码及解析