1. 程式人生 > >Android按鍵事件處理流程 -- 從事件被派發到View層次結構的根節點DecorView開始分析

Android按鍵事件處理流程 -- 從事件被派發到View層次結構的根節點DecorView開始分析

http://www.2cto.com/kf/201406/311432.html

http://www.cxyclub.cn/n/48237/

        剛接觸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();
        /// 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
            
           

相關推薦

Android按鍵事件處理流程 -- 事件派發View層次結構節點DecorView開始分析

http://www.2cto.com/kf/201406/311432.html http://www.cxyclub.cn/n/48237/         剛接觸Android開發的時候,對touch、key事件的處理總是一知半解,一會是Activity裡的方法,一會

Android按鍵事件處理流程

 剛接觸Android開發的時候,對touch、key事件的處理總是一知半解,一會是Activity裡的方法,一會是各種View 中的,自己始終不清楚到底哪個在先哪個在後,總之對整個處理流程沒能很好的把握。每次寫這部分程式碼的時候都有些心虛, 因為我不是很清楚什麼時候、

Android Activity的按鍵事件處理流程

       Android裡,Activity按鍵事件相關的分發/處理函式有如下幾個:        1) public boolean dispatchKeyEvent(KeyEvent event);       2)public boolean onKeyDown

Android熱插拔事件處理流程--Vold

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Android 4.2 Input Event事件處理流程---應用註冊

一個應用要接受Android的各種input訊息,就需要將自己註冊進去,這樣底層收到訊息後才後將訊息發給應用,應用註冊要接受訊息是在setView中觸發的。看下這個流程: setView由WindowManagerGlobal呼叫,setView是啟動Activity過

藍芽耳機按鍵Android側的處理流程

    目前大多數音訊視訊裝置採用紅外遙控器,由於距離、角度、障礙物等的影響,紅外遙控器的應用受到了很大限制。藍芽無線通訊技術可以實現傳統紅外遙控全部應用功能,而且客服了紅外遙控器的侷限性。藍芽音訊視訊遙控應用框架(Audio Video Remote Control P

Qt GUI 通過滑鼠事件剖析整個事件處理流程(基於Qt4.5.2原始碼windows平臺)

/*以下分析的是Windows平臺 Qt GUI程式的事件產生,分發,處理全過程(基於Qt5.4.2原始碼整理) 以一個滑鼠按壓的事件為例子講述 ...表示省略掉一些程式碼(skip code) 事件起源: 基於事件如何被產生與分發,可以把事件分為以下三類。 Spon

MQTT---HiveMQ原始碼詳解(十二)Netty-MQTT訊息、事件處理(流程)

MQTT交流群:221405150 簡介 前面這些章節,講的基本上都是屬於netty對MQTT周邊的一些處理,由於MQTT協議總共目前可用的訊息型別有14個,如果再加上對應的事件處理載入一起那就估計大概有14*3個handler,如果每

qt事件處理流程

前面的章節中我們曾經提到event()函式。事件物件建立完畢後,Qt 將這個事件物件傳遞給QObject的event()函式。event()函式並不直接處理事件,而是將這些事件物件按照它們不同的型別,分發給不同的事件處理器(event handler)。 如

Android開發知識(七):Android事件處理機制:事件分發、傳遞、攔截、處理機制的原理分析(上)

  在我們剛開始學習安卓的時候,總會一開始就接觸到Button,也就是對按鈕進行一個事件監聽的事件,當我們點選螢幕上的按鈕時就可以觸發一個點選事件。那麼,從我們點選螢幕到按鈕觸發事件這個過程,是什麼樣子的呢?本文我們就來談一下關於事件攔截處理機制的基本知識。

chromium手勢事件處理流程

 chromium手勢事件的處理,簡而言之就是browser程序呼叫系統介面捕獲並判斷事件,傳給render程序處理事件的過程。    捕獲事件的介面:ContentViewGestureHandler::onTouchEvent。在這個函式中有一些判斷這裡不再贅述

Android開發知識(八):Android事件處理機制:事件分發、傳遞、攔截、處理機制的原理分析(中)

  在本章節中,我們重點談論一下onTouch、onClick、onLongClick三個方法被回撥的過程。   在上一篇文章中,我們談到關於為View新增一個點選事件SetOnClickListener後,就可以通過回撥onClick方法來實現事件的響應

Node.js知識點整理之----基礎知識----事件處理機制及事件環機制

node 應用程序 nod eat 回調函數 clas 對象 繼續 知識 在event模塊中,定義了EventEmitter類,所有觸發事件的對象都是繼承了這個類的子類的實例對象。 addListener(event,listener) 對指定事件綁定事件處理函數 on

event.cancelBubble=true e.stopPropagation() 取消事件處理,阻止事件

<tr><a href="xxx">連線</a></tr> 如上結構,單擊tr的時候跳轉至另一頁   <tr style="cursor:pointer" onmouseover="this.style

Android 按鍵訊息處理

Android按鍵訊息處理     在android系統中,鍵盤按鍵事件是由SystemServer服務來管理的;然後在以訊息的形式分發給應用程式處理。產生鍵盤按鍵事件則是有Linux kernel的相關驅動來實現。 鍵盤訊息有別於其他型別的訊息;需要從Linux kernel drivers產生由上層ap

Android按鍵訊息處理

Android按鍵訊息處理     在android系統中,鍵盤按鍵事件是由SystemServer服務來管理的;然後在以訊息的形式分發給應用程式處理。產生鍵盤按鍵事件則是有Linux

js學習筆記:事件——事件流、事件處理程式、事件物件

Javascript與HTML之間的互動是通過事件實現的。 事件,就是文件或瀏覽器視窗中發生的一些特定的互動瞬間。 可以使用偵聽器來預定事件,以便事件發生時執行相應程式碼。 事件流 事件流描述的是從頁面中接受事件的順序。 事件冒泡 IE的事件

JS事件詳解(二) —— 事件處理程式(事件的繫結)

事件繫結方法 方法一:直接在HTML標籤中繫結 在html標籤中新增“on”+事件名稱的屬性來繫結事件 事件處理程式可直接寫在屬性值當中 <div class="demo" onclick="console.log(this)">&l

Touch事件傳遞流程事件分發中的onTouch 和onTouchEvent 有什麼區別,又該如何使用?

Touch事件傳遞流程1.Touch事件型別  Touch事件被封裝成MotionEvent,使用者當前的touch事件主要型別有:        ACTION_DOWN: 表示使用者開始觸控      ACTION_MOVE: 表示使用者在移動(手指或者其他)     

Android按鍵訊息傳播流程(WindowManagerService.java)

主要涉及的檔案有: WindowManagerService.java   frameworks\base\services\java\com\android\server\ PhoneWindow.java                     frameworks\p