Android事件分发机制前篇——事件如何传递到Activity中

网上关于Android事件分发机制的资料有许多,看过很多次,但是每次过一段时间就会忘记,感觉还是自己研究的不够深入,这一次,决定自己根据源码,来好好梳理一遍Android事件分发机制的知识,本篇文章讲的主要是一个触摸事件,如何传入到Activity中。

事件的入口

首先是有一个疑问,我们讨论事件分发,那么究竟事件的来源是哪里呢?我们知道ActivitydispatchTouchEvent(MotionEvent ev)是会接受到事件的,所以我们在该方法中调用Thread.dumpStack()来查看调用栈。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Thread.dumpStack();
        return super.dispatchTouchEvent(ev);
    }

运行程序,输出结果为:

at java.lang.Thread.dumpStack(Thread.java:490)
at com.dpal.javademo.MainActivity.dispatchTouchEvent(MainActivity.java:30)
at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68)
at com.android.internal.policy.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2364)
at android.view.View.dispatchPointerEvent(View.java:9539)
at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4281)
at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4144)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3683)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3736)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3702)
at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3828)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3710)
at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:3885)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3683)
at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3736)
at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3702)
at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3710)
at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3683)
at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5973)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5947)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5908)
at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6079)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:195)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:141)
at android.app.ActivityThread.main(ActivityThread.java:5653)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:746)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:636)

具体分析

at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6079)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:195)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:323)

我们知道Android的消息机制是Handler机制,通过将消息封装到Message中,再发送到消息所在Handler所在MessageQueue中,并且Looper不断调用MessageQueuenext()方法进行消息的处理。所以根据上面的调用信息,当我们触摸屏幕时,nativePollOnce()将会收到消息,并且将事件发送给InputEventReceiverdispatchInputEvent()方法

直接从名字我们应该能看出它的作用,输入事件的接受者。我们再来看看官方对InputEventReceiver的描述

/**
 * Provides a low-level mechanism for an application to receive input events.
 * 提供应用程序接收输入事件的低级机制。
 * @hide
 */
public abstract class InputEventReceiver {
  
   /**
     * Creates an input event receiver bound to the specified input channel.
     *  创建绑定到指定输入通道的输入事件接收器。
     * @param inputChannel The input channel.
     * @param looper The looper to use when invoking callbacks.
     */
    public InputEventReceiver(InputChannel inputChannel, Looper looper) {
        if (inputChannel == null) {
            throw new IllegalArgumentException("inputChannel must not be null");
        }
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }

        mInputChannel = inputChannel;
        mMessageQueue = looper.getQueue();
        mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);

        mCloseGuard.open("dispose");
    }

    /**
     * Called when an input event is received.
     * 当接收到输入事件时调用。
     *
     * @param event The input event that was received.
     */
    public void onInputEvent(InputEvent event) {
        finishInputEvent(event, false);
    }

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchInputEvent(int seq, InputEvent event) {
        mSeqMap.put(event.getSequenceNumber(), seq);
        onInputEvent(event);
    }

    private void dispose(boolean finalized) {
        if (mCloseGuard != null) {
            if (finalized) {
                mCloseGuard.warnIfOpen();
            }
            mCloseGuard.close();
        }

        if (mReceiverPtr != 0) {
            nativeDispose(mReceiverPtr);
            mReceiverPtr = 0;
        }
        mInputChannel = null;
        mMessageQueue = null;
    }
}

我省略了一些不关键的代码,果然,InputEventReceover提供了应用程序接受输入事件的低级机制,在它的构造器中,我们看到了一个nativeInit()方法的调用,这里系统 native 层就会将这个InputEventReceiver实例记录下来,每当有事件到达时就会通过inputChannel管道派发到这个实例上,当然还有注销的方法:dipose()。在InputEventReceiver中,我们看到dispatchInputEvent()方法注释着从native层代码调用,也就是nativePollOnce()内部会调用这个方法。

