刚接触Android开发的时候,对touch、key事件的处理总是一知半解,一会是Activity里的方法,一会是各种View

中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握。每次写这部分代码的时候都有些心虚,

因为我不是很清楚什么时候、以什么样的顺序被调用,大都是打下log看看,没问题就算ok了。但随着时间流逝,这种感觉一直

折磨着我。期间也在网上搜索了相关资料,但总感觉不是那么令人满意。自打开始研究Android源码起,这部分内容的分析早就

被列在我的TODO list上了。因为弄懂这部分处理逻辑对明明白白地写android程序实在是太重要了,所以今天我就带领大家看看

这部分的处理逻辑。touch事件的处理我将放在另一篇博客中介绍(相比KeyEvent,大体都一样,只是稍微复杂些)。

  为了突出本文的重点,我们直接从事件被派发到View层次结构的根节点DecorView开始分析,这里我们先来看看DecorView#

dispatchKeyEvent方法,代码如下:

  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. /// 1. 第一次down事件的时候,处理panel的快捷键
  8. if (isDown && (event.getRepeatCount() == 0)) {
  9. // First handle chording of panel key: if a panel key is held
  10. // but not released, try to execute a shortcut in it.
  11. if ((mPanelChordingKey > 0) && (mPanelChordingKey != keyCode)) {
  12. boolean handled = dispatchKeyShortcutEvent(event);
  13. if (handled) {
  14. return true;
  15. }
  16. }
  17.  
  18. // If a panel is open, perform a shortcut on it without the
  19. // chorded panel key
  20. if ((mPreparedPanel != null) && mPreparedPanel.isOpen) {
  21. if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {
  22. return true;
  23. }
  24. }
  25. }
  26.  
  27. /// 2. 这里是我们本文的重点,当window没destroy且其Callback非空的话,交给其Callback处理
  28. if (!isDestroyed()) { // Activity、Dialog都是Callback接口的实现
  29. final Callback cb = getCallback(); // mFeatureId < 0 表示是application的DecorView,比如Activity、Dialog
  30. final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) // 派发给callback的方法
  31. : super.dispatchKeyEvent(event); // 否则直接派发到ViewGroup#dispatchKeyEvent(View层次结构)
  32. if (handled) {
  33. return true; // 如果被上面的步骤处理了则直接返回true,不再往下传递
  34. }
  35. }
  36.  
  37. /// 3. 这是key事件的最后一步,如果到这一步还没处理掉,则派发到PhoneWindow对应的onKeyDown, onKeyUp方法
  38. return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
  39. : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
  40. }

  接下来我们按照这个派发顺序依次来看看相关方法的实现,这里先看看Activity(Callback)的dispatchKeyEvent实现:

  1. /**
  2. * Called to process key events. You can override this to intercept all
  3. * key events before they are dispatched to the window. Be sure to call
  4. * this implementation for key events that should be handled normally.
  5. *
  6. * @param event The key event.
  7. *
  8. * @return boolean Return true if this event was consumed.
  9. */
  10. @Override
  11. public boolean dispatchKeyEvent(KeyEvent event) {
  12. /// 2.1. 回调接口,实际开发中用处不大,你感兴趣可以参看其方法doc
  13. onUserInteraction();
  14. Window win = getWindow();
  15. /// 2.2. 从这里事件的处理交给了与之相关的window对象,实质是派发到了view层次结构
  16. if (win.superDispatchKeyEvent(event)) {
  17. return true; // 被view层次结构处理掉了则直接返回true
  18. }
  19. View decor = mDecor;
  20. if (decor == null) decor = win.getDecorView();
  21. /// 2.3. 到这里如果view层次结构没处理则交给KeyEvent本身的dispatch方法,Activity的各种回调方法会被触发
  22. return event.dispatch(this, decor != null
  23. ? decor.getKeyDispatcherState() : null, this);
  24. }

