您的位置:首页 > 移动开发 > Android开发

Android按键事件处理流程

2017-03-15 15:24 357 查看

刚接触Android开发的时候,对touch、key事件的处理总是一知半解,一会是Activity里的方法,一会是各种View

中的,自己始终不清楚到底哪个在先哪个在后,总之对整个处理流程没能很好的把握。每次写这部分代码的时候都有些心虚,

因为我不是很清楚什么时候、以什么样的顺序被调用,大都是打下log看看,没问题就算ok了。但随着时间流逝,这种感觉一直

折磨着我。期间也在网上搜索了相关资料,但总感觉不是那么令人满意。自打开始研究Android源码起,这部分内容的分析早就

被列在我的TODO list上了。因为弄懂这部分处理逻辑对明明白白地写android程序实在是太重要了,所以今天我就带领大家看看

这部分的处理逻辑。touch事件的处理我将放在另一篇博客中介绍(相比KeyEvent,大体都一样,只是稍微复杂些)。

  为了突出本文的重点,我们直接从事件被派发到View层次结构的根节点DecorView开始分析,这里我们先来看看DecorView#

dispatchKeyEvent方法,代码如下:

    @Override

    public boolean dispatchKeyEvent(KeyEvent event) {

        final int keyCode = event.getKeyCode();

        final int action = event.getAction();

        final boolean isDown = action == KeyEvent.ACTION_DOWN;

        /// 1. 第一次down事件的时候,处理panel的快捷键

        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 ((mPanelChordingKey > 0) && (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 ((mPreparedPanel != null) && mPreparedPanel.isOpen) {

                if (performPanelShortcut(mPreparedPanel, keyCode, event, 0)) {

                    return true;

                }

            }

        }

        /// 2. 这里是我们本文的重点,当window没destroy且其Callback非空的话,交给其Callback处理

        if (!isDestroyed()) { // Activity、Dialog都是Callback接口的实现

            final Callback cb = getCallback(); // mFeatureId < 0 表示是application的DecorView,比如Activity、Dialog

            final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event) // 派发给callback的方法

                    : super.dispatchKeyEvent(event); // 否则直接派发到ViewGroup#dispatchKeyEvent(View层次结构)

            if (handled) {

                return true; // 如果被上面的步骤处理了则直接返回true,不再往下传递

            }

        }

        /// 3. 这是key事件的最后一步,如果到这一步还没处理掉,则派发到PhoneWindow对应的onKeyDown, onKeyUp方法

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

                : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);

    }

  接下来我们按照这个派发顺序依次来看看相关方法的实现,这里先看看Activity(Callback)的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.

     */

    @Override

    public boolean dispatchKeyEvent(KeyEvent event) {

        /// 2.1. 回调接口,实际开发中用处不大,你感兴趣可以参看其方法doc

        onUserInteraction();

        Window win = getWindow();

        /// 2.2. 从这里事件的处理交给了与之相关的window对象,实质是派发到了view层次结构

        if (win.superDispatchKeyEvent(event)) {

            return true;

        }

        View decor = mDecor;

        if (decor == null) decor = win.getDecorView();

        //
14cfd
/ 2.3. 到这里如果view层次结构没处理则交给KeyEvent本身的dispatch方法,Activity的各种回调方法会被触发

        return event.dispatch(this, decor != null

                ? decor.getKeyDispatcherState() : null, this);

    }