大家注意这里的InputEventReceiver是一个抽象类,再根据栈中的信息,事件将会传到ViewRootImpl$WindowInputEventReceiver.onInputEvent()中,说明nativePollOnce()其实调用的是InputEventReceiver的子类WindoInputEventReceiverdispatchInputEvent()方法,然后再调用onInputEvent()方法。

WindowInputEventReceiverViewRootimpl的一个内部类,我们来看一下他的源码:

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {

    ···

    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event) {
            enqueueInputEvent(event, this, 0, true);
        }

        @Override
        public void onBatchedInputEventPending() {
            if (mUnbufferedInputDispatch) {
                super.onBatchedInputEventPending();
            } else {
                scheduleConsumeBatchedInput();
            }
        }

        @Override
        public void dispose() {
            unscheduleConsumeBatchedInput();
            super.dispose();
        }
    }

    void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        adjustInputEventForCompatibility(event);
        //将event事件,receiver,flags包装成一个QueuedInputEvent
        //QueuedInputEvent表示一个在队列中等待处理的输入事件,这个类有个next属性可以指向下一个事件
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
        //获得等待队列的最后一个输入事件(Pending的意思是等待的,Tail的意思是尾部)
        QueuedInputEvent last = mPendingInputEventTail;
        //下面的意思就是将事件加入到队列中
        if (last == null) {
            //如果没有最后一个,就说明队列是空的,那么第一个是该事件,最后一个也是该事件
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            //如果有最后一个,那么就将该事件设置成最后一个
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        //队列数量加1
        mPendingInputEventCount += 1;
        //事件跟踪机制。。。不需要管
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);
        //如果事件需要立即处理,则执行doProcessInputEvents(),
        //WindowInputEventReceiver中enqueueInputEvent(event, this, 0, true);传入的是true
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }
    ···
}

省略了一些不关键的代码,我们看到事件通过WindowInputEventReceiveronInputEvent()方法传递到了ViewRootImplenqueueInputEvent()中。在enqueueInputEvent()中,我已经注释了每一步的作用,事件将会传递到doProcessInputEvents()方法中,我们再来看这个方法的源码:

    void doProcessInputEvents() {
        // 处理队列中所有的输入事件
        while (mPendingInputEventHead != null) {
            //下面这段代码是取出事件队列中的第一个,若有第二个,将其置为第一个
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;
            //队列数量减1
            mPendingInputEventCount -= 1;
            //跟踪事件,不需要管
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);
            
            //下面的代码是获得当前事件的发生时间,以及此事件与上一个事件间隔间隔时间
            //通过Choreographer,协调动画、输入和绘图的时间
            long eventTime = q.mEvent.getEventTimeNano();
            long oldestEventTime = eventTime;
            if (q.mEvent instanceof MotionEvent) {
                MotionEvent me = (MotionEvent)q.mEvent;
                if (me.getHistorySize() > 0) {
                    oldestEventTime = me.getHistoricalEventTimeNano(0);
                }
            }
            mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
        
            deliverInputEvent(q);
        }

        // 处理完了所有的输入事件,将处理事件等待标记设为false
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }

Ok,我们看到在doProcessInputEvents()方法中,输入事件从一个一个从队列中被取出,并传入deliverInputEvent()方法中,这一点完全和文章开头的调用栈信息相同:

at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:5973)
at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:5947)
at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:5908)

我们继续说deliverInputEvent()方法,通过方法名,这个方法的作用应该是传递输入事件的吧?究竟是不是呢?我们继续探究它的代码:

    private void deliverInputEvent(QueuedInputEvent q) {
        //跟踪机制,不用管
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
       //一致性验证,不用管,一致性验证就是比如说判断ACTION_DOWN和ACTION_UP是否成对出现
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }
        
        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