紧接着我们看看,Window#superDispatchKeyEvent方法,相关代码如下:

  1. <!-- Window.java -->
  2. /**
  3. * Used by custom windows, such as Dialog, to pass the key press event
  4. * further down the view hierarchy. Application developers should
  5. * not need to implement or call this.
  6. *
  7. */
  8. public abstract boolean superDispatchKeyEvent(KeyEvent event);
  9.  
  10. <!-- PhoneWindow.java -->
  11. @Override
  12. public boolean superDispatchKeyEvent(KeyEvent event) {
  13. return mDecor.superDispatchKeyEvent(event);
  14. }
  15.  
  16. <!-- DecorView.superDispatchKeyEvent -->
  17.  
  18.   public boolean superDispatchKeyEvent(KeyEvent event) {
  19. /// 2.2.1. 进入view层次结构了,即调用ViewGroup的对应实现了。。。
  20. if (super.dispatchKeyEvent(event)) {
  21. return true; // 如果被view层次结构处理了则直接返回true。
  22. }
  23.  
  24. // Not handled by the view hierarchy, does the action bar want it
  25. // to cancel out of something special?
  26. /// 2.2.2. ActionBar对BACK key的特殊处理
  27. if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
  28. final int action = event.getAction();
  29. // Back cancels action modes first.
  30. if (mActionMode != null) {
  31. if (action == KeyEvent.ACTION_UP) {
  32. mActionMode.finish();
  33. }
  34. return true;
  35. }
  36.  
  37. // Next collapse any expanded action views.
  38. if (mActionBar != null && mActionBar.hasExpandedActionView()) {
  39. if (action == KeyEvent.ACTION_UP) {
  40. mActionBar.collapseActionView();
  41. }
  42. return true;
  43. }
  44. }
    /// 2.2.3. 最后返回false表示没处理掉,会接着2.3.步骤处理
    return false;
  45. }

然后我们接着看看2.2.1.包括的小步骤,即ViewGroup#dispatchKeyEvent的实现,代码如下:

  1. @Override
  2. public boolean dispatchKeyEvent(KeyEvent event) {
  3. /// 2.2.1.1. KeyEvent一致性检测用的,可忽略。。。
  4. if (mInputEventConsistencyVerifier != null) {
  5. mInputEventConsistencyVerifier.onKeyEvent(event, 1);
  6. }
  7.  
  8. if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
  9. == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
  10. /// 2.2.1.2. 如果此ViewGroup是focused或者具体的大小被设置了(有边界),则交给它处理,即调用View的实现
  11. if (super.dispatchKeyEvent(event)) {// super即View的实现
  12. return true;
  13. }
  14. } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
  15. == PFLAG_HAS_BOUNDS) {
  16. /// 2.2.1.3. 否则,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理
  17. if (mFocused.dispatchKeyEvent(event)) { // 注意这里可能是个递归调用
  18. return true; // 我们可以看到并不是每个child都能响应key事件,前提必须是focused child才有机会响应
  19. }
  20. }
  21.  
  22. if (mInputEventConsistencyVerifier != null) {
  23. mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
  24. }
  25. /// 2.2.1.4. 最后都没被处理返回false,2.2.2.步骤会接着执行。。。
  26. return false;
  27. }

这里我们可以看出对KeyEvent来说在View层次结构中,如果ViewGroup条件满足则会优先处理事件而不是先派发给其孩子view,

这一点和touch事件有所不同。这里我们看看View的dispatchKeyEvent实现:

  1. /**
  2. * Dispatch a key event to the next view on the focus path. This path runs
  3. * from the top of the view tree down to the currently focused view. If this
  4. * view has focus, it will dispatch to itself. Otherwise it will dispatch
  5. * the next node down the focus path. This method also fires any key
  6. * listeners.
  7. *
  8. * @param event The key event to be dispatched.
  9. * @return True if the event was handled, false otherwise.
  10. */
  11. public boolean dispatchKeyEvent(KeyEvent event) {
  12. if (mInputEventConsistencyVerifier != null) {
  13. mInputEventConsistencyVerifier.onKeyEvent(event, 0);
  14. }
  15.  
  16. // Give any attached key listener a first crack at the event.
  17. //noinspection SimplifiableIfStatement
  18. ListenerInfo li = mListenerInfo;
  19. /// 2.2.1.2(3).1. 调用onKeyListener,如果它非空且view是ENABLED状态,监听器优先触发
  20. if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
  21. && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
  22. return true;
  23. }
  24.  
  25. /// 2.2.1.2(3).2. 调用KeyEvent.dispatch方法,并将view对象本身作为参数传递进去,view的各种callback方法在这里被触发
  26. if (event.dispatch(this, mAttachInfo != null
  27. ? mAttachInfo.mKeyDispatchState : null, this)) {
  28. return true;
  29. }
  30.  
  31. if (mInputEventConsistencyVerifier != null) {
  32. mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
  33. }
  34. /// 2.2.1.2(3).3. 还没处理掉返回false,接着2.2.1.4.执行
  35. return false;
  36. }

这里关于View和ViewGroup的dispatchKeyEvent方法我们多说几句。这2个方法一起就实现了view层次结构按照focus路线从上到下

