《Android内核剖析》笔记 第13章 View的按键消息处理

来源:互联网 发布:趣读网络 编辑:程序博客网 时间:2024/04/28 01:37

Android中消息的整体派发过程:接收消息——消息处理前端——窗口管理系统派发消息——窗口进行消息处理

以上过程中前三步都在WmS中完成,按键消息直接发送给当前窗口,而触摸消息则根据触摸坐标位置来匹配所有窗口,并判断坐标落到哪个窗口区域中,然后把消息发送给相应的窗口。对于按键消息还会涉及到“生理长按”的检测,比如一直按住某个键,那么会产生一些列的按键消息,然而第1个和第2个消息之间往往会间隔较长的时间,这种设计是人类本身的生理特点决定的,因为从按下到弹起的过程中,如果CPU处理太快,会导致产生多次该消息,这往往不是用户所期望的,因此Android把这种消息处理延迟加入到了消息处理前端中,应用程序不需要关心第一次的延迟,只需按普通的DOWN消息处理。

下面具体分析Android中按键消息的派发流程:

每个窗口定义了一个ViewRoot(4.0中是ViewRootImpl)对象,而ViewRoot对象中定义了一个inputHandler,窗口管理系统(WmS)派发消息的过程中会调用inputHandler的handlekey(),该函数再调用ViewRoot中的dispatchKey()函数

[java] view plaincopy
  1. private final InputHandler mInputHandler = new InputHandler() {  
  2.         public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {  
  3.             startInputEvent(finishedCallback);  
  4.             dispatchKey(event, true);  
  5.         }  
  6.   
  7.         public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {  
  8.             startInputEvent(finishedCallback);  
  9.             dispatchMotion(event, true);  
  10.         }  
  11.     };  

dispatchKey()函数内部发送一个DISPATCH_KEY消息,消息的处理函数为deliverKeyEvent():

[java] view plaincopy
  1. private void dispatchKey(KeyEvent event, boolean sendDone) {  
  2.     //noinspection ConstantConditions  
  3.     if (false && event.getAction() == KeyEvent.ACTION_DOWN) {  
  4.         if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {  
  5.             if (DBG) Log.d("keydisp""===================================================");  
  6.             if (DBG) Log.d("keydisp""Focused view Hierarchy is:");  
  7.   
  8.             debug();  
  9.   
  10.             if (DBG) Log.d("keydisp""===================================================");  
  11.         }  
  12.     }  
  13.   
  14.     Message msg = obtainMessage(DISPATCH_KEY);  
  15.     msg.obj = event;  
  16.     msg.arg1 = sendDone ? 1 : 0;  
  17.   
  18.     if (LOCAL_LOGV) Log.v(  
  19.         TAG, "sending key " + event + " to " + mView);  
  20.   
  21.     enqueueInputEvent(msg, event.getEventTime());  
  22. }  
  23. @Override  
  24. public void handleMessage(Message msg) {  
  25.     switch (msg.what) {  
  26.     ...  
  27.     case FINISHED_EVENT:  
  28.         handleFinishedEvent(msg.arg1, msg.arg2 != 0);  
  29.         break;  
  30.     case DISPATCH_KEY:  
  31.         deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);  
  32.         break;  
  33.     case DISPATCH_POINTER:  
  34.         deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0);  
  35.         break;  
deliverKeyEvent()函数的执行流程如下:

1、调用mView.dispatchKeyEventPreIme(),如果有输入法存在,那么按键消息首先会被派发到输入法窗口,如果想在输入法截获消息之前处理该消息,那么可以重载该函数。

2、imm.dispatchKeyEvent()将消息派发到输入法窗口

3、调用deliverKeyEventPostIme()继而调用到mView.dispatchKeyEvent()

[java] view plaincopy
  1. private void deliverKeyEvent(KeyEvent event, boolean sendDone) {  
  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.                 int seq = 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.     }  
[java] view plaincopy
  1. private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {  
  2.         ...  
  3.   
  4.         // Deliver the key to the view hierarchy.  
  5.         if (mView.dispatchKeyEvent(event)) {  
  6.             finishKeyEvent(event, sendDone, true);  
  7.             return;  
  8.         }  
  9.         ...  
  10. }  

mView对于应用窗口而言就是PhoneWindow.DecorView,否则就是普通的ViewGroup,我们只讨论DecorView中dispatchKeyEvent的实现:

1、处理系统快捷键

2、调用View中Callback对象的dispatchKeyEvent(),即调用Activity的dispatchKeyEvent()

