5【Android 12】输入事件在App层的分发流程(一) —— InputStage

1,238 阅读10分钟

之前在分析InputDispatcher分发的时候,知道输入事件最终从Native层传到了framework上层,到达了ViewRootImpl通过setView方法注册的WindowInputEventReceiver的onInputEvent方法。 接下来分析输入事件是如何在App层传递的。

由于字数限制,本篇笔记分3部分去分析。

1 ViewRootImpl.WindowInputEventReceiver.onInputEvent

        @Override
        public void onInputEvent(InputEvent event) {
			......
                
            List<InputEvent> processedEvents;
            try {
                processedEvents =
                    mInputCompatProcessor.processInputEventForCompatibility(event);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            if (processedEvents != null) {
				......
            } else {
                enqueueInputEvent(event, this, 0, true);
            }
        }

这里的InputEventCompatProcessor.processInputEventCompatibility方法主要是为Android M平台以下的Motion事件处理做一些兼容处理,对于Android M以上的平台直接返回null,那么后续直接调用ViewRootImpl.enqueueInputEvent。

2 ViewRootImpl.enqueueInputEvent

    @UnsupportedAppUsage
    void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

		......
            
        // Always enqueue the input event in order, regardless of its time stamp.
        // We do this because the application or the IME may inject key events
        // in response to touch events and we want to ensure that the injected keys
        // are processed in the order they were received and we cannot trust that
        // the time stamp of injected events are monotonic.
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        mPendingInputEventCount += 1;

        ......

        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

1)、通过ViewRootImpl.obtainQueuedInputEvent创建一个QueuedInputEvent对象,将QueuedInputEvent成员变量mEvent指向当前处理的输入事件。

2)、将上一步得到的QueuedInputEvent入队。类QueuedInputEvent实现了一个等待处理的输入事件队列,ViewRootImpl的成员变量mPendingInputEventHead指向这个待处理队列的队首,mPendingInputEventTail指向队尾,QueuedInputEvent的成员变量mNext指向排在当前等待处理的事件之后的下一个事件QueuedInputEvent。

如果此时队伍中没有正在排队的事件,那么mPendingInputEventHead和mPendingInputEventTail都指向这个QueuedInputEvent对象。

如果此时队伍中有正在排队的事件,那么将队尾mPendingInputEventTail的成员变量mNext指向这个QueuedInputEvent对象,然后这个QueuedInputEvent对象变为队尾mPendingInputEventTail。

3)、在第1节调用ViewRootImpl.enqueueInputEvent的时候传入的processImmediately为true,那么调用doProcessInputEvents跳过调度直接处理。

3 ViewRootImpl.doProcessInputEvents

    void doProcessInputEvents() {
        // Deliver all pending input events in the queue.
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;

            ......

            deliverInputEvent(q);
        }

        // We are done processing all input events that we can process right now
        // so we can clear the pending flag immediately.
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }

1)、遍历待处理队列,对每一个QueuedInputEvent对象调用ViewRootImpl.deliverInputEvent方法进行处理。

2)、遍历结束后,我们已经处理完了所有当前我们可以处理的输入事件,因此我们可以清除掉mProcessInputEventsScheduled这个待处理标记。这个标记只有在ViewRootImpl.scheduleProcessInputEvents方法中才会被置为true,而上一步我们是检测到参数processImmediately为true直接调用了当前的ViewRootImpl.doProcessInputEvents方法,没有走ViewRootImpl.scheduleProcessInputEvents。

重点分析对每一个QueuedInputEvent对象都调用的deliverInputEvent方法。

