Android中按键消息分发机制 下

前面一篇文章我对消息分发机制做了一个开头,如果大家还没有阅读,可以先去阅读一下:

Android中按键消息分发机制1。上一篇在结尾说明了如果WMS没有处理掉该消息,那么这个消息是会传到客户端窗口的,那么客户端窗口时在哪里接受这个消息的呢?我先告诉你是在MessageQueue中的next方法获取的,我们先停在这里回忆一下吧。

我在前一篇的开始对Android2.2中消息分发机制做了简要说明,在2.2中,这些消息都是在java层处理的,并将获得的按键消息放在MessageQueue中,但是2.3以后这些都是在native层处理的,难道还是放在MessageQueue中的?当然不是,所以导致从2.3以后,MessageQueue中的next函数发生了改变,你们可以找来2.2和2.3以后的代码做比较,一个很明显的变化就是在2.3的代码中会调用一个nativePollOnce函数,我们来好好分析一下这个MessageQueue吧,首先看构造函数:


MessageQueue() {
        nativeInit();
}

构造函数非常简单,就是调用了nativeInit()函数,进入该函数瞧瞧:

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (! nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return;
    }
    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}

这段代码主要是创建了一个NativeMessageQueue对象,并将它保存在了MessageQueue中的mptr变量中,这是jni技术中用的非常多的技术。

然后进入到MessageQueue的next函数看看:

final Message next() {
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;

        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(mPtr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                final Message msg = mMessages;
                if (msg != null) {
                    final long when = msg.when;
                    if (now >= when) {
                        mBlocked = false;
                        mMessages = msg.next;
                        msg.next = null;
                        if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                        return msg;
                    } else {
                        nextPollTimeoutMillis = (i

这里较之前版本的代码,最大的改变就是多了一个nativePollOnce函数,我们进入这个函数看看:

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jint ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(timeoutMillis);
}

主要就是调用了nativeMessageQueue的pollOnce函数,进入看看:

void NativeMessageQueue::pollOnce(int timeoutMillis) {
    mLooper->pollOnce(timeoutMillis);
}

这个里面就出现了一个非常重要的变量:mLooper,这个mLooper和我们之前在java中的Looper不是一回事,它通过pollOnce从管道中读取消息,如果没有读取到就会发生阻塞。如果读取到了消息就会返回,然后经过辗转调用,就会调用到ViewRoot中的mInputHandler中的handKey函数(过程比较复杂,在注册客户端inputChannel的使用传入了一个InputHandler接口,当读取到消息后,就回调其中的方法)。

写到这里我先总结一下MessageQueue中的next方法:

UI线程自启动之后,就会通过Looper(java层的)不断从MessageQueue中获取消息,首先是获取native层的用户消息,如果没有获取到,就阻塞,如果获取到,就会调用ViewRoot中的mInputHandler中的handKey方法,此时你可能有疑问,如果在本地层阻塞了,那么当我们使用handler往MessageQueue中放消息,岂不是执行不了,放心,如果你往MessageQueue中存放消息,此时会唤醒该线程的,不信你可以看看enqueueMessage这个方法的最下面:

if (needWake) {
            nativeWake(mPtr);
        }

也就是说会将线程从native层唤醒。唤醒之后就会从MessageQueue中取出消息并调用Handler的handlerMessage函数。



下面我们继续分析一下InputHandler中的handKey是怎么处理消息的:贴出源代码:

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

在该函数中调用的是ViewRoot中的dispatchKey函数,进过辗转的调用,最终会调用到mView.dispatchKeyEvent。(中间过程很简单,如果你有比明白的地方,可以留言)。这个mView是什么,如果你不明白可以我阅读我的另外一篇文章《Android中窗口的创建过程》。对于应用程序窗口这个mView就是PhoneWindow.DecorView,对于非应用程序窗口则是一个ViewGroup的一个实现,那我们就以应用程序窗口为例吧。

进入DecorView的dispatchKeyEvent方法:

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

首先调用getCallback函数,这个函数返回的就是Activity,不明白的可以阅读我的另外一篇文章《Android的窗口创建过程》,那么cb肯定不会是空的,所以就会调用cb.dispatchKeyEvent,如果Activity处理了这个消息,那么直接返回true,如果没有处理,那么会调用PhoneWindw的onkeyDown和onKeyUp函数。

进入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);
}

首先调用PhoneWindow的superDispatchKeyEvent,这个函数的实现如下:

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

所以最终是调用的ViewGroup的dispatchKeyEvent,进入ViewGroup看看。

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

其中如果ViewGroup本身有焦点,那么直接调用父类的dispatchKeyEvent,不然就是调用孩子中的有焦点的那个View的dispatchKeyEvent。我们先回退一下吧,退到Activity中的dispatchKeyEvent中,也就是说如果ViewGroup处理了这个消息,那么就直接返回true,不然调用event.dispatch函数。我们进入到这个函数看看吧

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等方法,此时这里的receiver就是Activity的。我们继续回退,到DecorView中的dispatchKeyEvent中。可以看到如果Activity也没有处理这个消息,那么最终就是调用PhoneWinow中的onKeyUp和onKeyDown函数。

 

我们继续分析一下View中的dispatchKeyEvent这个过程就算完了

public boolean dispatchKeyEvent(KeyEvent event) {
        // If any attached key listener a first crack at the event.
        //noinspection SimplifiableIfStatement

        if (android.util.Config.LOGV) {
            captureViewInfo("captureViewKeyEvent", this);
        }

        if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                && mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
            return true;
        }

        return event.dispatch(this, mAttachInfo != null
                ? mAttachInfo.mKeyDispatchState : null, this);
}

在该函数中会先检查mOnKeyListener是否为空,如果不为空,就会调用mOnKeyListener中的onKey方法,如果onKey方法处理了这个消息,直接返回true,如果没有处理,则调用event.dispatch方法,我们上面已经分析了,这个方法里面就是调用receiver的onKeyDown和onKeyUp等方法。

 

分析过程差不多完了,可能过程有点凌乱,没办法,这个过程实在有点复杂,如果大家有哪里看不懂,可以给我留言。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值