2、如果Activity没有消耗该消息,则调用PhoneWindow的OnKeyEvent()对消息做最后的处理

[java] view plaincopy
  1. @Override  
  2. public boolean dispatchKeyEvent(KeyEvent event) {  
  3.     final int keyCode = event.getKeyCode();  
  4.     final int action = event.getAction();  
  5.     final boolean isDown = action == KeyEvent.ACTION_DOWN;  
  6.   
  7.     if (isDown && (event.getRepeatCount() == 0)) {  
  8.         // First handle chording of panel key: if a panel key is held  
  9.         // but not released, try to execute a shortcut in it.  
  10.         if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {  
  11.             boolean handled = dispatchKeyShortcutEvent(event);  
  12.             if (handled) {  
  13.                 return true;  
  14.             }  
  15.         }  
  16.   
  17.         // If a panel is open, perform a shortcut on it without the  
  18.         // chorded panel key  
  19.         if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {  
  20.             if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {  
  21.                 return true;  
  22.             }  
  23.         }  
  24.     }  
  25.   
  26.     if (!isDestroyed()) {  
  27.         final Callback cb = getCallback();  
  28.         final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)  
  29.                 : super.dispatchKeyEvent(event);  
  30.         if (handled) {  
  31.             return true;  
  32.         }  
  33.     }  
  34.   
  35.     return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)  
  36.             : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);  
  37. }  

下面来具体看下Activity中dispatchKeyEvent的执行过程,首先来看源码:

[java] view plaincopy
  1. public boolean dispatchKeyEvent(KeyEvent event) {  
  2.     onUserInteraction();  
  3.     Window win = getWindow();  
  4.     if (win.superDispatchKeyEvent(event)) {  
  5.         return true;  
  6.     }  
  7.     View decor = mDecor;  
  8.     if (decor == null) decor = win.getDecorView();  
  9.     return event.dispatch(this, decor != null  
  10.             ? decor.getKeyDispatcherState() : nullthis);  
  11. }  
主要过程如下:

1、调用onUserInteraction(),可重载该函数在消息派发前做一些处理

2、回调Activity包含的Window对象的superDispatchKeyEvent,该函数继而调用mDecor.superDispatchKveyEent,该函数继而又调用super.dispatchKeyEvent,DecorView的父类是FrameLayout,而FrameLayout未重载dispatchKeyEvent,因此最终调用ViewGroup的dispatchKeyEvent

3、如果DecorView未消耗消息,则调用event的dispatch()函数,这里的第一个参数receiver是Activity对象

[java] view plaincopy
  1. @Override  
  2.     public boolean superDispatchKeyEvent(KeyEvent event) {  
  3.         return mDecor.superDispatchKeyEvent(event);  
  4.     }  
[java] view plaincopy
  1.  public boolean superDispatchKeyEvent(KeyEvent event) {  
  2.     if (super.dispatchKeyEvent(event)) {  
  3.         return true;  
  4.     }  
  5.   
  6.     // Not handled by the view hierarchy, does the action bar want it  
  7.     // to cancel out of something special?  
  8.     if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {  
  9.         final int action = event.getAction();  
  10.         // Back cancels action modes first.  
  11.         if (mActionMode != null) {  
  12.             if (action == KeyEvent.ACTION_UP) {  
  13.                 mActionMode.finish();  
  14.             }  
  15.             return true;  
  16.         }  
  17.   
  18.         // Next collapse any expanded action views.  
  19.         if (mActionBar != null && mActionBar.hasExpandedActionView()) {  
  20.             if (action == KeyEvent.ACTION_UP) {  
  21.                 mActionBar.collapseActionView();  
  22.             }  
  23.             return true;  
  24.         }  
  25.     }  
  26.   
  27.     return false;  
  28. }  

下面分析ViewGroup中dispatchKeyEvent的执行流程:如果ViewGroup本身拥有焦点,则调用super.dispatchKeyEvent把该消息派发到ViewGroup自身,如果其子视图拥有焦点,则调用mFocused.dispatchKeyEvent将消息派发给子视图,假如子视图也是ViewGroup,并且焦点是其子视图,则继续递归调用ViewGroup的dispatchKeyEvent

[java] view plaincopy
  1. @Override  
  2. public boolean dispatchKeyEvent(KeyEvent event) {  
  3.     if (mInputEventConsistencyVerifier != null) {  
  4.         mInputEventConsistencyVerifier.onKeyEvent(event, 1);  
  5.     }  
  6.   
  7.     if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {  
  8.         if (super.dispatchKeyEvent(event)) {  
  9.             return true;  
  10.         }  
  11.     } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {  
  12.         if (mFocused.dispatchKeyEvent(event)) {  
  13.             return true;  
  14.         }  
  15.     }  
  16.   
  17.     if (mInputEventConsistencyVerifier != null) {  
  18.         mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);  
  19.     }  
  20.     return false;  
  21. }  