在这个方法中,得到了一个InputStage对象。它是处理输入事件的一个阶段,可以将事件完成或者转送到下一个阶段。InputStage分为多种,例如SyntheticInputStageViewPostImeInputStageNativePostImeInputStage等等,Android在这里使用了设计模式中的责任链模式,多个InputStage连成一条链,并沿着这条链传递输入事件,直到有一个InputStage处理了该输入事件。触摸事件就是由ViewPostImeInputStage处理,这一点也可以通过文章一开始的调用栈输出信息来确认,事件最终传递给了ViewPostImeInputStage中的onProcess()然后传递给processPointerEvent()方法:

我们先来看看ViewPostImeInputStage中的onProcess()方法:

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                //按键事件,比如回退键
                return processKeyEvent(q);
            } else {
                final int source = q.mEvent.getSource();
                if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                    //普通的触摸点事件
                    return processPointerEvent(q);
                } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                    //轨迹球事件,我并不清楚轨迹球是啥玩意,应该是下面那张轨迹球的图片把?
                    return processTrackballEvent(q);
                } else {
                    //滚轮事件,比如外接蓝牙鼠标时,可以触发滚轮事件
                    return processGenericMotionEvent(q);
                }
            }
        }

在onProcess()方法中,根据判断,分了四种情况处理输入事件,具体的分类我在注释中已经给出,本文我们只分析普通的触摸事件,也就是分析processPointerEvent()方法。

轨迹球

我们再来看processPointerEvent()方法的代码:

        private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            mAttachInfo.mHandlingPointerEvent = true;
           //最关键的代码!!!!
            boolean handled = mView.dispatchPointerEvent(event);
            maybeUpdatePointerIcon(event);
            maybeUpdateTooltip(event);
            mAttachInfo.mHandlingPointerEvent = false;
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

来吧,让我们看到最关键的代码,总算出现了View的踪迹,其中的mView就是我们熟悉的DecorView了。为什么mView就是DecorView呢?大家可以参考这篇文章Android View源码解读:浅谈DecorView与ViewRootImpl,我们进入DecorView中的dispatchPointerEvent()方法中。。。咦?你是否真的在DecorView中找dispatchPointerEvent()方法了?哈哈哈,是不是找不到?当然了!这个方法其实是在View类里面:

    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

好吧,很简单的几行代码,如果事件属于触摸事件,就调用dispatchTouchEvent(event)方法,如果不是,则调用dispatchGenericMotionEvent(event)方法。让我们继续跟随事件,进入DecorView中的dispatchTouchEvent(),如下:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        //mFeatureId :面板的特征ID,如果这是应用程序的DecorView就为-1,在初始化时设置
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

在这个方法中,mWindow就是与Activity关联的PhoneWindow对象,由于DecorView是PhoneWindow创建的,并且通过setWindow()方法,DecorView对象持有了PhoneWindow对象的引用。通过getCallback()方法,就获得了Window.Callback对象,Window.Callback包含了窗口的各种回调接口,Activity就实现了该接口。根据return后的判断,当调用cb.dispatchTouchEvent(ev)时,其实调用的就是Activity中的dispatchTouchEvent()方法。接下来就是从Activity出发,进一步分析事件分发机制了。

终于好了,让我们来总结一下吧

  1. 首先,当我们触摸屏幕时,通过Android消息机制,从Looper从MessageQueue中取出该事件,发送给WindowInputEventReceiver。
  2. WindowInputEventReceiver是ViewRootImpl的内部类,通过enqueueInputEvent方法,将输入事件加入输入事件队列中,并进行处理和转发。
  3. ViewPostImeInputStage收到输入事件,将事件传递给DecorView的dispatchPointerEvent()方法(是View的方法)
  4. dispatchPointerEvent()方法通过DecorView中的dispatchTouchEvent()方法,调用了Activity的dispatchTouchEvent()方法。
    到此事件进入Activity中!!!!进入Activity后,事件又是如何被处理的呢?可以查看Android事件分发机制及源码分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容