按鍵事件在activity中的流程

android的事件有很多種,touch、key、mouse等。但是基本流程大概是一致的。本文將探尋activity中的事件流程,至於事件如何傳遞到activity,將另起篇幅介紹。


activity的事件入口

    public boolean dispatchKeyEvent(KeyEvent event) {
        onUserInteraction();
        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);
    }

以上代碼是activity的dispatchKeyEvent()方法,該方法就是key事件的入口。至於其它的touch、mouse等事件,也都有類似的dispatch方法作爲一類事件的入口。activity收到事件之後,會獲取當前的window,將該事件傳遞給window。如果window的superDispatchKeyEvent()方法捕獲該事件,則該事件傳遞結束。否則,將調用KeyEvent的dispatch()方法。


window的處理流程

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

Window接口的superDispatchKeyEvent()的api說明,已經明確指出,事件會通過此方法進入view層級中。其具體實現是在PhoneWindow中。

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

這個mDecor是PhoneWindow的一個內部類,直接繼承於FrameLayout。

    public boolean superDispatchKeyEvent(KeyEvent event) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }

            // Not handled by the view hierarchy, does the action bar want it
            // to cancel out of something special?
            if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
                // Back cancels action modes first.
                // ... return true;

                // Next collapse any expanded action views.
                // ... return true;
            }

            return false;
        }

DecorView會按FrameLayout的方式處理該事件,此時,事件正式傳遞到了view層級中。如果FrameLayout未捕獲該事件,並且是BACK按鍵時,DecorView會和ActionMode和ActionBar有簡單交互。否則直接返回false。


View層級的事件處理
事件在View層級中傳遞,主要流程在ViewGroup和View中。先看ViewGroup的dispatchKeyEvent:

    public boolean dispatchKeyEvent(KeyEvent event) {
        // 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;
            }
        }

        // mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);

        return false;
    }

如果ViewGroup自身已經獲得焦點,則調用View的dispatchKeyEvent();否則將查找焦點子View,將事件傳遞給焦點子View處理。

再看View的dispatchKeyEvent()

    public boolean dispatchKeyEvent(KeyEvent event) {                                   
        // 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;                                                 
        }                                                                

        // mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);   
        return false;                                                    
    } 

如果View設置了KeyListener,並且是enable狀態,則該事件先由KeyListener處理。KeyListener返回true,則事件結束。否則調用KeyEvent的dispatch()方法。這個dispatch()方法的第一個參數是KeyEvent.Callback,它有2個方法onKeyDown()和onKeyUp()。View和Activity都實現了這個接口。dispatch方法將判斷按鍵是up還是down,然後傳遞分發給callback。View在onKeyDown()和onKeyUp()中,主要處理了enter按鍵,設置和取消了press狀態,觸發click事件,並在一定條件下,觸發long press狀態。如果在Callback中返回true,則事件結束;否則,事件流程回溯到起點activity的dispatchKeyEvent()中的第二部分。即調用的KeyEvent的dispatch()方法,事件將進入activity的onKeyDown()和onKeyUp()中。這2個函數是在應用中可以接收到事件的最後機會。

事件流程總結
activity中先在dispatch函數中接收到事件,交由window處理,window則再轉給DecorView,進入view層級處理。View層級中直接將事件轉發給當前焦點view處理,而焦點view則依次轉給自己的OnKeyListener回調、onKeyDown、onKeyUp處理。如果焦點view未處理,則回到了activity的dispatch函數,進入第二步流程onKeyDown、onKeyUp處理。這中間的節點是應用開發可以控制到的。至於事件如何進入activity、ime和activity如何事件交互,且看後續文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章