Android中按键消息的派发过程及源码分析

Android中消息的整体派发过程:接收消息——消息处理前端——窗口管理系统派发消息——窗口进行消息处理

以上过程中前三步都在WmS中完成,按键消息直接发送给当前窗口,而触摸消息则根据触摸坐标位置来匹配所有窗口,并判断坐标落到哪个窗口区域中,然后把消息发送给相应的窗口。对于按键消息还会涉及到“生理长按”的检测,比如一直按住某个键,那么会产生一些列的按键消息,然而第1个和第2个消息之间往往会间隔较长的时间,这种设计是人类本身的生理特点决定的,因为从按下到弹起的过程中,如果CPU处理太快,会导致产生多次该消息,这往往不是用户所期望的,因此Android把这种消息处理延迟加入到了消息处理前端中,应用程序不需要关心第一次的延迟,只需按普通的DOWN消息处理。

下面具体分析Android中按键消息的派发流程:

每个窗口定义了一个ViewRoot(4.0中是ViewRootImpl)对象,而ViewRoot对象中定义了一个inputHandler,窗口管理系统(WmS)派发消息的过程中会调用inputHandler的handlekey(),该函数再调用ViewRoot中的dispatchKey()函数

private final InputHandler mInputHandler = new InputHandler() {
        public void handleKey(KeyEvent event, InputQueue.FinishedCallback finishedCallback) {
            startInputEvent(finishedCallback);
            dispatchKey(event, true);
        }

        public void handleMotion(MotionEvent event, InputQueue.FinishedCallback finishedCallback) {
            startInputEvent(finishedCallback);
            dispatchMotion(event, true);
        }
    };

dispatchKey()函数内部发送一个DISPATCH_KEY消息,消息的处理函数为deliverKeyEvent():

    private void dispatchKey(KeyEvent event, boolean sendDone) {
        //noinspection ConstantConditions
        if (false && event.getAction() == KeyEvent.ACTION_DOWN) {
            if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) {
                if (DBG) Log.d("keydisp", "===================================================");
                if (DBG) Log.d("keydisp", "Focused view Hierarchy is:");

                debug();

                if (DBG) Log.d("keydisp", "===================================================");
            }
        }

        Message msg = obtainMessage(DISPATCH_KEY);
        msg.obj = event;
        msg.arg1 = sendDone ? 1 : 0;

        if (LOCAL_LOGV) Log.v(
            TAG, "sending key " + event + " to " + mView);

        enqueueInputEvent(msg, event.getEventTime());
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        ...
        case FINISHED_EVENT:
            handleFinishedEvent(msg.arg1, msg.arg2 != 0);
            break;
        case DISPATCH_KEY:
            deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
            break;
        case DISPATCH_POINTER:
            deliverPointerEvent((MotionEvent) msg.obj, msg.arg1 != 0);
            break;
deliverKeyEvent()函数的执行流程如下:

1、调用mView.dispatchKeyEventPreIme(),如果有输入法存在,那么按键消息首先会被派发到输入法窗口,如果想在输入法截获消息之前处理该消息,那么可以重载该函数。

2、imm.dispatchKeyEvent()将消息派发到输入法窗口

3、调用deliverKeyEventPostIme()继而调用到mView.dispatchKeyEvent()

private void deliverKeyEvent(KeyEvent event, boolean sendDone) {
        if (ViewDebug.DEBUG_LATENCY) {
            mInputEventDeliverTimeNanos = System.nanoTime();
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 0);
        }

        // If there is no view, then the event will not be handled.
        if (mView == null || !mAdded) {
            finishKeyEvent(event, sendDone, false);
            return;
        }

        if (LOCAL_LOGV) Log.v(TAG, "Dispatching key " + event + " to " + mView);

        // Perform predispatching before the IME.
        if (mView.dispatchKeyEventPreIme(event)) {
            finishKeyEvent(event, sendDone, true);
            return;
        }

        // Dispatch to the IME before propagating down the view hierarchy.
        // The IME will eventually call back into handleFinishedEvent.
        if (mLastWasImTarget) {
            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null) {
                int seq = enqueuePendingEvent(event, sendDone);
                if (DEBUG_IMF) Log.v(TAG, "Sending key event to IME: seq="
                        + seq + " event=" + event);
                imm.dispatchKeyEvent(mView.getContext(), seq, event, mInputMethodCallback);
                return;
            }
        }

        // Not dispatching to IME, continue with post IME actions.
        deliverKeyEventPostIme(event, sendDone);
    }