派发KeyEvent的整个流程,后面我会专门用一篇文章来详解下ViewGroup里mFocused变量和View里focus变化的处理过程。

  言归正传,不管是这里的2.2.1.2(3).2.步骤还是前面Activity里的2.3.步骤,都调到了KeyEvent.dispatch方法,不过在看其代码之前我们

先来看看这里用到的mAttachInfo.mKeyDispatchState对象是咋来的,代码如下:

  1. // 这句代码位于View.AttachInfo类里
  2. final KeyEvent.DispatcherState mKeyDispatchState
  3. = new KeyEvent.DispatcherState();
  4.  
  5. /**
  6. * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}
  7. * for this view's window. Returns null if the view is not currently attached
  8. * to the window. Normally you will not need to use this directly, but
  9. * just use the standard high-level event callbacks like
  10. * {@link #onKeyDown(int, KeyEvent)}.
  11. */
  12. public KeyEvent.DispatcherState getKeyDispatcherState() {
  13. return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;
  14. }
  15.  
  16. // KeyEvent.DispatcherState类
  17. /**
  18. * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}
  19. * for more advanced key dispatching, such as long presses.
  20. */
  21. public static class DispatcherState {
  22. int mDownKeyCode;
  23. Object mDownTarget;
  24. SparseIntArray mActiveLongPresses = new SparseIntArray();
  25.  
  26. /**
  27. * Reset back to initial state.
  28. */
  29. public void reset() { // 清空内部状态
  30. if (DEBUG) Log.v(TAG, "Reset: " + this);
  31. mDownKeyCode = 0;
  32. mDownTarget = null;
  33. mActiveLongPresses.clear();
  34. }
  35.  
  36. /**
  37. * Stop any tracking associated with this target.
  38. */
  39. public void reset(Object target) { // 清空target对应的内部状态
  40. if (mDownTarget == target) { // 只有相同时才清空,否则啥也不做
  41. if (DEBUG) Log.v(TAG, "Reset in " + target + ": " + this);
  42. mDownKeyCode = 0;
  43. mDownTarget = null;
  44. }
  45. }
  46.  
  47. /**
  48. * Start tracking the key code associated with the given event. This
  49. * can only be called on a key down. It will allow you to see any
  50. * long press associated with the key, and will result in
  51. * {@link KeyEvent#isTracking} return true on the long press and up
  52. * events.
  53. *
  54. * <p>This is only needed if you are directly dispatching events, rather
  55. * than handling them in {@link Callback#onKeyDown}.
  56. */
  57. public void startTracking(KeyEvent event, Object target) {
  58. if (event.getAction() != ACTION_DOWN) { // 状态检测
  59. throw new IllegalArgumentException(
  60. "Can only start tracking on a down event");
  61. }
  62. if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);
  63. mDownKeyCode = event.getKeyCode(); // 赋值,表示正在track某个keycode
  64. mDownTarget = target;
  65. }
  66.  
  67. /**
  68. * Return true if the key event is for a key code that is currently
  69. * being tracked by the dispatcher.
  70. */
  71. public boolean isTracking(KeyEvent event) {
  72. return mDownKeyCode == event.getKeyCode();
  73. }
  74.  
  75. /**
  76. * Keep track of the given event's key code as having performed an
  77. * action with a long press, so no action should occur on the up.
  78. * <p>This is only needed if you are directly dispatching events, rather
  79. * than handling them in {@link Callback#onKeyLongPress}.
  80. */
  81. public void performedLongPress(KeyEvent event) {// 用来记录发生了生理长按事件
  82. mActiveLongPresses.put(event.getKeyCode(), 1);
  83. }
  84.  
  85. /**
  86. * Handle key up event to stop tracking. This resets the dispatcher state,
  87. * and updates the key event state based on it.
  88. * <p>This is only needed if you are directly dispatching events, rather
  89. * than handling them in {@link Callback#onKeyUp}.
  90. */
  91. public void handleUpEvent(KeyEvent event) {
  92. final int keyCode = event.getKeyCode();
  93. if (DEBUG) Log.v(TAG, "Handle key up " + event + ": " + this);
  94. int index = mActiveLongPresses.indexOfKey(keyCode);
  95. if (index >= 0) { // 如果发生过生理长按则设置event.mFlags为CACELED,这样在接下来的receiver.onKeyUp中有些处理就不会发生了
  96. if (DEBUG) Log.v(TAG, " Index: " + index); // 因为事件被标记为CANCELED了
  97. event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;
  98. mActiveLongPresses.removeAt(index);
  99. }
  100. if (mDownKeyCode == keyCode) {
  101. if (DEBUG) Log.v(TAG, " Tracking!");
  102. event.mFlags |= FLAG_TRACKING; // 设置event正确的mFlags,接下来的receiver.onKeyUp可能会检测此状态
  103. mDownKeyCode = 0; // reset,表示此keycode的tracking到此结束了
  104. mDownTarget = null;
  105. }
  106. }
  107. }

