android窗体加载过程剖析之事件的分发机制

上面讲了注册,下面将讲述当发生事件时消息的分发机制来讲述。

底层的事件传递不管,只研究在安卓代码里的传递。上文注册了InputChannel,里面有传入一个inputHandler作为参数。InputHandler就是一个接口,接口里就定义了handleKey和handleMotion两个方法。方法体是在ViewRootImpl里定义的,代码如下:

源码路径:frameworks\base\core\java\android\view\ViewRootImpl.java
源码中国链接:http://www.oschina.net/code/explore/android-2.2-froyo/android/view/ViewRoot.java
    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这个函数
    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);

        sendMessageAtTime(msg, event.getEventTime());
    }

主要是包装并发送了一个关于这个事件的消息。由于ViewRootImpl是一个Handler,那么sendMessageAtTime其实是Handler的一个方法。根据我们的经验,接下来就是

被调用了。这个方法处理了很多种消息,此处只截出跟DISPATCH_KEY这个消息有关的部分:

    public void handleMessage(Message msg) {
        switch (msg.what) {
        case DISPATCH_KEY:
            deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0);
            break;
        }
    }
再来看看deliverKeyEvent
    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);
    }

可以看到是先调用mView.dispatchKeyEventPreIme(event)函数,再判断输入法是否打开,如果有的话会先将事件传入到输入法里,否则往下调用deliverKeyEventPostIme函数 

这里的mView,就是通过setView函数传递进来的参数view, 也即窗体的最外层View。对于应用窗口涞说实际上就是PhoneWindow.DecorView。DecorView没有重写dispatchKeyEventPreIme函数,实际上调用的是ViewGroup的:

ViewGroup

    @Override
    public boolean dispatchKeyEventPreIme(KeyEvent event) {
        if ((mPrivateFlags & (FOCUSED | HAS_BOUNDS)) == (FOCUSED | HAS_BOUNDS)) {
            return super.dispatchKeyEventPreIme(event);
        } else if (mFocused != null && (mFocused.mPrivateFlags & HAS_BOUNDS) == HAS_BOUNDS) {
            return mFocused.dispatchKeyEventPreIme(event);
        }
        return false;
    }

如果我们需要在键盘弹出时拦截一些事件,比如back事件。就需要重写窗体的根布局并重写dispatchKeyEventPreIme函数。具体在

android下当键盘弹出时拦截Back事件

这篇文章里有讲述。

最后来看看deliverKeyEventPostIme做了什么事情(发现这个函数好长,我就只贴重点的吧):

        if (mView.dispatchKeyEvent(event)) {
            finishKeyEvent(event, sendDone, true);
            return;
        }
这里的mView对于应用窗口涞说就是PhoneWindow.DecorView。我们来看看DecorView中dispatchKeyEvent的定义:

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);
        }
这里主要做了几件事情:
1、处理系统快捷键
2、调用View中Callback对象的dispatchKey Event(),即调用Activity的dispatchKeyEvent()

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

这里就将事件通知到了Activity了。再往下就是Activity的dispatchKeyEvent和onKeyDown函数被调用。


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值