在View类的dispatchKeyEvent中首先回调onKey()函数,应用程序可重载该函数以实现自定义消息处理,如果onKey函数未消耗该消息,则调用event的dispatch函数,在调用该函数是,第一个参数receiver是View对象本身
[java] view plaincopy
  1. public boolean dispatchKeyEvent(KeyEvent event) {  
  2.     if (mInputEventConsistencyVerifier != null) {  
  3.         mInputEventConsistencyVerifier.onKeyEvent(event, 0);  
  4.     }  
  5.   
  6.     // Give any attached key listener a first crack at the event.  
  7.     //noinspection SimplifiableIfStatement  
  8.     ListenerInfo li = mListenerInfo;  
  9.     if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED  
  10.             && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {  
  11.         return true;  
  12.     }  
  13.   
  14.     if (event.dispatch(this, mAttachInfo != null  
  15.             ? mAttachInfo.mKeyDispatchState : nullthis)) {  
  16.         return true;  
  17.     }  
  18.   
  19.     if (mInputEventConsistencyVerifier != null) {  
  20.         mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);  
  21.     }  
  22.     return false;  
  23. }  

如果拥有焦点的View没有处理该按键消息,则继续调用event.dispatch()函数:

[java] view plaincopy
  1. /** 
  2.      * Deliver this key event to a {@link Callback} interface.  If this is 
  3.      * an ACTION_MULTIPLE event and it is not handled, then an attempt will 
  4.      * be made to deliver a single normal event. 
  5.      *  
  6.      * @param receiver The Callback that will be given the event. 
  7.      * @param state State information retained across events. 
  8.      * @param target The target of the dispatch, for use in tracking. 
  9.      *  
  10.      * @return The return value from the Callback method that was called. 
  11.      */  
  12.     public final boolean dispatch(Callback receiver, DispatcherState state,  
  13.             Object target) {  
  14.         switch (mAction) {  
  15.             case ACTION_DOWN: {  
  16.                 mFlags &= ~FLAG_START_TRACKING;  
  17.                 if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state  
  18.                         + ": " + this);  
  19.                 boolean res = receiver.onKeyDown(mKeyCode, this);  
  20.                 if (state != null) {  
  21.                     if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {  
  22.                         if (DEBUG) Log.v(TAG, "  Start tracking!");  
  23.                         state.startTracking(this, target);  
  24.                     } else if (isLongPress() && state.isTracking(this)) {  
  25.                         try {  
  26.                             if (receiver.onKeyLongPress(mKeyCode, this)) {  
  27.                                 if (DEBUG) Log.v(TAG, "  Clear from long press!");  
  28.                                 state.performedLongPress(this);  
  29.                                 res = true;  
  30.                             }  
  31.                         } catch (AbstractMethodError e) {  
  32.                         }  
  33.                     }  
  34.                 }  
  35.                 return res;  
  36.             }  
  37.             case ACTION_UP:  
  38.                 if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state  
  39.                         + ": " + this);  
  40.                 if (state != null) {  
  41.                     state.handleUpEvent(this);  
  42.                 }  
  43.                 return receiver.onKeyUp(mKeyCode, this);  
  44.             case ACTION_MULTIPLE:  
  45.                 final int count = mRepeatCount;  
  46.                 final int code = mKeyCode;  
  47.                 if (receiver.onKeyMultiple(code, count, this)) {  
  48.                     return true;  
  49.                 }  
  50.                 if (code != KeyEvent.KEYCODE_UNKNOWN) {  
  51.                     mAction = ACTION_DOWN;  
  52.                     mRepeatCount = 0;  
  53.                     boolean handled = receiver.onKeyDown(code, this);  
  54.                     if (handled) {  
  55.                         mAction = ACTION_UP;  
  56.                         receiver.onKeyUp(code, this);  
  57.                     }  
  58.                     mAction = ACTION_MULTIPLE;  
  59.                     mRepeatCount = count;  
  60.                     return handled;  
  61.                 }  
  62.                 return false;  
  63.         }  
  64.         return false;  
  65.     }  

该函数中主要根据相应的逻辑回调了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键等,具体代码就不再贴了。


0 0
原创粉丝点击