[Android] 事件分发机制学习简记

前言

在 Android 系统中,事件分发机制是用户与界面进行交互时,整个事件传递流程的核心环节,如触摸、点击等操作时,这些事件会通过一套严密有序的分发机制传递给相应的视图(View)进行处理。理解这一机制对于开发者来说至关重要,因为它关系到应用的响应性和用户体验。

1、事件分发的起点

View 处理事件的起点在哪呢?答案是 ViewRootImplsetView() 方法内执行的一段初始化 WindowInputEventReceiver 的代码:

Java 复制代码
mInputEventReceiver = new WindowInputEventReceiver(inputChannel, Looper.myLooper());

那么这个 WindowInputEventReceiver 是什么呢?从命名就能看出来,他是输入事件的接收者,在这里也就是指用户与屏幕交互的事件,它是 ViewRootImpl 中的一个内部类:

java 复制代码
final class WindowInputEventReceiver extends InputEventReceiver {
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);
    }

    @Override
    public void onInputEvent(InputEvent event) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
        List<InputEvent> processedEvents;
        try {
            processedEvents =
                mInputCompatProcessor.processInputEventForCompatibility(event);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        if (processedEvents != null) {
            if (processedEvents.isEmpty()) {
                // InputEvent consumed by mInputCompatProcessor
                finishInputEvent(event, true);
            } else {
                for (int i = 0; i < processedEvents.size(); i++) {
                    enqueueInputEvent(
                            processedEvents.get(i), this,
                            QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                }
            }
        } else {
            enqueueInputEvent(event, this, 0, true);
        }
    }

    ......
    ......
}

这个类就是用来处理接收到的事件的,类中还有其他方法,如 onFocusEvent()onDragEvent() 等,处理的事件种类从方法名就很容易看出。

2、事件接收的流程

从上文我们知道了事件是从 WindowInputEventReceiver 中的 onInputEvent() 方法开始的,随后进入判断,若事件不为空,则调用 enqueueInputEvent() 方法:

Java 复制代码
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    ......
    ......

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

由于传入的 processImmediately 参数为 true,则会进入 doProcessInputEvents() 方法:

Java 复制代码
void doProcessInputEvents() {
    // Deliver all pending input events in the queue.
    while (mPendingInputEventHead != null) {
        
        ......
        ......

        deliverInputEvent(q);
    }

    ......
    ......
}

doProcessInputEvents() 方法中又调用到 deliverInputEvent() 方法:

Java 复制代码
private void deliverInputEvent(QueuedInputEvent q) {
    
    ......
    ......
    
    try {
        ......

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

        ......

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

在这里调用了 stage.deliver() 方法:

Java 复制代码
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);
    }
}

然后就调用到了 onProcess() 方法,但是我们点进 onProcess() 方法中发现直接返回了一个标志位:

Java 复制代码
protected int onProcess(QueuedInputEvent q) {
    return FORWARD;
}

然后就没了?其实,关键就在于 stage.deliver() 方法中的这个 stage,在 ViewRootImpl.setView() 方法中,有一系列不同种类的 stage 的创建:

Java 复制代码
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);

而上面执行 stage.deliver() 方法的,是 ViewPostImeInputStage(mSyntheticInputStage),这个就是一个用来处理 View 事件的 stage 对象,我们进入它的 onProcess() 方法中:

Java 复制代码
@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() 方法:

Java 复制代码
private int processPointerEvent(QueuedInputEvent q) {
    
    ......
    
    handled = handled || mView.dispatchPointerEvent(event);
    
    ......
    
    return handled ? FINISH_HANDLED : FORWARD;
}

这里调用到了 mView.dispatchPointerEvent() 方法,这个 mView 对象就是 DecorView,这里的流程是 View.dispatchPointerEvent() -> DecorView.dispatchEvent(),我们看看 DecorView 的 dispatchEvent() 方法:

Java 复制代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

这个 cb 对象就是 Activity,接着执行 ActivitydispatchTouchEvent() 方法:

Java 复制代码
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

接着调用到 PhoneWindowsuperDispatchTouchEvent() 方法:

Java 复制代码
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

这里的 mDecor.superDispatchTouchEvent(event) 方法调用了 DecorViewsuperDispatchTouchEvent 方法:

Java 复制代码
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

最终调用到了 ViewGroupdispatchTouchEvent(),也就是从这里进入了控件树的事件处理流程。

事件接收整体流程: WindowInputEventReceiver.onInputEvent() -> ViewRootImpl.onInputEvent() -> ViewRootImpl.doProcessInputEvents() -> ViewRootImpl.deliverInputEvent() -> InputStage.deliver() -> ViewPostImeInputStage.onProcess() -> ViewRootImpl.processPointerEvent() -> View.dispatchPointerEvent() -> DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() -> ViewGroup.dispatchTouchEvent()

3、事件分发(dispatchTouchEvent)的解析

上面我们梳理了 View 接收到事件的流程,最终调用到 ViewGroup.dispatchTouchEvent() 方法,下面我们将分析 View 在接收到事件之后,处理的流程是什么。

View: dispatchTouchEvent()

我们知道 ViewGroup 是继承自 View 的,而 ViewGroup 和 View 处理事件的逻辑有所不同,这里提到的 View 只是指父容器中的子 View,相对于 ViewGroup,单个子 View 的 dispatchTouchEvent() 方法要简单的多,因此我们先来分析一下 View 的事件分发。