4 ViewRootImpl.deliverInputEvent

    private void deliverInputEvent(QueuedInputEvent q) {
		......
        try {
            if (mInputEventConsistencyVerifier != null) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
                try {
                    mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }
            
			......

            if (stage != null) {
                ......
                stage.deliver(q);
            } else {
                finishInputEvent(q);
            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

整个方法需要分成几部分来看。

4.1 InputEventConsistencyVerifier

            if (mInputEventConsistencyVerifier != null) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
                try {
                    mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
            }

InputEventConsistencyVerifier用来判断属于同一系列的输入事件的一致性,并且收集每一个检测出的错误,同时避免相同错误重复收集。

4.2 第一个InputStage的选取

            InputStage stage;
            if (q.shouldSendToSynthesizer()) {
                stage = mSyntheticInputStage;
            } else {
                stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
            }

这里先不去分析InputStage是用来干啥的,先看下这里创建的局部变量stage最终指向了哪个InputStage。

这里QuquedInputEvent的两个方法都检测了当前QueuedInputEvent的flag:

        public boolean shouldSkipIme() {
            if ((mFlags & FLAG_DELIVER_POST_IME) != 0) {
                return true;
            }
            return mEvent instanceof MotionEvent
                    && (mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
                        || mEvent.isFromSource(InputDevice.SOURCE_ROTARY_ENCODER));
        }

        public boolean shouldSendToSynthesizer() {
            if ((mFlags & FLAG_UNHANDLED) != 0) {
                return true;
            }

            return false;
        }

回顾在ViewRootImpl.enqueueInputEvent方法中创建QueuedInputEvent的时候,传入的lfags参数是0,因此这里对于所有flag的判断都会返回false。那么ViewRootImpl.shouldSendToSynthesizer方法返回false,而ViewRootImpl.shouldSkipIme方法会进一步判断当前是否是MotionEvent以及MotionEvent的输入源。

InputDevice在InputReader调用processEventsLocked函数处理从EventHub读取的原始数据的时候也遇到过,InputDevice描述了一个特定的输入设备的功能。

SOURCE_CLASS_POINTER代表了事件类型是触摸点,这些事件的输入源包括触摸屏、鼠标和手写笔:

    /**
     * The input source is a touch screen pointing device.
     *
     * @see #SOURCE_CLASS_POINTER
     */
    public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER;

    /**
     * The input source is a mouse pointing device.
     * This code is also used for other mouse-like pointing devices such as trackpads
     * and trackpoints.
     *
     * @see #SOURCE_CLASS_POINTER
     */
    public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER;

    /**
     * The input source is a stylus pointing device.
     * <p>
     * Note that this bit merely indicates that an input device is capable of obtaining
     * input from a stylus.  To determine whether a given touch event was produced
     * by a stylus, examine the tool type returned by {@link MotionEvent#getToolType(int)}
     * for each individual pointer.
     * </p><p>
     * A single touch event may multiple pointers with different tool types,
     * such as an event that has one pointer with tool type
     * {@link MotionEvent#TOOL_TYPE_FINGER} and another pointer with tool type
     * {@link MotionEvent#TOOL_TYPE_STYLUS}.  So it is important to examine
     * the tool type of each pointer, regardless of the source reported
     * by {@link MotionEvent#getSource()}.
     * </p>
     *
     * @see #SOURCE_CLASS_POINTER
     */
    public static final int SOURCE_STYLUS = 0x00004000 | SOURCE_CLASS_POINTER;

SOURCE_ROTARY_ENCODER代表了旋转编码设备,遇到的比较少:

    /**
     * The input source is a rotating encoder device whose motions should be interpreted as akin to
     * those of a scroll wheel.
     *
     * @see #SOURCE_CLASS_NONE
     */
    public static final int SOURCE_ROTARY_ENCODER = 0x00400000 | SOURCE_CLASS_NONE;

那么总结一下,如果当前输入事件是Motion类型且输入源是触摸点相关类型,或者输入源是旋转解码器类型,那么第一个InputStage选择mFirstPostImeInputStage,否则选择mFirstInputStage。

4.3 InputStage介绍

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

根据上一小节的分析,那么这里的stage可能是mFirstPostImeInputStage或mFirstInputStage。

再进一步分析前,需要看一下InputStage是干嘛的。

    /**
     * Base class for implementing a stage in the chain of responsibility
     * for processing input events.
     * <p>
     * Events are delivered to the stage by the {@link #deliver} method.  The stage
     * then has the choice of finishing the event or forwarding it to the next stage.
     * </p>
     */
    abstract class InputStage {
        private final InputStage mNext;

        protected static final int FORWARD = 0;
        protected static final int FINISH_HANDLED = 1;
        protected static final int FINISH_NOT_HANDLED = 2;

        private String mTracePrefix;

        /**
         * Creates an input stage.
         * @param next The next stage to which events should be forwarded.
         */
        public InputStage(InputStage next) {
            mNext = next;
        }

        /**
         * Delivers an event to be processed.
         */
        public final void deliver(QueuedInputEvent q) {
            if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
                forward(q);
            } else if (shouldDropInputEvent(q)) {
                finish(q, false);
            } else {
                traceEvent(q, Trace.TRACE_TAG_VIEW);
                final int result;
                try {
                    result = onProcess(q);
                } finally {
                    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
                }
                apply(q, result);
            }
        }

        /**
         * Marks the the input event as finished then forwards it to the next stage.
         */
        protected void finish(QueuedInputEvent q, boolean handled) {
            q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
            if (handled) {
                q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
            }
            forward(q);
        }

        /**
         * Forwards the event to the next stage.
         */
        protected void forward(QueuedInputEvent q) {
            onDeliverToNext(q);
        }

        /**
         * Applies a result code from {@link #onProcess} to the specified event.
         */
        protected void apply(QueuedInputEvent q, int result) {
            if (result == FORWARD) {
                forward(q);
            } else if (result == FINISH_HANDLED) {
                finish(q, true);
            } else if (result == FINISH_NOT_HANDLED) {
                finish(q, false);
            } else {
                throw new IllegalArgumentException("Invalid result: " + result);
            }
        }

        /**
         * Called when an event is ready to be processed.
         * @return A result code indicating how the event was handled.
         */
        protected int onProcess(QueuedInputEvent q) {
            return FORWARD;
        }

        /**
         * Called when an event is being delivered to the next stage.
         */
        protected void onDeliverToNext(QueuedInputEvent q) {
            if (DEBUG_INPUT_STAGES) {
                Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
            }
            if (mNext != null) {
                mNext.deliver(q);
            } else {
                finishInputEvent(q);
            }
        }

        protected void onWindowFocusChanged(boolean hasWindowFocus) {
            if (mNext != null) {
                mNext.onWindowFocusChanged(hasWindowFocus);
            }
        }

		......
    }

InputStage是用于实现处理输入事件的责任链中的一个阶段的基类。事件通过InputStage.deliver方法发送给stage,接下来stage有权决定结束掉这个事件或者把它转交给下一个stage。

根据InputStage的提供的方法接口,可以总结出输入事件在InputStage链中传递的一般规律:

1)、上一个InputStage调用InputStage.onDeliverToNext -> InputStage.deliver将输入事件发送到当前InputStage。

2.1)、如果输入事件被标记了QueuedInputEvent.FLAG_FINISHED,那么调用InputStage.forward继续将事件向下一个InputStage分发,当前InputStage不做处理。

