Android TV按键传递机制

Android TV按键传递机制

Activity#dispatchKeyEvent()
    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();
        return event.dispatch(this, decor != null
                ? decor.getKeyDispatcherState() : null, this);
    }
  • 分析源码可知,事件传递的优先顺序如下:ActionBar——>Window——>event.dispatch,下面按照这个顺序依次分析.
一、ActionBar
  • keyCode如果是KEYCODE_MENU,且actionBar存在,则交给了actionBar处理:使用其onMenuKeyEvent方法,对菜单按键进行响应;

  • 如果actionBar不消费,就交给Window处理。

二、Window
1.Window#superDispatchKeyEvent()
public abstract boolean superDispatchKeyEvent(KeyEvent event);
2.PhoneWindow ->Window#superDispatchKeyEvent()
 @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }
3.DecorView#superDispatchKeyEvent()
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;
            }
        }
        return super.dispatchKeyEvent(event);
    }
  • DecorView在keyCode == KECODE_BACK时,检查是否有Menu弹出,有则毁MENU,防止内存泄漏。(mPrimaryActionMode是一种菜单方式,这里使用它销毁Menu)。

  • 不是BACK按键时,return super.dispatchKeyEvent,即交给父类处理,因为DecorView继承于FrameLayout,所以super.dispatchKeyEvent其实就是ViewGroup.dispatchKeyEvent,key事件在View层级中的传递最终在这里进行。

4.ViewGroup#dispatchKeyEvent()
@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)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)
                == PFLAG_HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }
  • mInputEventConsistencyVerifier主要在debug的时候使用。
  • 如果ViewGroup自身已经获得焦点,就调用super.dispatchKeyEvent,即View.dispatchKeyEvent。
  • 如果ViewGroup没有获取到焦点,而是ViewGroup中的某一个child获得了焦点,则把事件交给这个child,即上述代码中的mFocused,将key事件传递给焦点子View处理,也是View.dispatchKeyEvent。
  • 如果都没有获取到焦点,则直接return false,不进行消费。那么最终就会执行到Activity中的event.dispatch()方法。
    总结一下就是,只有当ViewGroup或其child获取到焦点,才有机会对key事件进行消费。如果都没有获取焦点则交给Activity中的event.dispatch()进行消费。
5.View#dispatchKeyEvent()(终于不是@override了)
    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)) {
            return true;
        }

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

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }
  • 注释的意思是将key事件派发到焦点路径上的某一个View,这个焦点路径遵循View树结构自顶向下的规则,如果当前View获得焦点,事件就派发给它自己,否则继续在焦点路径上往下派发。该方法也会触发KeyListener。
  • 如果View设置了KeyListener,且该View是启用状态,则将事件交给它设置的KeyListener处理。
  • 如果KeyListener不消费,或者未设置KeyListener,则调用event.dispatch方法。这个方法和Activity中是同一个方法。
  • 再来回忆一下Activity中是如何调用该方法的,
    event.dispatch(this, decor != null ? decor.getKeyDispatcherState() : null, this);
    KeyEvent.dispath的第一个参数this,其实就是View和Activity都实现的接口KeyEvent.Callback。
  • 查阅代码看一下这个接口里的内容:
public interface Callback {
        
        boolean onKeyDown(int keyCode, KeyEvent event);
        boolean onKeyLongPress(int keyCode, KeyEvent event);
        boolean onKeyUp(int keyCode, KeyEvent event);
        boolean onKeyMultiple(int keyCode, int count, KeyEvent event);
    }
  • KeyEvent的Callback接口主要包含onKeyDown、onKeyUp、onKeyLongPress、onKeyMultiPle方法,这就是平时开发中,接收key事件常常复写的那个onKeyDown。
  • 接下来我们在event.dispatch方法看一下
6.KeyEvent#dispatch()
    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;
    }
  • dispatch方法中,根据KeyEvent.action进行判断:ACTION_DOWN调用receiver.onKeyDownACTION_UP调用receiver.onKeyUpACTION_MULTIPLE调用receiver.onKeyMultiple。
  • 其中的receiver就是在View和Activity中传入的this,那么我们需要在View和Activity看这个receiver中的具体实现了
