TV

从源码分析key事件的传递机制

2017-06-13  本文已影响0人  鞋xx

开发中需要用到遥控器,各种上下左右菜单音量飞鼠OK按键满天飞...
对于key事件的捕获仅限于BACK/MENU/HOME按键的我来说,这完全是在搞事情啊!
因此,决定深刻认识一下,从源码角度(android-23)来理解key事件的传递机制。
首先从入口开始,直接看Activity的dispatchKeyEvent方法:

    /**
     * Called to process key events.  You can override this to intercept all
     * key events before they are dispatched to the window.  Be sure to call
     * this implementation for key events that should be handled normally.
     *
     * @param event The key event.
     *
     * @return boolean Return true if this event was consumed.
     */
    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();

        // Let action bars open menus in response to the menu key prioritized over
        // the window handling it
        if (event.getKeyCode() == 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

Window对key事件的处理与Touch事件有些类似,都是View树中的传递,
不过Touch事件是由父到子层层往下传递,而key事件的传递是由焦点位置决定的。
下面按层级从Window——>View逐个分析

1、Window.superDispatchKeyEvent
如果actionBar不消费,事件传递给Window,
window直接调用了上述代码中的Window.superDispatchKeyEvent方法,
Window.superDispatchKeyEvent源码如下:

    /**
     * Used by custom windows, such as Dialog, to pass the key press event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchKeyEvent(KeyEvent event);

注释已经说得很清楚:
*“该方法用于自定义窗口下(如dialog、Activity),key事件在View层级中的传递,开发过程中不需要自己去实现或调用这个方法。” *
Window是一个抽象类,Window#superDispatchKeyEvent也是一个抽象方法,
这个方法具体都做了些什么,我们到Window的唯一实现类PhoneWindow去查看,

2、PhoneWindow.superDispatchKeyEvent
PhoneWindow.superDispatchKeyEvent源码如下:

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

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

我们看到,PhoneWindow中直接调用了DecorView的superDispatchKeyEvent方法
简单介绍一下DecorView:
它是Activity的顶层容器,整个View树结构的最顶层View,代表整个应用的界面。Window、DecorView、Activity三者关系可以这样类比:
Window是窗户,DecorView是粘在窗户上的纸,Activity显示的内容就是纸上所画的内容。
DecorView分为title和content两部分,而这个content,也就是我们在Activity中setContentView所设置的布局View。

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

3、ViewGroup.dispatchKeyEvent
ViewGroup.dispatchKeyEvent源码:

    // The view contained within this ViewGroup that has or contains focus.
    private View mFocused;

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

4、View.dispatchKeyEvent
View.dispatchKeyEvent源码:

    /**
     * Dispatch a key event to the next view on the focus path. This path runs
     * from the top of the view tree down to the currently focused view. If this
     * view has focus, it will dispatch to itself. Otherwise it will dispatch
     * the next node down the focus path. This method also fires any key
     * listeners.
     *
     * @param event The key event to be dispatched.
     * @return True if the event was handled, false otherwise.
     */
    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;
    }

5、KeyEvent.dispatch
(1)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;
    }
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

显然,这里的receiver就是View本身,所以KeyEvent的dispatch方法将事件分了一下类别,然后又交给了View中对应的方法去处理,接下来看看KeyEvent.Callback在View中的实现

6、KeyEvent.Callback
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);
    }

View中对Callback的实现如下:

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

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

    public boolean onKeyDown(int keyCode, KeyEvent event) {
        boolean result = false;

        if (KeyEvent.isConfirmKey(keyCode)) {
            if ((mViewFlags & ENABLED_MASK) == DISABLED) {
                return true;
            }
            // Long clickable items don't necessarily have to be clickable
            if (((mViewFlags & CLICKABLE) == CLICKABLE ||
                    (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&
                    (event.getRepeatCount() == 0)) {
                setPressed(true);
                checkForLongClick(0);
                return true;
            }
        }
        return result;
    }

    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();
                    return performClick();
                }
            }
        }
        return false;
    }

     /** Whether key will, by default, trigger a click on the focused view.
     * @hide
     */
    public static final boolean isConfirmKey(int keyCode) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
                return true;
            default:
                return false;
        }
    }

7、最后,总结一下Key事件在Window传递的整个流程:
1、 如果是BACK按键,先交给MENU处理,
2、如果不是BACK按键或MENU未处理,交给ViewGroup传递给它的焦点View:焦点View可以是ViewGroup本身或ViewGroup的child,如果焦点View设置了KeyListener,传递给KeyListener,如果没有,按KeyEvent.action类型传递给对应的onKeyDown等方法
3、 如果事件传递到了onKeyDown都没被处理,代表Window传递到它的View各个层级都没有消费该事件,回溯到Activity.dispatchKeyEvent继续往下传递

三、event.dispatch交给自己的onKeyDown

1、如果window不消费,最后直接执行了keyEvent.dispatch方法,这里的逻辑与View中的keyEvent.dispatch完全相同,只是Callback在Activity中实现,所以key事件都到了Activity的onKeyDown、onKeyUp....
2、注意:
正因为key事件从dispatchKeyEvent分发到onKeyDown、onKeyUp等是由envet.dispatch实现的,
所以如果用户在Activity中复写了dispatchKeyEvent方法,
如果返回true或false,key事件都不会到Activity的onKeyDown或onKeyUp中,
只有返回super.dispatchKeyEvent,执行到了event.dispatch,key事件才能够派发到Activity的onKeyDown或onKeyUp中,这是不同于dispatchTouchEvent的地方

四、Activity中Key事件传递流程总结

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

整个事件分发流程图如下:

Key传递流程.png

五、Activity中Key事件与Touch事件传递机制的区别

以上分析,如果有错误或任何建议的地方,欢迎指正,一起交流学习!

上一篇 下一篇

猜你喜欢

热点阅读