文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Key事件的分发逻辑,Android按键事件的产生与处理解析

2024-11-29 20:58

关注
private int processKeyEvent(QueuedInputEvent q) {
    final KeyEvent event = (KeyEvent) q.mEvent;
    if (mUnhandledKeyManager.preViewDispatch(event)) {
        return FINISH_HANDLED;
    }

    // Deliver the key to the view hierarchy.
    if (mView.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }

    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    // This dispatch is for windows that don't have a Window.Callback. Otherwise,
    // the Window.Callback usually will have already called this (see
    // DecorView.superDispatchKeyEvent) leaving this call a no-op.
    if (mUnhandledKeyManager.dispatch(mView, event)) {
        return FINISH_HANDLED;
    }

    int groupNavigationDirection = 0;

    if (event.getAction() == KeyEvent.ACTION_DOWN
            && event.getKeyCode() == KeyEvent.KEYCODE_TAB) {
        if (KeyEvent.metaStateHasModifiers(event.getMetaState(), KeyEvent.META_META_ON)) {
            groupNavigationDirection = View.FOCUS_FORWARD;
        } else if (KeyEvent.metaStateHasModifiers(event.getMetaState(),KeyEvent.META_META_ON | KeyEvent.META_SHIFT_ON)) {
            groupNavigationDirection = View.FOCUS_BACKWARD;
        }
    }

    // If a modifier is held, try to interpret the key as a shortcut.
    if (event.getAction() == KeyEvent.ACTION_DOWN && !KeyEvent.metaStateHasNoModifiers(event.getMetaState()) && event.getRepeatCount() == 0
                    && !KeyEvent.isModifierKey(event.getKeyCode())
                    && groupNavigationDirection == 0) {
        if (mView.dispatchKeyShortcutEvent(event)) {
            return FINISH_HANDLED;
        }
        if (shouldDropInputEvent(q)) {
            return FINISH_NOT_HANDLED;
        }
    }

    // Apply the fallback event policy.
    if (mFallbackEventHandler.dispatchKeyEvent(event)) {
        return FINISH_HANDLED;
    }
    if (shouldDropInputEvent(q)) {
        return FINISH_NOT_HANDLED;
    }

    // Handle automatic focus changes.
    if (event.getAction() == KeyEvent.ACTION_DOWN) {
        if (groupNavigationDirection != 0) {
            if (performKeyboardGroupNavigation(groupNavigationDirection)) {
                return FINISH_HANDLED;
            }
        } else {
            if (performFocusNavigation(event)) {
                return FINISH_HANDLED;
            }
        }
    }
    return FORWARD;
}
private boolean performFocusNavigation(KeyEvent event) {
    int direction = 0;
    switch (event.getKeyCode()) {
        case KeyEvent.KEYCODE_DPAD_LEFT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_LEFT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_RIGHT:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_RIGHT;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_UP:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_UP;
            }
            break;
        case KeyEvent.KEYCODE_DPAD_DOWN:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_DOWN;
            }
            break;
        case KeyEvent.KEYCODE_TAB:
            if (event.hasNoModifiers()) {
                direction = View.FOCUS_FORWARD;
            } else if (event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
                direction = View.FOCUS_BACKWARD;
            }
            break;
    }
    if (direction != 0) {
        View focused = mView.findFocus();
        if (focused != null) {
            View v = focused.focusSearch(direction);
            if (v != null && v != focused) {
                // do the math the get the interesting rect
                // of previous focused into the coord system of
                // newly focused view
                focused.getFocusedRect(mTempRect);
                if (mView instanceof ViewGroup) {
                    ((ViewGroup) mView).offsetDescendantRectToMyCoords(focused, mTempRect);
                    ((ViewGroup) mView).offsetRectIntoDescendantCoords(v, mTempRect);
                }
                if (v.requestFocus(direction, mTempRect)) {
                    Log.i(TAG, "v.requestFocus == true");
                    boolean isFastScrolling = event.getRepeatCount() > 0;
                    playSoundEffect(SoundEffectConstants.getConstantForFocusDirection(direction,isFastScrolling));
                    return true;
                }
            }

            // Give the focused view a last chance to handle the dpad key.
            if (mView.dispatchUnhandledMove(focused, direction)) {
                return true;
            }
        } else {
            if (mView.restoreDefaultFocus()) {
                return true;
            }
        }
    }
    return false;
}

该方法用于处理焦点导航相关的按键事件,例如方向键和Tab键等。该方法接收一个KeyEvent对象作为参数,根据不同的按键码和修饰符,计算出焦点导航的方向,然后尝试在View树中找到新的焦点,并将其设置为当前焦点。如果找到新的焦点并成功设置为当前焦点,则播放焦点变化时的声音效果,并返回true表示焦点变化事件已被处理。如果没有找到新的焦点,或者新的焦点不接受焦点设置请求,则返回false表示该事件未被处理。

/frameworks/base/core/java/com/android/internal/policy/DecorView.java

@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 ((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;
        }
    }
    boolean result = isDown ? mWindow.onKeyDown(mFeatureId, event.getKeyCode(), event) : mWindow.onKeyUp(mFeatureId, event.getKeyCode(), event);
    return result;
}