紧接着我们看看,Window#superDispatchKeyEvent方法,相关代码如下:

    <!-- Window.java -->

    /**

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

    <!-- PhoneWindow.java -->

    @Override

    public boolean superDispatchKeyEvent(KeyEvent event) {

        return mDecor.superDispatchKeyEvent(event);

    }

      <!-- DecorView.superDispatchKeyEvent -->

  public boolean superDispatchKeyEvent(KeyEvent event) {

            /// 2.2.1. 进入view层次结构了,即调用ViewGroup的对应实现了。。。

            if (super.dispatchKeyEvent(event)) {

                return true; // 如果被view层次结构处理了则直接返回true。

            }

            // Not handled by the view hierarchy, does the action bar want it

            // to cancel out of something special?

            /// 2.2.2. ActionBar对BACK key的特殊处理

            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {

                final int action = event.getAction();

                // Back cancels action modes first.

                if (mActionMode != null) {

                    if (action == KeyEvent.ACTION_UP) {

                        mActionMode.finish();

                    }

                    return true;

                }

                // Next collapse any expanded action views.

                if (mActionBar != null && mActionBar.hasExpandedActionView()) {

                    if (action == KeyEvent.ACTION_UP) {

                        mActionBar.collapseActionView();

                    }

                    return true;

                }

            }

            /// 2.2.3. 最后返回false表示没处理掉,会接着2.3.步骤处理

            return false;

        }

然后我们接着看看2.2.1.包括的小步骤,即ViewGroup#dispatchKeyEvent的实现,代码如下:

    @Override

    public boolean dispatchKeyEvent(KeyEvent event) {

        /// 2.2.1.1. keyevent一致性检测用的,可忽略。。。

        if (mInputEventConsistencyVerifier != null) {

            mInputEventConsistencyVerifier.onKeyEvent(event, 1);

        }

        if ((mPrivateFlags & (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS))

                == (PFLAG_FOCUSED | PFLAG_HAS_BOUNDS)) {

            /// 2.2.1.2. 如果此ViewGroup是focused或者具体的大小被设置了,则交给他处理,即调用View的实现

            if (super.dispatchKeyEvent(event)) {

                return true;

            }

        } else if (mFocused != null && (mFocused.mPrivateFlags & PFLAG_HAS_BOUNDS)

                == PFLAG_HAS_BOUNDS) {

            /// 2.2.1.3. 否则,如果此ViewGroup中有focused的child,且child有具体的大小,则交给mFocused处理

            if (mFocused.dispatchKeyEvent(event)) { // 注意这里可能是个递归调用

                return true; // 我们可以看到并不是每个child都能响应key事件,前提必须是focused child才有机会响应

            }

        }

        if (mInputEventConsistencyVerifier != null) {

            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);

        }

        /// 2.2.1.4. 最后都没被处理返回false,2.2.2.步骤会接着执行。。。

        return false;

    }

这里我们可以看出对KeyEvent来说在View层次结构中,如果ViewGroup条件满足则会优先处理事件而不是先派发给其孩子view,

这一点和touch事件有所不同。这里我们看看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;

        /// 2.2.1.2(3).1. 调用onKeyListener,如果它非空且view是ENABLED状态,监听器优先触发

        if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED

                && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {

            return true;

        }

        /// 2.2.1.2(3).2. 调用KeyEvent.dispatch方法,并将view对象本身作为参数传递进去,view的各种callback方法在这里被触发

        if (event.dispatch(this, mAttachInfo != null

                ? mAttachInfo.mKeyDispatchState : null, this)) {

            return true;

        }

        if (mInputEventConsistencyVerifier != null) {

            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);

        }

        /// 2.2.1.2(3).3. 还没处理掉返回false,接着2.2.1.4.执行

        return false;

    }

不管是这里的2.2.1.2(3).2.步骤还是前面Activity里的2.3.步骤,都调到了KeyEvent.dispatch方法,不过在看其代码之前我们

先来看看这里用到的mAttachInfo.mKeyDispatchState对象是咋来的,代码如下:

// 这句代码位于View.AttachInfo类里

final KeyEvent.DispatcherState mKeyDispatchState

                = new KeyEvent.DispatcherState();

/**

     * Return the global {@link KeyEvent.DispatcherState KeyEvent.DispatcherState}

     * for this view's window.  Returns null if the view is not currently attached

     * to the window.  Normally you will not need to use this directly, but

     * just use the standard high-level event callbacks like

     * {@link #onKeyDown(int, KeyEvent)}.

     */

    public KeyEvent.DispatcherState getKeyDispatcherState() {

        return mAttachInfo != null ? mAttachInfo.mKeyDispatchState : null;

    }