2.2)、如果输入事件经过InputStage.shouldDropInputEvent判断应该被丢弃,那么调用InputStage.finish为输入事件添加QueuedInputEvent.FLAG_FINISHED标记,InputStage.finish中又调用InputStage.forward把事件分发给下一个InputStage。

2.3)、如果2.1和2.2的判断条件不满足,说明本次事件需要当前InputStage进行处理,那么调用InputStage.onProcess对事件进行处理,这是每一个InputStage处理事件的核心部分。

3)、根据InputStage.onProcess的处理结果,调用InputStage.apply方法判断是将事件在当前InputStage结束掉,还是调用InputStage.forward继续将事件分发给下一个InputStage。

IputStage.png

4.3.1 InputStage责任链

    abstract class InputStage {
        private final InputStage mNext;
		......

        /**
         * Creates an input stage.
         * @param next The next stage to which events should be forwarded.
         */
        public InputStage(InputStage next) {
            mNext = next;
        }

		......
    }

首先能看到InputStage有一个InputStage类型的mNext成员变量,在InputStage创建的时候传入,指向当前InputStage的下一个InputStage,以此构成一个链表形式。

所有InputStage创建的地方在ViewRootImpl.setView中,也就是在从system_server进程返回客户端InputChannel之后。

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
			   ......	

                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
            }
        }
    }

因此InputStage责任链的处理顺序是:

InputStage责任链.png

根据4.2的分析可知,MotionEvent是直接从EarlyPostImeInputStage这一阶段开始分发的。