该方法用于处理按键事件的分发和处理,该方法接收一个KeyEvent对象,并提取出键码和事件类型。如果该事件为按下事件并且重复次数为0,则执行以下操作:

public boolean superDispatchKeyEvent(KeyEvent event) {
    // Give priority to closing action modes if applicable.
    if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
        final int action = event.getAction();
        // Back cancels action modes first.
        if (mPrimaryActionMode != null) {
            if (action == KeyEvent.ACTION_UP) {
                mPrimaryActionMode.finish();
            }
            return true;
        }
    }

    if (super.dispatchKeyEvent(event)) {
        return true;
    }
    boolean handle = (getViewRootImpl() != null) && getViewRootImpl().dispatchUnhandledKeyEvent(event);
    return handle;
}

其作用是对KeyEvent事件进行分发处理。该方法首先判断事件是否为返回键,如果是则优先处理当前的操作模式,如果存在操作模式则先结束操作模式,否则将事件交由父类ViewGroup进行处理。如果父类能够处理该事件,则返回true,否则返回false,并将事件交由该DecorView 所对应的ViewRootImpl实例进行处理。

其中,getViewRootImpl()方法返回当前DecorView所在的ViewRootImpl实例。如果该实例存在,则调用dispatchUnhandledKeyEvent(event)方法进行处理,否则返回false。dispatchUnhandledKeyEvent(event)方法用于将该事件交由输入法进行处理。

  1. 「View的事件处理」:

/frameworks/base/core/java/android/app/Activity.java

public boolean dispatchKeyEvent(KeyEvent event) {
    onUserInteraction();

    // Let action bars open menus in response to the menu key prioritized over
    // the window handling it
    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();
    boolean handler = event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
    return handler;
}

该方法用于分发键事件,当用户按下或释放某个按键时,该方法将被调用。

/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

public boolean superDispatchKeyEvent(KeyEvent event) {
    return mDecor.superDispatchKeyEvent(event);
}

/frameworks/base/core/java/android/view/ViewGroup.java

@Override
public boolean dispatchKeyEvent(KeyEvent event) {
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onKeyEvent(event, 1);
    }

    if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {
        if (super.dispatchKeyEvent(event)) {
            Log.e("ViewRootImpl","super.dispatchKeyEvent(event)");
            return true;
        }
    } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS) == PFLAG_HAS_BOUNDS) {
        if (mFocused.dispatchKeyEvent(event)) {
            Log.e("ViewRootImpl","Focused.dispatchKeyEvent(event)");
            return true;
        }
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
    }
    return false;
}

用于将按键事件分派到该ViewGroup及其子View。该方法首先通过检查ViewGroup本身的状态(是否拥有焦点且有边界)来决定是否自己处理KeyEvent,如果ViewGroup本身满足条件,则通过调用父类的dispatchKeyEvent方法处理事件,并返回true表示已处理。如果ViewGroup本身不满足条件,则将KeyEvent分派到当前拥有焦点的子View,如果该子View处理了事件,则返回true表示已处理。如果KeyEvent最终未被处理,则返回false表示未处理。此方法还包含一些调试代码,用于确保事件派发的一致性。

/frameworks/base/core/java/android/view/View.java

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)) {
        Log.e("ViewRootImpl","mOnKeyListener.onKey");
        return true;
    }

    if (event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)) {
        Log.e("ViewRootImpl","event.dispatch");
        return true;
    }

    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    return false;
}

该方法用于分派键盘事件到对应的视图,并根据事件的处理结果返回一个布尔值。方法的参数event表示一个键盘事件,该事件将被分派到相应的视图。方法中的第一步是调用mInputEventConsistencyVerifier.onKeyEvent(event, 0)方法来记录键盘事件的一些基本信息,用于后续的事件一致性检查。然后会首先调用视图的OnKeyListener对象(如果有的话)的onKey 方法,如果该方法返回true,则表示该键盘事件被该监听器处理,方法返回true,否则该键盘事件被传递给了该视图的dispatch方法。在这里调用了event.dispatch(this, mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null, this)方法,该方法会根据键盘事件的类型,将其分发给视图的onKeyDown或onKeyUp方法进行处理。如果该事件被处理了,则返回true,否则会调用mInputEventConsistencyVerifier.onUnhandledEvent(event, 0)方法记录该事件未被处理的信息,并返回false表示该事件未被处理。

/frameworks/base/core/java/android/view/KeyEvent.java

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;
}

KeyEvent事件分发的核心代码,主要处理按键事件的分发。当一个按键事件被分发到一个View时,该View首先尝试处理该事件。如果该View无法处理该事件,则事件将被分发给它的parent View或Activity,直到事件被处理或到达了View层级的最顶层。

总的来说,KeyEvent类的dispatch方法实现了按键事件的分发和处理,为Android应用程序提供了丰富的按键事件处理能力。

  1. 「特殊情况」:

Key事件的分发逻辑是一个复杂但有序的过程,涉及事件的产生、分发和View的事件处理等多个阶段。通过理解这个过程,可以更好地控制Android应用中按键事件的行为。

来源:沐雨花飞蝶内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