// KeyEvent.DispatcherState类

    /**

     * Use with {@link KeyEvent#dispatch(Callback, DispatcherState, Object)}

     * for more advanced key dispatching, such as long presses.

     */

    public static class DispatcherState {

        int mDownKeyCode;

        Object mDownTarget;

        SparseIntArray mActiveLongPresses = new SparseIntArray();

       

        /**

         * Reset back to initial state.

         */

        public void reset() { // 清空内部状态

            if (DEBUG) Log.v(TAG, "Reset: " + this);

            mDownKeyCode = 0;

            mDownTarget = null;

            mActiveLongPresses.clear();

        }

       

        /**

         * Stop any tracking associated with this target.

         */

        public void reset(Object target) { // 清空target对应的内部状态

            if (mDownTarget == target) { // 只有相同时才清空,否则啥也不做

                if (DEBUG) Log.v(TAG, "Reset in " + target + ": " + this);

                mDownKeyCode = 0;

                mDownTarget = null;

            }

        }

       

        /**

         * Start tracking the key code associated with the given event.  This

         * can only be called on a key down.  It will allow you to see any

         * long press associated with the key, and will result in

         * {@link KeyEvent#isTracking} return true on the long press and up

         * events.

         *

         * <p>This is only needed if you are directly dispatching events, rather

         * than handling them in {@link Callback#onKeyDown}.

         */

        public void startTracking(KeyEvent event, Object target) {

            if (event.getAction() != ACTION_DOWN) { // 状态检测

                throw new IllegalArgumentException(

                        "Can only start tracking on a down event");

            }

            if (DEBUG) Log.v(TAG, "Start trackingt in " + target + ": " + this);

            mDownKeyCode = event.getKeyCode(); // 赋值,表示正在track某个keycode

            mDownTarget = target;

        }

       

        /**

         * Return true if the key event is for a key code that is currently

         * being tracked by the dispatcher.

         */

        public boolean isTracking(KeyEvent event) {

            return mDownKeyCode == event.getKeyCode();

        }

       

        /**

         * Keep track of the given event's key code as having performed an

         * action with a long press, so no action should occur on the up.

         * <p>This is only needed if you are directly dispatching events, rather

         * than handling them in {@link Callback#onKeyLongPress}.

         */

        public void performedLongPress(KeyEvent event) {// 用来记录发生了生理长按事件

            mActiveLongPresses.put(event.getKeyCode(), 1);

        }

       

        /**

         * Handle key up event to stop tracking.  This resets the dispatcher state,

         * and updates the key event state based on it.

         * <p>This is only needed if you are directly dispatching events, rather

         * than handling them in {@link Callback#onKeyUp}.

         */

        public void handleUpEvent(KeyEvent event) {

            final int keyCode = event.getKeyCode();

            if (DEBUG) Log.v(TAG, "Handle key up " + event + ": " + this);

            int index = mActiveLongPresses.indexOfKey(keyCode);

            if (index >= 0) { // 如果发生过生理长按则设置event.mFlags为CACELED,这样在接下来的receiver.onKeyUp中有些处理就不会发生了

                if (DEBUG) Log.v(TAG, "  Index: " + index); // 因为事件被标记为CANCELED了

                event.mFlags |= FLAG_CANCELED | FLAG_CANCELED_LONG_PRESS;

                mActiveLongPresses.removeAt(index);

            }

            if (mDownKeyCode == keyCode) {

                if (DEBUG) Log.v(TAG, "  Tracking!");

                event.mFlags |= FLAG_TRACKING; // 设置event正确的mFlags,接下来的receiver.onKeyUp可能会检测此状态

                mDownKeyCode = 0; // reset,表示此keycode的tracking到此结束了

                mDownTarget = null;

            }

        }

    }