7.View#onKeyDown()
/**
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }

            if (event.getRepeatCount() == 0) {
                // Long clickable items don't necessarily have to be clickable.
                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
                    // For the purposes of menu anchoring and drawable hotspots,
                    // key events are considered to be at the center of the view.
                    final float x = getWidth() / 2f;
                    final float y = getHeight() / 2f;
                    if (clickable) {
                        setPressed(true, x, y);
                    }
                    checkForLongClick(0, x, y);
                    return true;
                }
            }
        }

        return false;
    }
    
    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        return false;
    }

    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }
            if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {
                setPressed(false);

                if (!mHasPerformedLongPress) {
                    // This is a tap, so remove the longpress check
                    removeLongPressCallback();
                    if (!event.isCanceled()) {
                        return performClick();
                    }
                }
            }
        }
        return false;
    }

    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return false;
    }
  • View的onKeyLongPress和onKeyMultiPe方法都默认返回了false。
  • View的onKeyDown和onKeyUp方法中默认使用isConfirmKey进行判断,查看上述源码可知,isConfirmKey只在ENTER或PPAD_CENTER时返回true,即遥控器上的确认键或OK键,所以onKeyDown和onKeyUp在默认情况下只消费ENTER和CENTER这种确认类型的按键。
  • 如果View的onKeyDown、onKeyUp等KeyEvent.Callback的实现方法都不消费,表示Activity.dispatchKeyEvent将事件派发给Window没有被消费,事件继续往下传递,到了Activity.dispatchKeyEvent方法中的第三部分——交给Activity中的event.dispatch处理。
三、Activity中的event.dispatch

终于从Window中出来了,这时候前面两个都没有对按键进行消费,交给Activity来处理。

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

        if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
            return false;
        } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
            Window w = getWindow();
            if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
                    w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
                            Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
                return true;
            }
            return false;
        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
            // Don't consume TAB here since it's used for navigation. Arrow keys
            // aren't considered "typing keys" so they already won't get consumed.
            return false;
        } else {
            // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
            boolean clearSpannable = false;
            boolean handled;
            if ((event.getRepeatCount() != 0) || event.isSystem()) {
                clearSpannable = true;
                handled = false;
            } else {
                handled = TextKeyListener.getInstance().onKeyDown(
                        null, mDefaultKeySsb, keyCode, event);
                if (handled && mDefaultKeySsb.length() > 0) {
                    // something useable has been typed - dispatch it now.

                    final String str = mDefaultKeySsb.toString();
                    clearSpannable = true;

                    switch (mDefaultKeyMode) {
                    case DEFAULT_KEYS_DIALER:
                        Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        startActivity(intent);
                        break;
                    case DEFAULT_KEYS_SEARCH_LOCAL:
                        startSearch(str, false, null, false);
                        break;
                    case DEFAULT_KEYS_SEARCH_GLOBAL:
                        startSearch(str, false, null, true);
                        break;
                    }
                }
            }
            if (clearSpannable) {
                mDefaultKeySsb.clear();
                mDefaultKeySsb.clearSpans();
                Selection.setSelection(mDefaultKeySsb,0);
            }
            return handled;
        }
    }

    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
        return false;
    }

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

    /**
     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
     * the event).
     */
    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
        return false;
    }
  • 当一个键被按下,并且没有被活动中的任何View处理时才会调用其中的方法。例如,当光标在TextView内时按下键不会触发事件(除非是指向另一个对象),因为TextView会自己处理。一般用于获取焦点的view不想自己处理按键事件时调用。
  • 在Activity的dispatchKeyEvent中使用时需注意注意return super.dispatchKeyEvent,正因为key事件从dispatchKeyEvent分发到onKeyDown、onKeyUp等是由envet.dispatch实现的,所以如果用户在Activity中复写了dispatchKeyEvent方法,如果返回true或false,key事件都不会到Activity的onKeyDown或onKeyUp中,只有返回super.dispatchKeyEvent,执行到了event.dispatch,key事件才能够派发到Activity的onKeyDown或onKeyUp中。
四、总结

Key事件在Window传递的流程

1、 如果是BACK按键,先交给MENU处理,
2、如果不是BACK按键或MENU未处理,交给ViewGroup传递给它的焦点View:焦点View可以是ViewGroup本身或ViewGroup的child,如果焦点View设置了KeyListener,传递给KeyListener,如果没有,按KeyEvent.action类型传递给对应的onKeyXXX方法
3、 如果事件传递到了onKeyDown都没被处理,代表Window传递到它的View各个层级都没有消费该事件,回溯到Activity.dispatchKeyEvent继续往下传递,即执行event.dispatch()方法。

Key事件在Activity中传递的流程

1、key事件首先交给了actionBar处理
2、如果actionBar未处理,则交给window处理,window直接交给了DecorView.superDispatchKeyEvent,之后再传给ViewGroup的dispatchKeyEvent(),将事件传递给了获取焦点的View。
4、如果焦点view未处理,继续传递给View中的KeyEvent.dispatch,如果还未处理最终KeyEvent.dispatch将事件交给Activity的onKeyDown、onKeyUp、onKeyMultiPe或onKeyLongPress处理。
5、如果Activity的onKeyDown、onKeyUp、onKeyMultiPe或onKeyLongPress都没处理,即Activity本身及其ContentView都没有消费该事件,最终该事件就交给PhoneWindow或ViewRootImpl自动处理:自动查找焦点、系统音量调节等。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值