大概了解了KeyEvent.DispatcherState类,我们就可以来看看KeyEvent.dispatch方法了,代码如下:

  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: { // DOWN事件
  16. mFlags &= ~FLAG_START_TRACKING; //先清掉START_TRACKING标记
  17. if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state
  18. + ": " + this);
  19. boolean res = receiver.onKeyDown(mKeyCode, this); // 回调Callback接口的onKeyDown方法,View和Activity都是此接口的实现者
  20. if (state != null) { // 一般都成立
  21. if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {
  22. if (DEBUG) Log.v(TAG, " Start tracking!"); // receiver.onKeyDown返回true了且不是repeated
  23. state.startTracking(this, target); // 并且也没有开始tracking,则开始tracking当前的KeyEvent和target
  24. } else if (isLongPress() && state.isTracking(this)) { // 处理生理长按
  25. try { // 检测到生理长按则调用receiver.onKeyLongPress方法
  26. if (receiver.onKeyLongPress(mKeyCode, this)) {
  27. if (DEBUG) Log.v(TAG, " Clear from long press!");
  28. state.performedLongPress(this); // 记录此event已经有生理long press发生了。。。
  29. res = true; // 设置为处理了
  30. }
  31. } catch (AbstractMethodError e) {
  32. }
  33. }
  34. }
  35. return res; // 返回down事件处理的结果
  36. }
  37. case ACTION_UP: // UP事件
  38. if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state
  39. + ": " + this);
  40. if (state != null) {
  41. state.handleUpEvent(this); // reset state的内部状态,也改变了KeyEvent的某些状态
  42. }
  43. return receiver.onKeyUp(mKeyCode, this); // 最后调用receiver.onKeyUp方法
  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. }

  看完了KeyEvent的具体实现,我们接着看看receiver(Callback接口)的onKeyDown、onKeyUp实现,先来看View相关的,代码如下:

  1. /**
  2. * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)
  3. * KeyEvent.Callback.onKeyDown()}: perform press of the view
  4. * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or {@link KeyEvent#KEYCODE_ENTER}
  5. * is released, if the view is enabled and clickable.
  6. *
  7. * <p>Key presses in software keyboards will generally NOT trigger this listener,
  8. * although some may elect to do so in some situations. Do not rely on this to
  9. * catch software key presses.
  10. *
  11. * @param keyCode A key code that represents the button pressed, from
  12. * {@link android.view.KeyEvent}.
  13. * @param event The KeyEvent object that defines the button action.
  14. */
  15. public boolean onKeyDown(int keyCode, KeyEvent event) {
  16. boolean result = false;
  17.  
  18. if (KeyEvent.isConfirmKey(keyCode)) { // 只处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER这2个按键
  19. if ((mViewFlags & ENABLED_MASK) == DISABLED) {
  20. return true; // 针对disabled View直接返回true表示处理过了
  21. }
  22. // Long clickable items don't necessarily have to be clickable
  23. if (((mViewFlags & CLICKABLE) == CLICKABLE ||
  24. (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
  25. (event.getRepeatCount() == 0)) { // clickable或者long_clickable且是第一次down事件
  26. setPressed(true); // 标记pressed,你可能设置了View不同的background,这时候就会有所体现(比如高亮效果)
  27. checkForLongClick(0); // 启动View的long click检测
  28. return true; // 到达这一步就表示KeyEvent被处理掉了
  29. }
  30. }
  31. return result;
  32. }
  33.  
  34. /**
  35. * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)
  36. * KeyEvent.Callback.onKeyUp()}: perform clicking of the view
  37. * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
  38. * {@link KeyEvent#KEYCODE_ENTER} is released.
  39. * <p>Key presses in software keyboards will generally NOT trigger this listener,
  40. * although some may elect to do so in some situations. Do not rely on this to
  41. * catch software key presses.
  42. *
  43. * @param keyCode A key code that represents the button pressed, from
  44. * {@link android.view.KeyEvent}.
  45. * @param event The KeyEvent object that defines the button action.
  46. */
  47. public boolean onKeyUp(int keyCode, KeyEvent event) {
  48. if (KeyEvent.isConfirmKey(keyCode)) { // 同onKeyDown,默认也只处理confirm key
  49. if ((mViewFlags & ENABLED_MASK) == DISABLED) {
  50. return true; // 同样的逻辑,如果是DISABLED view,直接返回true表示处理过了
  51. }
  52. if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
  53. setPressed(false); // 重置pressed状态
  54.  
  55. if (!mHasPerformedLongPress) { // 长按没发生的话,
  56. // This is a tap, so remove the longpress check
  57. removeLongPressCallback(); // 当up事件发生的时候,移除这些已经没用的callback
  58. return performClick(); // 调用单击onClick监听器
  59. }
  60. }
  61. }
  62. return false; // 其他所有的Key默认不处理
  63. }
  64.  
  65. /**
  66. * Sets the pressed state for this view.
  67. *
  68. * @see #isClickable()
  69. * @see #setClickable(boolean)
  70. *
  71. * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts
  72. * the View's internal state from a previously set "pressed" state.
  73. */
  74. public void setPressed(boolean pressed) {
  75. final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);
  76.  
  77. if (pressed) {
  78. mPrivateFlags |= PFLAG_PRESSED;
  79. } else {
  80. mPrivateFlags &= ~PFLAG_PRESSED;
  81. }
  82.  
  83. if (needsRefresh) {
  84. refreshDrawableState(); // 这行代码会刷新View的显示状态
  85. }
  86. dispatchSetPressed(pressed);
  87. }
  88.  
  89. private void checkForLongClick(int delayOffset) {
  90. if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { // 必须得是LONG_CLICKABLE的View
  91. mHasPerformedLongPress = false; // 设置初始值
  92.  
  93. if (mPendingCheckForLongPress == null) { // 只非空的时候才new一个
  94. mPendingCheckForLongPress = new CheckForLongPress();
  95. }
  96. mPendingCheckForLongPress.rememberWindowAttachCount();
  97. postDelayed(mPendingCheckForLongPress, // post一个Runnable,注意延迟是个差值,而不是delayOffset
  98. ViewConfiguration.getLongPressTimeout() - delayOffset);
  99. }
  100. }
  101.  
  102. class CheckForLongPress implements Runnable {
  103.  
  104. private int mOriginalWindowAttachCount;
  105.  
  106. public void run() {
  107. if (isPressed() && (mParent != null) // 当时间到了,此Runnable没被移除掉的话,并且这些条件都满足的时候,
  108. && mOriginalWindowAttachCount == mWindowAttachCount) {
  109. if (performLongClick()) { // 客户端定义的onLongClickListener监听器被触发
  110. mHasPerformedLongPress = true; // 只有当被上面的方法处理掉了,才表示LongPress发生过了
  111. }
  112. }
  113. }
  114.  
  115. public void rememberWindowAttachCount() {
  116. mOriginalWindowAttachCount = mWindowAttachCount;
  117. }
  118. }
  119.  
  120. /**
  121. * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
  122. * OnLongClickListener did not consume the event.
  123. *
  124. * @return True if one of the above receivers consumed the event, false otherwise.
  125. */
  126. public boolean performLongClick() {
  127. sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
  128.  
  129. boolean handled = false;
  130. ListenerInfo li = mListenerInfo;
  131. if (li != null && li.mOnLongClickListener != null) { // 优先触发监听器
  132. handled = li.mOnLongClickListener.onLongClick(View.this);
  133. }
  134. if (!handled) { // 如果还没处理,显示ContextMenu如果定义了的话
  135. handled = showContextMenu();
  136. }
  137. if (handled) {
  138. performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
  139. }
  140. return handled; // 返回处理结果
  141. }

  接下来,看看Activity对应的onKeyDown,onKeyUp方法:

  1. /**
  2. * Called when a key was pressed down and not handled by any of the views
  3. * inside of the activity. So, for example, key presses while the cursor
  4. * is inside a TextView will not trigger the event (unless it is a navigation
  5. * to another object) because TextView handles its own key presses.
  6. *
  7. * <p>If the focused view didn't want this event, this method is called.
  8. *
  9. * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
  10. * by calling {@link #onBackPressed()}, though the behavior varies based
  11. * on the application compatibility mode: for
  12. * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,
  13. * it will set up the dispatch to call {@link #onKeyUp} where the action
  14. * will be performed; for earlier applications, it will perform the
  15. * action immediately in on-down, as those versions of the platform
  16. * behaved.
  17. *
  18. * <p>Other additional default key handling may be performed
  19. * if configured with {@link #setDefaultKeyMode}.
  20. *
  21. * @return Return <code>true</code> to prevent this event from being propagated
  22. * further, or <code>false</code> to indicate that you have not handled
  23. * this event and it should continue to be propagated.
  24. * @see #onKeyUp
  25. * @see android.view.KeyEvent
  26. */
  27. public boolean onKeyDown(int keyCode, KeyEvent event) {
  28. if (keyCode == KeyEvent.KEYCODE_BACK) {
  29. if (getApplicationInfo().targetSdkVersion
  30. >= Build.VERSION_CODES.ECLAIR) { // >= Android 2.0之后
  31. event.startTracking(); // 标记追踪这个key event
  32. } else {
  33. onBackPressed(); // 2.0之前直接调用onBackPressed
  34. }
  35. return true; // 返回true表示被activity处理掉了
  36. }
  37.  
  38. if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
  39. return false;
  40. } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
  41. if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,
  42. keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
  43. return true;
  44. }
  45. return false;
  46. } else {
  47. // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
  48. boolean clearSpannable = false;
  49. boolean handled;
  50. if ((event.getRepeatCount() != 0) || event.isSystem()) {
  51. clearSpannable = true;
  52. handled = false;
  53. } else {
  54. handled = TextKeyListener.getInstance().onKeyDown(
  55. null, mDefaultKeySsb, keyCode, event);
  56. if (handled && mDefaultKeySsb.length() > 0) {
  57. // something useable has been typed - dispatch it now.
  58.  
  59. final String str = mDefaultKeySsb.toString();
  60. clearSpannable = true;
  61.  
  62. switch (mDefaultKeyMode) {
  63. case DEFAULT_KEYS_DIALER:
  64. Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + str));
  65. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  66. startActivity(intent);
  67. break;
  68. case DEFAULT_KEYS_SEARCH_LOCAL:
  69. startSearch(str, false, null, false);
  70. break;
  71. case DEFAULT_KEYS_SEARCH_GLOBAL:
  72. startSearch(str, false, null, true);
  73. break;
  74. }
  75. }
  76. }
  77. if (clearSpannable) {
  78. mDefaultKeySsb.clear();
  79. mDefaultKeySsb.clearSpans();
  80. Selection.setSelection(mDefaultKeySsb,0);
  81. }
  82. return handled;
  83. }
  84. }
  85.  
  86. /**
  87. * Called when a key was released and not handled by any of the views
  88. * inside of the activity. So, for example, key presses while the cursor
  89. * is inside a TextView will not trigger the event (unless it is a navigation
  90. * to another object) because TextView handles its own key presses.
  91. *
  92. * <p>The default implementation handles KEYCODE_BACK to stop the activity
  93. * and go back.
  94. *
  95. * @return Return <code>true</code> to prevent this event from being propagated
  96. * further, or <code>false</code> to indicate that you have not handled
  97. * this event and it should continue to be propagated.
  98. * @see #onKeyDown
  99. * @see KeyEvent
  100. */
  101. public boolean onKeyUp(int keyCode, KeyEvent event) {
  102. if (getApplicationInfo().targetSdkVersion
  103. >= Build.VERSION_CODES.ECLAIR) { // 同onKeyDown,2.0之后的版本
  104. if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
  105. && !event.isCanceled()) { // 是back key,且正在追踪&event没被取消掉(从前面的分析我们知道当发生生理长按时会被标记为Canceled)
  106. onBackPressed(); // 在这种情况下执行onBackPressed表示处理掉了
  107. return true;
  108. }
  109. }
  110. return false;
  111. }

  最后是3.步骤,回到一开始DecorView.dispatchKeyEvent的最后几行代码,我们来看看PhoneWindow对应的onKeyDown,onKeyUp方法:

  1. /**
  2. * A key was pressed down and not handled by anything else in the window.
  3. *
  4. * @see #onKeyUp
  5. * @see android.view.KeyEvent
  6. */
  7. protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {
  8. /* ****************************************************************************
  9. * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.
  10. *
  11. * If your key handling must happen before the app gets a crack at the event,
  12. * it goes in PhoneWindowManager.
  13. *
  14. * If your key handling should happen in all windows, and does not depend on
  15. * the state of the current application, other than that the current
  16. * application can override the behavior by handling the event itself, it
  17. * should go in PhoneFallbackEventHandler.
  18. *
  19. * Only if your handling depends on the window, and the fact that it has
  20. * a DecorView, should it go here.
  21. * ****************************************************************************/
  22.  
  23. final KeyEvent.DispatcherState dispatcher =
  24. mDecor != null ? mDecor.getKeyDispatcherState() : null;
  25. //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()
  26. // + " flags=0x" + Integer.toHexString(event.getFlags()));
  27.  
  28. switch (keyCode) {
  29. case KeyEvent.KEYCODE_VOLUME_UP: // key event处理中的最后一步,
  30. case KeyEvent.KEYCODE_VOLUME_DOWN:
  31. case KeyEvent.KEYCODE_VOLUME_MUTE: { // 处理音量调节键
  32. // Similar code is in PhoneFallbackEventHandler in case the window
  33. // doesn't have one of these. In this case, we execute it here and
  34. // eat the event instead, because we have mVolumeControlStreamType
  35. // and they don't.
  36. getAudioManager().handleKeyDown(event, mVolumeControlStreamType);
  37. return true;
  38. }
  39.  
  40. case KeyEvent.KEYCODE_MENU: {
  41. onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);
  42. return true;
  43. }
  44.  
  45. case KeyEvent.KEYCODE_BACK: {
  46. if (event.getRepeatCount() > 0) break;
  47. if (featureId < 0) break;
  48. // Currently don't do anything with long press.
  49. if (dispatcher != null) {
  50. dispatcher.startTracking(event, this);
  51. }
  52. return true;
  53. }
  54.  
  55. }
  56.  
  57. return false;
  58. }
  59.  
  60. /**
  61. * A key was released and not handled by anything else in the window.
  62. *
  63. * @see #onKeyDown
  64. * @see android.view.KeyEvent
  65. */
  66. protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {
  67. final KeyEvent.DispatcherState dispatcher =
  68. mDecor != null ? mDecor.getKeyDispatcherState() : null;
  69. if (dispatcher != null) {
  70. dispatcher.handleUpEvent(event);
  71. }
  72. //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()
  73. // + " flags=0x" + Integer.toHexString(event.getFlags()));
  74.  
  75. switch (keyCode) {
  76. case KeyEvent.KEYCODE_VOLUME_UP:
  77. case KeyEvent.KEYCODE_VOLUME_DOWN:
  78. case KeyEvent.KEYCODE_VOLUME_MUTE: {
  79. // Similar code is in PhoneFallbackEventHandler in case the window
  80. // doesn't have one of these. In this case, we execute it here and
  81. // eat the event instead, because we have mVolumeControlStreamType
  82. // and they don't.
  83. getAudioManager().handleKeyUp(event, mVolumeControlStreamType);
  84. return true;
  85. }
  86.  
  87. case KeyEvent.KEYCODE_MENU: {
  88. onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,
  89. event);
  90. return true;
  91. }
  92.  
  93. case KeyEvent.KEYCODE_BACK: {
  94. if (featureId < 0) break;
  95. if (event.isTracking() && !event.isCanceled()) {
  96. if (featureId == FEATURE_OPTIONS_PANEL) {
  97. PanelFeatureState st = getPanelState(featureId, false);
  98. if (st != null && st.isInExpandedMode) {
  99. // If the user is in an expanded menu and hits back, it
  100. // should go back to the icon menu
  101. reopenMenu(true);
  102. return true;
  103. }
  104. }
  105. closePanel(featureId);
  106. return true;
  107. }
  108. break;
  109. }
  110.  
  111. case KeyEvent.KEYCODE_SEARCH: {
  112. /*
  113. * Do this in onKeyUp since the Search key is also used for
  114. * chording quick launch shortcuts.
  115. */
  116. if (getKeyguardManager().inKeyguardRestrictedInputMode()) {
  117. break;
  118. }
  119. if (event.isTracking() && !event.isCanceled()) {
  120. launchDefaultSearch();
  121. }
  122. return true;
  123. }
  124. }
  125.  
  126. return false;
  127. }

  至此所有按键事件的处理就分析完毕了,鉴于篇幅略长,我们最后稍微总结下。主要有这么几点:

1. View的各种KeyEvent.Callback接口早于Activity的对应接口被调用;

2. 整个处理环节中只要有一处表明处理掉了,则处理结束,不在往下传递;

3. 各种Callback接口的处理优先级低于监听器,也就是说各种onXXXListener的方法优先被调用。

Android按键事件处理流程 -- KeyEvent的更多相关文章

  1. Android touch事件处理流程

    前面我们看了key事件的处理流程,相信大家对此已经有了新的认识,这篇文章我打算带领大家来看看稍微复杂些的touch 事件的处理流程.说它复杂是因为key事件本身就key down,up,long pr ...

  2. Android热插拔事件处理详解

    一.Android热插拔事件处理流程图 Android热插拔事件处理流程如下图所示: 二.组成 1. NetlinkManager:        全称是NetlinkManager.cpp位于And ...

  3. Android按键事件传递流程(二)

    5    应用层如何从Framework层接收按键事件 由3.2和4.5.4节可知,当InputDispatcher通过服务端管道向socket文件描述符发送消息后,epoll机制监听到了I/O事件, ...

  4. Android 7.0 Power 按键处理流程

    Android 7.0  Power 按键处理流程 Power按键的处理逻辑由PhoneWindowManager来完成,本文只关注PhoneWindowManager中与Power键相关的内容,其他 ...

  5. Android系统input按键处理流程(从驱动到framework)【转】

    本文转载自:http://blog.csdn.net/jwq2011/article/details/51234811 (暂时列出提纲,后续添加具体内容) 涉及到的几个文件: 1.out/target ...

  6. Android下添加新的自定义键值和按键处理流程【转】

    本文转载自: Android下添加新的自定义键值和按键处理流程     说出来不怕大家笑话,我写这篇博客的原因在于前几天去一个小公司面试Android系统工程师,然后在面试的时候对方的技术总监问了我一 ...

  7. Android下添加新的自定义键值和按键处理流程

            Android下添加新的自定义键值和按键处理流程     说出来不怕大家笑话,我写这篇博客的原因在于前几天去一个小公司面试Android系统工程师,然后在面试的时候对方的技术总监问了我 ...

  8. 【Android】事件处理系统

    linux输入子系统 Android是linux内核的,所以它的事件处理系统也在linux的基础上完成的. Linux内核提供了一个Input子系统来实现的,Input子系统会在/dev/input/ ...

  9. Android的事件处理

    1 android事件处理概述 不论是桌面应用还是手机应用程序,面对最多的就是用户,经常需要处理用户的动作-------也就是需要为用户动作提供响应,这种为用户动作提供响应的机制就是事件处理.andr ...