大概了解了KeyEvent.DispatcherState类,我们就可以来看看KeyEvent.dispatch方法了,代码如下:

    /**

     * Deliver this key event to a {@link Callback} interface.  If this is

     * an ACTION_MULTIPLE event and it is not handled, then an attempt will

     * be made to deliver a single normal event.

     *

     * @param receiver The Callback that will be given the event.

     * @param state State information retained across events.

     * @param target The target of the dispatch, for use in tracking.

     *

     * @return The return value from the Callback method that was called.

     */

    public final boolean dispatch(Callback receiver, DispatcherState state,

            Object target) {

        switch (mAction) {

            case ACTION_DOWN: { // DOWN事件

                mFlags &= ~FLAG_START_TRACKING; //先清掉START_TRACKING标记

                if (DEBUG) Log.v(TAG, "Key down to " + target + " in " + state

                        + ": " + this);

                boolean res = receiver.onKeyDown(mKeyCode, this); // 回调Callback接口的onKeyDown方法,View和Activity都是此接口的实现者

                if (state != null) { // 一般都成立

                    if (res && mRepeatCount == 0 && (mFlags&FLAG_START_TRACKING) != 0) {

                        if (DEBUG) Log.v(TAG, "  Start tracking!"); // receiver.onKeyDown返回true了且不是repeated

                        state.startTracking(this, target); // 并且也没有开始tracking,则开始tracking当前的KeyEvent和target

                    } else if (isLongPress() && state.isTracking(this)) { // 处理生理长按

                        try { // 检测到生理长按则调用receiver.onKeyLongPress方法

                            if (receiver.onKeyLongPress(mKeyCode, this)) {

                                if (DEBUG) Log.v(TAG, "  Clear from long press!");

                                state.performedLongPress(this); // 记录此event已经有生理long press发生了。。。

                                res = true; // 设置为处理了

                            }

                        } catch (AbstractMethodError e) {

                        }

                    }

                }

                return res; // 返回down事件处理的结果

            }

            case ACTION_UP: // UP事件

                if (DEBUG) Log.v(TAG, "Key up to " + target + " in " + state

                        + ": " + this);

                if (state != null) {

                    state.handleUpEvent(this); // reset state的内部状态,也改变了KeyEvent的某些状态

                }

                return receiver.onKeyUp(mKeyCode, this); // 最后调用receiver.onKeyUp方法

            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的具体实现,我们接着看看receiver(Callback接口)的onKeyDown、onKeyUp实现,先来看View相关的,代码如下:

/**

     * Default implementation of {@link KeyEvent.Callback#onKeyDown(int, KeyEvent)

     * KeyEvent.Callback.onKeyDown()}: perform press of the view

     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or
{@link KeyEvent#KEYCODE_ENTER}

     * is released, if the view is enabled and clickable.

     *

     * <p>Key presses in software keyboards will generally NOT trigger this listener,

     * although some may elect to do so in some situations. Do not rely on this to

     * catch software key presses.

     *

     * @param keyCode A key code that represents the button pressed, from

     *                {@link android.view.KeyEvent}.

     * @param event   The KeyEvent object that defines the button action.

     */

public boolean onKeyDown(int keyCode, KeyEvent event) {

        boolean result = false;

        if (KeyEvent.isConfirmKey(keyCode)) { // 只处理KEYCODE_DPAD_CENTER、KEYCODE_ENTER这2个按键

            if ((mViewFlags & ENABLED_MASK) == DISABLED) {

                return true; // 针对disabled View直接返回true表示处理过了

            }

            // Long clickable items don't necessarily have to be clickable

            if (((mViewFlags & CLICKABLE) == CLICKABLE ||

                    (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) &&

                    (event.getRepeatCount() == 0)) { // clickable或者long_clickable且是第一次down事件

                setPressed(true); // 标记pressed,你可能设置了View不同的background,这时候就会有所体现(比如高亮效果)

                checkForLongClick(0); // 启动View的long click检测

                return true; // 到达这一步就表示KeyEvent被处理掉了

            }

        }

        return result;

    }

/**

     * Default implementation of {@link KeyEvent.Callback#onKeyUp(int, KeyEvent)

     * KeyEvent.Callback.onKeyUp()}: perform clicking of the view

     * when {@link KeyEvent#KEYCODE_DPAD_CENTER} or

     * {@link KeyEvent#KEYCODE_ENTER} is released.

     * <p>Key presses in software keyboards will generally NOT trigger this listener,

     * although some may elect to do so in some situations. Do not rely on this to

     * catch software key presses.

     *

     * @param keyCode A key code that represents the button pressed, from

     *                {@link android.view.KeyEvent}.

     * @param event   The KeyEvent object that defines the button action.

     */

    public boolean onKeyUp(int keyCode, KeyEvent event) {

        if (KeyEvent.isConfirmKey(keyCode)) { // 同onKeyDown,默认也只处理confirm key

            if ((mViewFlags & ENABLED_MASK) == DISABLED) {

                return true; // 同样的逻辑,如果是DISABLED view,直接返回true表示处理过了

            }

            if ((mViewFlags & CLICKABLE) == CLICKABLE && isPressed()) {

                setPressed(false); // 重置pressed状态

                if (!mHasPerformedLongPress) { // 长按没发生的话,

                    // This is a tap, so remove the longpress check

                    removeLongPressCallback(); // 当up事件发生的时候,移除这些已经没用的callback

                    return performClick(); // 调用单击onClick监听器

                }

            }

        }

        return false; // 其他所有的Key默认不处理

    }

/**

     * Sets the pressed state for this view.

     *

     * @see #isClickable()

     * @see #setClickable(boolean)

     *

     * @param pressed Pass true to set the View's internal state to "pressed", or false to reverts

     *        the View's internal state from a previously set "pressed" state.

     */

    public void setPressed(boolean pressed) {

        final boolean needsRefresh = pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

        if (pressed) {

            mPrivateFlags |= PFLAG_PRESSED;

        } else {

            mPrivateFlags &= ~PFLAG_PRESSED;

        }

        if (needsRefresh) {

            refreshDrawableState(); // 这行代码会刷新View的显示状态

        }

        dispatchSetPressed(pressed);

    }

private void checkForLongClick(int delayOffset) {

        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) { // 必须得是LONG_CLICKABLE的View

            mHasPerformedLongPress = false; // 设置初始值

            if (mPendingCheckForLongPress == null) { // 只非空的时候才new一个

                mPendingCheckForLongPress = new CheckForLongPress();

            }

            mPendingCheckForLongPress.rememberWindowAttachCount();

            postDelayed(mPendingCheckForLongPress, // post一个Runnable,注意延迟是个差值,而不是delayOffset

                    ViewConfiguration.getLongPressTimeout() - delayOffset);

        }

    }

class CheckForLongPress implements Runnable {

        private int mOriginalWindowAttachCount;

        public void run() {

            if (isPressed() && (mParent != null) // 当时间到了,此Runnable没被移除掉的话,并且这些条件都满足的时候,

                    && mOriginalWindowAttachCount == mWindowAttachCount) {

                if (performLongClick()) { // 客户端定义的onLongClickListener监听器被触发

                    mHasPerformedLongPress = true; // 只有当被上面的方法处理掉了,才表示LongPress发生过了

                }

            }

        }

        public void rememberWindowAttachCount() {

            mOriginalWindowAttachCount = mWindowAttachCount;

        }

    }

/**

     * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the

     * OnLongClickListener did not consume the event.

     *

     * @return True if one of the above receivers consumed the event, false otherwise.

     */

    public boolean performLongClick() {

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);

        boolean handled = false;

        ListenerInfo li = mListenerInfo;

        if (li != null && li.mOnLongClickListener != null) { // 优先触发监听器

            handled = li.mOnLongClickListener.onLongClick(View.this);

        }

        if (!handled) { // 如果还没处理,显示ContextMenu如果定义了的话

            handled = showContextMenu();

        }

        if (handled) {

            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);

        }

        return handled; // 返回处理结果

    }

  接下来,看看Activity对应的onKeyDown,onKeyUp方法:

    /**

     * Called when a key was pressed down and not handled by any of the views

     * inside of the activity. So, for example, key presses while the cursor

     * is inside a TextView will not trigger the event (unless it is a navigation

     * to another object) because TextView handles its own key presses.

     *

     * <p>If the focused view didn't want this event, this method is called.

     *

     * <p>The default implementation takes care of
{@link KeyEvent#KEYCODE_BACK}

     * by calling {@link #onBackPressed()}, though the behavior varies based

     * on the application compatibility mode: for

     * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,

     * it will set up the dispatch to call
{@link #onKeyUp} where the action

     * will be performed; for earlier applications, it will perform the

     * action immediately in on-down, as those versions of the platform

     * behaved.

     *

     * <p>Other additional default key handling may be performed

     * if configured with {@link #setDefaultKeyMode}.

     *

     * @return Return <code>true</code> to prevent this event from being propagated

     * further, or <code>false</code> to indicate that you have not handled

     * this event and it should continue to be propagated.

     * @see #onKeyUp

     * @see android.view.KeyEvent

     */

    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) {

            if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL,

                    keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {

                return true;

            }

            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;

        }

    }

