Android按键事件传递流程(三)

725 阅读2分钟

「这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战

Android按键事件传递流程(三)

应用层按键事件的传递

事件传递流程

这里主要讲解两部分,mView的创建事件传递流程

上节讲解了mView是如何创建的,下面继续讲解他是如何传递的。

DecorView的dispatchKeyEvent

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 ((mWindow.mPanelChordingKey > 0) && (mWindow.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 ((mWindow.mPreparedPanel != null) && mWindow.mPreparedPanel.isOpen) {
            if (mWindow.performPanelShortcut(mWindow.mPreparedPanel, keyCode, event, 0)) {
                return true;
            }
        }
    }
​
    if (!mWindow.isDestroyed()) {
        final Window.Callback cb = mWindow.getCallback();
        final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
            : super.dispatchKeyEvent(event);
        if (handled) {
            return true;
        }
    }
​
    return isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event)
        : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
}

前面的两个判断是对快捷按键的处理。

//...
if (!mWindow.isDestroyed()) {
        //cb代表一个Activity
        final Window.Callback cb = mWindow.getCallback();
        final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
            : super.dispatchKeyEvent(event);
        if (handled) {
            return true;
        }
    }
//...

mFeatureId:代表程序的特征表示或整个屏幕的标识,如果是应用程序则为-1,具体的流程如下:

Activity的oncreate方法->setContentView->PhoneWindow的setContentView-> installDecor()->generateDecor-> new DecorView(getContext(), -1)

如果Activity对象不为空,mFeatureId为-1,调用Activity对象的dispatchKeyEvent方法,如果为空,就调用super.dispatchKeyEvent(event)即父类ViewGroup的dispatchKeyEvent

如果返回值为true,表明事件已经被消耗了,按键事件不会在传递,否则会继续执行

return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);

将传递到PhoneWindow的onKeyDown、onKeyUp方法。

Activity的dispatchKeyEvent

Activity中的这个方法起到了拦截按键的作用,如果在这一步没有处理,则事件将继续向下分发给view或viewGroup去处理。

public boolean dispatchKeyEvent(KeyEvent event) {
    //如果有按键、触摸轨迹球等事件分发给Activity时,会调用
    onUserInteraction();
​
    // Let action bars open menus in response to the menu key prioritized over
    // the window handling it
    final int keyCode = event.getKeyCode();
    //如果Menu键且状态栏消耗了该键,则直接返回true
    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);
}
//...
Window win = getWindow();
if (win.superDispatchKeyEvent(event)) {
    return true;
}
//...

通过getWindow返回PhoneWindow对象,将事件传递给PhoneWindow对象,主要是对Back按键松开的一些处理。

如果没有被消耗,则连同其他时间一起传递到父类的ViewGroup,父类没有处理则会传递到KeyEvent的dispatchKeyEvent。