随机推荐

  1. JSP中<img>标签引用本地图片

    问题描述: jsp页面中<img>标签如何读取本地文件夹中的图片. 问题起因: 由于上传图片至本地文件夹中,图片路径为: D:/upload/file/image/img.jpg 所以将这 ...

  2. 网络基础:NetBIOS

    网络基础小补. 利用 NetBIOS 名称与其他计算机通信 网络中的计算机之间必须知道IP地址后才能相互通信.但对人来说IP难以记忆,NetBIOS计算机名称比较容易记忆.当计算机使用 NetBIOS ...

  3. C语言学习020:可变参数函数

    顾名思义,可变参数函数就是参数数量可变的函数,即函数的参数数量是不确定的,比如方法getnumbertotal()我们即可以传递一个参数,也可以传递5个.6个参数 #include <stdio ...

  4. JavaScript星形评分

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  5. easyui的window插件再次封装

    easyui的window插件再次封装 说明:该插件弹出的弹出框在最顶层的页面中,而不是在当前页面所在的iframe中,它的可拖动范围是整个浏览器窗口:所以不能用JS的parent对象获取弹出它的父页 ...

  6. C# 文件 文件夹

    //判断文件夹(路径)是否存在 if (Directory.Exists(Path)) { } //获取文件大小 FileInfo file = new FileInfo(labOfPath); si ...

  7. iOS阶段学习第18天笔记(Plist-Archiver-归档与解归档操作)

    iOS学习(OC语言)知识点整理 一.归档与解归档的操作 1)归档是一个过程,将一个或多个对象存储起来,以便以后可以还原,包括将对象存入文件,以后再读取 将数据对象归档成plist文件 2)plist ...

  8. Oracle-- (RANK) 排名函数

    内容来自: Oracle® Database SQL Language Reference 11g Release 2 (11.2) E41084-03. empolyees表来自hr方案. RANK ...

  9. c#重点[封装,继承,多肽]

    面向对象的语言三大特点:封装.继承.多态 Ⅰ.封装:是把类的内部隐藏起来,以防止外部世界看见的一个面向对象的概念,通过关键字去控制变量,方法的访问权限. 1).访问修饰符: Ⅱ.继承: eg:我们建一 ...

  10. MySQL知识总结

    一.基础 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份sql server --- ...