/**

     * Called when a key was released and not handled by any of the views

     * inside of the activity. So, for example, key presses while the cursor

     * is inside a TextView will not trigger the event (unless it is a navigation

     * to another object) because TextView handles its own key presses.

     *

     * <p>The default implementation handles KEYCODE_BACK to stop the activity

     * and go back.

     *

     * @return Return <code>true</code> to prevent this event from being propagated

     * further, or <code>false</code> to indicate that you have not handled

     * this event and it should continue to be propagated.

     * @see #onKeyDown

     * @see KeyEvent

     */

    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;

    }

  最后是3.步骤,回到一开始DecorView.dispatchKeyEvent的最后几行代码,我们来看看PhoneWindow对应的onKeyDown,onKeyUp方法:

/**

     * A key was pressed down and not handled by anything else in the window.

     *

     * @see #onKeyUp

     * @see android.view.KeyEvent

     */

    protected boolean onKeyDown(int featureId, int keyCode, KeyEvent event) {

        /* ****************************************************************************

         * HOW TO DECIDE WHERE YOUR KEY HANDLING GOES.

         *

         * If your key handling must happen before the app gets a crack at the event,

         * it goes in PhoneWindowManager.

         *

         * If your key handling should happen in all windows, and does not depend on

         * the state of the current application, other than that the current

         * application can override the behavior by handling the event itself, it

         * should go in PhoneFallbackEventHandler.

         *

         * Only if your handling depends on the window, and the fact that it has

         * a DecorView, should it go here.

         * ****************************************************************************/

        final KeyEvent.DispatcherState dispatcher =

                mDecor != null ? mDecor.getKeyDispatcherState() : null;

        //Log.i(TAG, "Key down: repeat=" + event.getRepeatCount()

        //        + " flags=0x" + Integer.toHexString(event.getFlags()));

       

        switch (keyCode) {

            case KeyEvent.KEYCODE_VOLUME_UP: // key event处理中的最后一步,

            case KeyEvent.KEYCODE_VOLUME_DOWN:

            case KeyEvent.KEYCODE_VOLUME_MUTE: { // 处理音量调节键

                // Similar code is in PhoneFallbackEventHandler in case the window

                // doesn't have one of these.  In this case, we execute it here and

                // eat the event instead, because we have mVolumeControlStreamType

                // and they don't.

                getAudioManager().handleKeyDown(event, mVolumeControlStreamType);

                return true;

            }

            case KeyEvent.KEYCODE_MENU: {

                onKeyDownPanel((featureId < 0) ? FEATURE_OPTIONS_PANEL : featureId, event);

                return true;

            }

            case KeyEvent.KEYCODE_BACK: {

                if (event.getRepeatCount() > 0) break;

                if (featureId < 0) break;

                // Currently don't do anything with long press.

                if (dispatcher != null) {

                    dispatcher.startTracking(event, this);

                }

                return true;

            }

        }

        return false;

    }

