从源码角度看 Android 触摸事件分发

简介

在之前的文章中,我分析了Input点击事件从底层到JAVA层的传输过程,在分析到事件传输到InputEventReceiver时便没有继续分析。这一篇文章将会从onInputEvent方法为起点,系统性的分析Android对触摸事件是如何派发给控件树的。

文章的开头照旧给出整体类图、时序图方便掌握整体与回忆。之后会详细探讨InputStage的责任链模式涉及、dispatchTouchEvent派发过程。文章的最后会给出一张事件派发流转图作整体回顾。

整体图

类图

  • WindowInputEventReceiver: 在ViewRootImpl.setView中被初始化,当事件到来时会从native中回调onInputEvent方法到java层。是事件派发的动力所在。
  • ViewRootImpl: 处理视图方面的,同Input, Window等服务交互的大管家,会在Activity显示视图时初始化
  • InputStage: 被抽象成责任链模式的父类,代表着事件处理的阶段
  • View, ViewGroup, DecorView: 视图树的主要组成成分

时序图

InputStage 责任链模式

设计模式中的责任链模式充分发挥了OOP三大特性"封装"、"继承"、"多态",巧妙的使用了继承与关联的对象关系,将类的单一职责进行封装并将各个职责对象进行关联,实现了一个从头到尾的职责链,链上的节点如果不能够处理任务将会交给下一个节点进行处理。

frameworks/base/core/java/android/view/ViewRootImpl#setView

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;

这一责任链又可以被形象的称为输入事件处理的 pipeline,Android 是在APP要显示视图的过程中将它进行初始化的,关于ViewRootImpl相关的视图初始化、绘制相关分析,可以看我之前的文章。该代码总结后可以得到下面的图:

我们这里跟踪的触摸流程将会途径EarlyPostImeInputStage->NativePostImeInputStage->ViewPostImeInputStage三条流水线,其中ViewPostImeInputStage的onProcess将会处理按键、触摸事件。

特意用AS调试抓了一张input事件处理的调用栈,可以看到责任链模式刚好经过两个deliver->apply->forward->onDliverToNext的阶段,并且最终进入了ViewPostImeInputStage的onProcess方法,开始对View的事件进行派发。

这里总结一下 InputStage 责任链核心方法的作用:

方法名 作用
deliver 首先会根据事件状态来选择是否需要直接跳过与结束。一般调用apply进行事件处理
onProcess 进行事件的处理,根据处理状态返回状态值
apply 根据onProcess返回的状态值来决定是继续处理事件还是结束处理,如果事件已经被处理,那么将会进行结束阶段
forward 当前Stage不能处理该事件,将会调用onDeliverToNext处理
onDeliverToNext 调用下一个Stage进行事件处理
finish 结束事件处理,调用此方法后,之后的阶段将会在deliver时直接调用forward

ViewPostImeInputStage

input stage 事件处理pipeline各个stage的作用我就不分析了,这边直接入正题,触摸相关的事件会在ViewPostImeInputStage中进行处理

processPointerEvent

frameworks/base/core/java/android/view/ViewRootImpl.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); // 处理触摸事件
        }
        ...
    }
}
private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    mAttachInfo.mUnbufferedDispatchRequested = false;
    // 获取到事件传递对象 mView
    final View eventTarget =
            (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
                    mCapturingView : mView;
    mAttachInfo.mHandlingPointerEvent = true;
    // 调用方法进行事件派发
    boolean handled = eventTarget.dispatchPointerEvent(event);
    ...
    // 如果事件被处理,则结束input stage pipeline,如果否,则继续派发到下一stage
    return handled ? FINISH_HANDLED : FORWARD;
}

dispatchPointerEvent为整个视图树派发的入口,其中的View是调用addView时传给ViewRootImpl的。这块的流程我在之前的Activity视图显示分析中提到过,mView实际上是DecorView的实例

frameworks/base/core/java/android/view/View.java

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event); // 随后会分发到ViewGroup或View的对应方法
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

DecorView并没有覆写dispatchPointerEvent方法,直接调用的父类View的实现。这里随后会调用DecorView的dispatchTouchEvent方法中

frameworks/base/core/java/com/android/internal/policy/DecorView.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);
}

这里的Callback实例实际上是由Activity进行实现的,也就是说DecorView的该方法会随后调用Activity的dispatch方法

Activity.dispatchTouchEvent

frameworks/base/core/java/android/app/Activity.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    // 处理Activity相关的逻辑后,直接调用PhoneWindow的方法进行事件派发
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // 如果事件没有被处理,则调用onTouchEvent方法
    return onTouchEvent(ev);
}

这里需要注意的是:

  1. 事件派发将会先被Activity的dispatchTouchEvent接收到
  2. 当Activity的DecorView下的所有子View都不能够处理事件时,Activity的onTouchEvent才会被调用

frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
     // 又重新调用了DecorView的派发方法
    return mDecor.superDispatchTouchEvent(event);
}

因为DecorView是ViewGroup的子类,所以接下来就到了事件派发的核心方法dispatchTouchEvent

ViewGroup.dispatchTouchEvent

```java
@Override

top Created with Sketch.