Java 复制代码
public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    if (event.isTargetAccessibilityFocus()) {
        // We don't have focus or no virtual descendant has it, do not handle the event.
        if (!isAccessibilityFocusedViewOrHost()) {
            return false;
        }
        // We have focus and got the event, then use normal event dispatch.
        event.setTargetAccessibilityFocus(false);
    }
    boolean result = false;

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

    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        // Defensive cleanup for new gesture
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // Clean up after nested scrolls if this is the end of a gesture;
    // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
    // of the gesture.
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

这里我们看到 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) 中,前三个条件在调用了 setOnTouchListener() 方法后,一般都为 true,之后就调用 li.mOnTouchListener.onTouch(this, event) 方法,并根据返回值给 result 赋值,在下面可以看到,根据 result 的值判断是否执行 onTouchEvent() 方法,也就是说,如果我们在 setOnTouchListener() 方法中返回 true,则不会执行 onTouchEvent() 方法。

同时,与 onTouchEvent() 紧密相关的,还有我们常用的一个属性:onClick,但是在我们平时开发中,onTouchListeneronClickListener 一般是不会同时设置的,否则可能会导致冲突,比如 onTouchListener 的回调执行,但是 onClickListener 的回调不执行。这是为什么呢?

因为:

在上文我们分析了:如果调用了 setOnTouchListener() 方法,并返回 true,则不会执行 onTouchEvent() 方法,而 onClickListener 的回调则是在 onTouchEvent() 方法中的 ACTION_UP 中执行的:

Java 复制代码
public boolean onTouchEvent(MotionEvent event) {
    
    ......
    ......

    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                ......
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    ......
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        ......
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            ......
                        }
                    }
                    ......
                }
                ......

            ......
            ......
            
        }

        return true;
    }

    return false;
}

执行 onClickListener 回调过程就是这里的 PerformClick(),它是一个实现了 Runnable 接口的任务,感兴趣的可以自己点进去看,这里就不展开。到这里我们就知道了为什么 onTouchListeneronClickListener 同时设置可能会导致冲突。

ViewGroup: dispatchTouchEvent()

Java 复制代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    ......
    ......
    
    if (onFilterTouchEventForSecurity(ev)) {
        ......

        // 1)
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 2)
        final boolean intercepted;
        ......
        ......
        
        if (!canceled && !intercepted) {
            ......

            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ......

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getXDispatchLocation(actionIndex);
                    final float y = ev.getYDispatchLocation(actionIndex);
                    // 3)
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    .......
                    
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        ......
                        // 4)
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            ......
                        }

                        ev.setTargetAccessibilityFocus(false);
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                ......
            }
        }

        // 5)
        if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            ......
        }

        ......
    }

    if (!handled && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
    }
    return handled;
}

我们从上往下分析,首先是在 1) 处的代码重置触摸状态,随后 2) 处根据各种参数判断是否需要拦截事件,如果拦截,则不会传递给子 View,随后在 3) 处调用 buildTouchDispatchChildList() 方法,根据 View 的 Z 坐标值给各控件排序,确保更在上层的 View 能更早接收到事件,在 4) 处(这段循环中涉及了多指操作,这里暂不介绍,只讨论单指)的 if 语句中调用 dispatchTransformedTouchEvent() 方法判断子 View 是否消费事件,点进这个方法之后,关键在于最后几句:

Java 复制代码
if (child == null) {
    handled = super.dispatchTouchEvent(transformedEvent);
} else {
    final float offsetX = mScrollX - child.mLeft;
    final float offsetY = mScrollY - child.mTop;
    transformedEvent.offsetLocation(offsetX, offsetY);
    if (! child.hasIdentityMatrix()) {
        transformedEvent.transform(child.getInverseMatrix());
    }

    handled = child.dispatchTouchEvent(transformedEvent);
}

即:如果下一层是 ViewGroup,则会重新调用到 ViewGroup 中的 dispatchTouchEvent() 方法,重新执行上面的过程,相当于递归,否则调用 View 的 dispatchTouchEvent() 方法,如果没有一个子 View 消费事件,则会运行到 5),调用 dispatchTransformedTouchEvent() 方法,且第三个参数传入 null,即判断自己是否消费,若消费则到此结束,不消费则继续向上传递,Activity、DecorView 等。

👉 对于 Android 的事件分发机制就简单记录到这里了。

相关推荐
雨白10 小时前
Android 快捷方式实战指南:静态、动态与固定快捷方式详解
android
hqk10 小时前
鸿蒙项目实战:手把手带你实现 WanAndroid 布局与交互
android·前端·harmonyos
LING10 小时前
RN容器启动优化实践
android·react native
恋猫de小郭13 小时前
Flutter 发布官方 Skills ,Flutter 在 AI 领域再添一助力
android·前端·flutter
Kapaseker18 小时前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
黄林晴18 小时前
你的 Android App 还没接 AI?Gemini API 接入全攻略
android
恋猫de小郭1 天前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
冬奇Lab1 天前
PowerManagerService(上):电源状态与WakeLock管理
android·源码阅读
BoomHe1 天前
Now in Android 架构模式全面分析
android·android jetpack
二流小码农2 天前
鸿蒙开发:上传一张参考图片便可实现页面功能
android·ios·harmonyos