[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 的事件分发机制就简单记录到这里了。

相关推荐
vocal18 分钟前
我的安卓第一课:四大组件之一Service
android
懋学的前端攻城狮2 小时前
Vue源码解析-01:从创建到挂载的完整流程
前端·vue.js·源码
用户2018792831675 小时前
如何利用AI工具快速学习Android源码
android
工业互联网专业5 小时前
基于django+vue的健身房管理系统-vue
vue.js·python·django·毕业设计·源码·课程设计·健身房管理系统
音视频牛哥6 小时前
Android 平台RTSP/RTMP播放器SDK接入说明
android·音视频·大牛直播sdk·rtsp播放器·rtmp播放器·rtmp低延迟播放·rtmpplayer
aningxiaoxixi7 小时前
Android Framework 之 AudioDeviceBroker
android·windows·ffmpeg
~Yogi7 小时前
今日学习:工程问题(场景题)
android·学习
奔跑吧 android7 小时前
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
android·bluetooth·bt·aosp13
移动开发者1号7 小时前
Android Activity状态保存方法
android·kotlin
移动开发者1号7 小时前
Volley源码深度分析与设计亮点
android·kotlin