private void deliverKeyEventPostIme(KeyEvent event, boolean sendDone) {
        ...

        // Deliver the key to the view hierarchy.
        if (mView.dispatchKeyEvent(event)) {
            finishKeyEvent(event, sendDone, true);
            return;
        }
        ...
}

mView对于应用窗口而言就是PhoneWindow.DecorView,否则就是普通的ViewGroup,我们只讨论DecorView中dispatchKeyEvent的实现:

1、处理系统快捷键

2、调用View中Callback对象的dispatchKeyEvent(),即调用Activity的dispatchKeyEvent()

2、如果Activity没有消耗该消息,则调用PhoneWindow的OnKeyEvent()对消息做最后的处理

        @Override
        public boolean dispatchKeyEvent(KeyEvent event) {
            final int keyCode = event.getKeyCode();
            final int action = event.getAction();
            final boolean isDown = action == KeyEvent.ACTION_DOWN;

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

            if (!isDestroyed()) {
                final Callback cb = getCallback();
                final boolean handled = cb != null && mFeatureId < 0 ? cb.dispatchKeyEvent(event)
                        : super.dispatchKeyEvent(event);
                if (handled) {
                    return true;
                }
            }

            return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
                    : PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
        }

下面来具体看下Activity中dispatchKeyEvent的执行过程,首先来看源码:

    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);
    }
主要过程如下:

1、调用onUserInteraction(),可重载该函数在消息派发前做一些处理

2、回调Activity包含的Window对象的superDispatchKeyEvent,该函数继而调用mDecor.superDispatchKveyEent,该函数继而又调用super.dispatchKeyEvent,DecorView的父类是FrameLayout,而FrameLayout未重载dispatchKeyEvent,因此最终调用ViewGroup的dispatchKeyEvent

3、如果DecorView未消耗消息,则调用event的dispatch()函数,这里的第一个参数receiver是Activity对象

@Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        return mDecor.superDispatchKeyEvent(event);
    }
         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) {
                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;
                }
            }

            return false;
        }

下面分析ViewGroup中dispatchKeyEvent的执行流程:如果ViewGroup本身拥有焦点,则调用super.dispatchKeyEvent把该消息派发到ViewGroup自身,如果其子视图拥有焦点,则调用mFocused.dispatchKeyEvent将消息派发给子视图,假如子视图也是ViewGroup,并且焦点是其子视图,则继续递归调用ViewGroup的dispatchKeyEvent

    @Override
    public boolean dispatchKeyEvent(KeyEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onKeyEvent(event, 1);
        }

        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
            if (super.dispatchKeyEvent(event)) {
                return true;
            }
        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
            if (mFocused.dispatchKeyEvent(event)) {
                return true;
            }
        }

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 1);
        }
        return false;
    }
在View类的dispatchKeyEvent中首先回调onKey()函数,应用程序可重载该函数以实现自定义消息处理,如果onKey函数未消耗该消息,则调用event的dispatch函数,在调用该函数是,第一个参数receiver是View对象本身
    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;
    }

如果拥有焦点的View没有处理该按键消息,则继续调用event.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: {
                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;
    }

该函数中主要根据相应的逻辑回调了receiver中的onKeyDown, onKeyUp,OnKeyLongPress, OnKeyMultiple函数。View中onKeyDown和onKeyUp有自己默认的处理,主要处理presse状态,长按检测,onCick回调。而OnKeyLongPress和OnKeyMultiple为空实现。对于Activity的OnKeyDown和onKeyUp函数主要实现按数字启动打电话程序( onKeyDown)以及back键的onBackPressed回调(onKeyUp)

如果按键消息在View树内部和Activity中没有被处理,就会调用到PhoneWindow的OnKeyDown和OnKeyUp函数,这是按键消息的最后处理机会,在PhoneWindow的OnKeyDown和OnKeyUp函数中主要处理了一些系统按键,例如音量键、音乐播放控制按键、照相机键、菜单键、拨号键、Search键等,具体代码就不再贴了。


以上博文主要根据《Android内核剖析》第13章以及Android源码总结而成

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值