从名字可以清晰的将InputStage分为三个阶段:IME处理前阶段,IME处理阶段,IME处理后阶段。

4.3.2 InputStage各阶段介绍

对这7个InputStage进行简介。

4.3.2.1 IME处理前阶段 - NativePreImeInputStage

    /**
     * Delivers pre-ime input events to a native activity.
     * Does not support pointer events.
     */
    final class NativePreImeInputStage extends AsyncInputStage
            implements InputQueue.FinishedInputEventCallback {
            ......
     }

在IME处理之前,将输入事件发送给一个native Activity。不支持点触事件。

在此阶段只支持将KeyEvent类型的时间提前发送给native Activity,而且是异步进行的,也就是说,这里对输入事件的处理可能会推迟。

4.3.2.2 IME处理前阶段 - ViewPreImeInputStage

    /**
     * Delivers pre-ime input events to the view hierarchy.
     * Does not support pointer events.
     */
    final class ViewPreImeInputStage extends InputStage {
   	 	......
    }

在IME处理之间,将输入事件发送给View层级结构。不支持点触事件。

主要是针对KeyEvent类型的事件,在将KeyEvent发送给IME并由IME消费掉之前,使用View.dispatchKeyEventPreIme和View.onKeyPreIme尝试进行一次拦截,主要是为了一些特殊情况,比如当BACK按键事件分发的时候,我们更希望View层级结构能够处理这个BACK按键事件来更新App的UI,而不是让IME接收到BACK键然后关闭掉IME窗口。

4.3.2.3 IME处理阶段 - ImeInputStage

    /**
     * Delivers input events to the ime.
     * Does not support pointer events.
     */
    final class ImeInputStage extends AsyncInputStage
            implements InputMethodManager.FinishedInputEventCallback {
   		......
    }

将输入事件分发给输入法。不支持点触事件。

调用InputMethodManager.dispatchInputEvent对输入事件进行分发,之前处理过功能机按键的相关问题,比如向编辑框中输入信息的时候,数字按键事件KEYCODE_0、KEYCODE_1等会在这个阶段分发给InputMethodManager处理,不会发送给后续的InputStage。。

4.3.2.4 IME处理后阶段 - EarlyPostImeInputStage

    /**
     * Performs early processing of post-ime input events.
     */
    final class EarlyPostImeInputStage extends InputStage {
        ......
    }

在IME处理之后,对输入事件进行早期处理。

主要是为了在输入事件进一步发送之前,判断当前输入事件是否会影响touch mode的进入和退出,以及,让AudioManager能够提前接收到输入事件等。

4.3.2.5 IME处理后阶段 - NativePostImeInputStage

    /**
     * Delivers post-ime input events to a native activity.
     */
    final class NativePostImeInputStage extends AsyncInputStage
            implements InputQueue.FinishedInputEventCallback {
        ......
    }
            

在IME处理阶段之后,将输入事件发送给native Activity。

这一步和4.3.2.1中NativePreImeInputStage的处理很像,不同的地方在于,NativePreImeInputStage只允许发送KeyEvent事件,而到了NativePostImeInputStage这里就不再限制输入事件的类型。在此阶段,native Activity仍然能比Java层的Activity进一步处理输入事件。

4.3.2.6 IME处理后阶段 - ViewPostImeInputStage

    /**
     * Delivers post-ime input events to the view hierarchy.
     */
    final class ViewPostImeInputStage extends InputStage {
        ......
    }

在IME处理阶段之后,将输入事件发送给View层级结构。

此阶段是输入事件发送给Activity和View的地方,后面重点分析。

4.3.2.7 IME处理后阶段 - SyntheticInputStage

    /**
     * Performs synthesis of new input events from unhandled input events.
     */
    final class SyntheticInputStage extends InputStage {
        ......
    }

从未处理的输入事件中合成新的输入事件。

此阶段是责任链的最后一个阶段,主要用来处理前几个阶段无法处理的输入事件类型,如轨迹球,游戏摇杆,导航面板等。

5 ViewRootImpl.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);
                }
            }
        }

根据输入事件类型进行相应处理。

我们的重点分析处理处理点触事件类型的processPointerEvent和KeyEvent类型事件的processKeyEvent方法。