/**

     * A key was released and not handled by anything else in the window.

     *

     * @see #onKeyDown

     * @see android.view.KeyEvent

     */

    protected boolean onKeyUp(int featureId, int keyCode, KeyEvent event) {

        final KeyEvent.DispatcherState dispatcher =

                mDecor != null ? mDecor.getKeyDispatcherState() : null;

        if (dispatcher != null) {

            dispatcher.handleUpEvent(event);

        }

        //Log.i(TAG, "Key up: repeat=" + event.getRepeatCount()

        //        + " flags=0x" + Integer.toHexString(event.getFlags()));

       

        switch (keyCode) {

            case KeyEvent.KEYCODE_VOLUME_UP:

            case KeyEvent.KEYCODE_VOLUME_DOWN:

            case KeyEvent.KEYCODE_VOLUME_MUTE: {

                // Similar code is in PhoneFallbackEventHandler in case the window

                // doesn't have one of these.  In this case, we execute it here and

                // eat the event instead, because we have mVolumeControlStreamType

                // and they don't.

                getAudioManager().handleKeyUp(event, mVolumeControlStreamType);

                return true;

            }

            case KeyEvent.KEYCODE_MENU: {

                onKeyUpPanel(featureId < 0 ? FEATURE_OPTIONS_PANEL : featureId,

                        event);

                return true;

            }

            case KeyEvent.KEYCODE_BACK: {

                if (featureId < 0) break;

                if (event.isTracking() && !event.isCanceled()) {

                    if (featureId == FEATURE_OPTIONS_PANEL) {

                        PanelFeatureState st = getPanelState(featureId, false);

                        if (st != null && st.isInExpandedMode) {

                            // If the user is in an expanded menu and hits back, it

                            // should go back to the icon menu

                            reopenMenu(true);

                            return true;

                        }

                    }

                    closePanel(featureId);

                    return true;

                }

                break;

            }

            case KeyEvent.KEYCODE_SEARCH: {

                /*

                 * Do this in onKeyUp since the Search key is also used for

                 * chording quick launch shortcuts.

                 */

                if (getKeyguardManager().inKeyguardRestrictedInputMode()) {

                    break;

                }

                if (event.isTracking() && !event.isCanceled()) {

                    launchDefaultSearch();

                }

                return true;

            }

        }

        return false;

    }

  至此所有按键事件的处理就分析完毕了,鉴于篇幅略长,我们最后稍微总结下。主要有这么几点:

1. View的各种KeyEvent.Callback接口早于Activity的对应接口被调用;

2. 整个处理环节中只要有一处表明处理掉了,则处理结束,不在往下传递;

3. 各种Callback接口的处理优先级低于监听器,也就是说各种onXXXListener的方法优先被调用。

文章来自: 程序员俱乐部(www.cxyclub.cn) 详文参考:http://www.cxyclub.cn/n/48237/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: