简介

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

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

整体图

类图

2635d86aa6f75c87167f597d81646007.png

WindowInputEventReceiver: 在ViewRootImpl.setView中被初始化,当事件到来时会从native中回调onInputEvent方法到java层。是事件派发的动力所在。

ViewRootImpl: 处理视图方面的,同Input, Window等服务交互的大管家,会在Activity显示视图时初始化

InputStage: 被抽象成责任链模式的父类,代表着事件处理的阶段

View, ViewGroup, DecorView: 视图树的主要组成成分

时序图

f80e17e8bc06a26e72c189d8cc97cc25.png

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相关的视图初始化、绘制相关分析,可以看我之前的文章。该代码总结后可以得到下面的图:

e0f0ffae69133a061a83933ee9f93b31.png

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

48fb2026f08883c86610d538db47dfdc.png

特意用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);

}

这里需要注意的是:

事件派发将会先被Activity的dispatchTouchEvent接收到

当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

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