KeyEvent事件的传递

KeyEvent事件的传递

最近解bug的时候遇到这么一个case,就是用户点击导航栏的返回键时,onBackPress并没有执行,再进一步调试发现onKeyDown也没有执行到。所以下面我会尽可能从代码层面分析KeyEvent事件的传递,而一些涉及WMS,IME的就不去深入分析了。

KeyEvent主要包括手机导航键Back、Home、Task,也有可能是连接了外置键盘的键盘键。

onBackPress和onKeyDown的关系

Activity.java

public boolean onKeyDown(int keyCode, KeyEvent event)  {
      if (keyCode == KeyEvent.KEYCODE_BACK) {
          if (getApplicationInfo().targetSdkVersion
                  >= Build.VERSION_CODES.ECLAIR) {
              event.startTracking();
          } else {
              onBackPressed();
          }
          return true;
      }
      ...
}

public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (getApplicationInfo().targetSdkVersion
                >= Build.VERSION_CODES.ECLAIR) {
            if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
                    && !event.isCanceled()) {
                onBackPressed();
                return true;
            }
        }
        return false;
    }

从代码中可以看到,

  1. Android E以下,back键只要按下就触发onBackPress
  2. Android E以上,当back按下后,会开始追溯tracking,当back按下一定事件会触发onLongPress,短时间弹起的话会触发onKeyUp。

KeyEvent.java

//KeyEvent 省略许多代码
public final boolean dispatch(Callback receiver, DispatcherState state,
          Object target) {
          switch (mAction) {
          case ACTION_DOWN: {
              boolean res = receiver.onKeyDown(mKeyCode, this);
              if (!res) {
                //一定条件下
              	onKeyLongPress;
              }
             ...
             case ACTION_UP: {
             	onKeyUp;
             }
			...
  1. 从KeyEvent的分发来看,onKeyDown和onKeyLongPress有可能都执行。
  2. 而onKeyUp和onKeyLongPress只会执行其中一个。

KeyEvent分发给onKeyDown

Activity最早可以处理KeyEvent的时机应该是dispatchEvent:
Activity.java

    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();
        final int keyCode = event.getKeyCode();
        if (keyCode == 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);
    }
  1. onUserInteraction()表示用户触发了点击事件,但该方法不能阻止事件往下传递。
  2. 传递流:menu处理-> window处理-> KeyEvent的dispatch处理,在dispatch里面就交给Activituy执行onKeyDown和onKeyUp。

从系统层面看

  1. WMS接收到KeyEvent,判断是否为系统事件,比如home,那么系统会自己处理,不会往下传递;
  2. WMS将KeyEvent交给ViewRootImpl,按照触摸事件的传递方式调用mView.dispatchKeyEventPreIme,如果上层ViewGroup没有处理,将交给下层的ViewGroup或View,然后执行View的onKeyPreIme;
  3. onKeyPreIme处理完之后,如果输入法窗口有显示,则交给输入法处理
  4. 最后交给Activity的diapatchKeyEvent处理了。

onKeyPreIme分析

IME全称InputMethodEditors,也就是输入法。

ViewGroup.java

@Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))
                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
            return super.dispatchKeyEventPreIme(event);
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
                //将KeyEvent传递给具有焦点的子View
            return mFocused.dispatchKeyEventPreIme(event);
        }
        return false;
    }

View.java

/**
     * Handle a key event before it is processed by any input method
     * associated with the view hierarchy.  This can be used to intercept
     * key events in special situations before the IME consumes them; a
     * typical example would be handling the BACK key to update the application's
     * UI instead of allowing the IME to see it and close itself.
     *
     * @param keyCode The value in event.getKeyCode().
     * @param event Description of the key event.
     * @return If you handled the event, return true. If you want to allow the
     *         event to be handled by the next receiver, return false.
     */
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        return false;
    }
  1. onKeyPreIme会在交给输入法之前执行,返回true则表示消费该事件,则输入法接收不到该事件,返回false则表示传递给输入法。
  2. View默认返回false。

AutoCompleteTextView实现了onKeyPreIme:

@Override
    public boolean onKeyPreIme(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
                && !mPopup.isDropDownAlwaysVisible()) {
            // special case for the back key, we do not even try to send it
            // to the drop down list but instead, consume it immediately
            if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
                KeyEvent.DispatcherState state = getKeyDispatcherState();
                if (state != null) {
                    state.startTracking(event, this);
                }
                return true;
            } else if (event.getAction() == KeyEvent.ACTION_UP) {
                KeyEvent.DispatcherState state = getKeyDispatcherState();
                if (state != null) {
                    state.handleUpEvent(event);
                }
                if (event.isTracking() && !event.isCanceled()) {
                    dismissDropDown();
                    return true;
                }
            }
        }
        return super.onKeyPreIme(keyCode, event);
    }

AutoCompleteTextView在输入法之前拦截KeyEvent主要是为了:当联想词浮窗展开(PopupShowing)的时候,用户按back键后收起联想词,同时return true不让输入法处理。

在这里插入图片描述

总结

  1. Activity层最早接收到KeyEvent的时机是dispatchKeyEvent,可以通过重写来达到不往下传递的作用;
  2. View的onPreIme是在交给输入法之前处理KeyEvent的,通过返回值决定是否交给输入法处理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值