前言
在 Android 系统中,事件分发机制是用户与界面进行交互时,整个事件传递流程的核心环节,如触摸、点击等操作时,这些事件会通过一套严密有序的分发机制传递给相应的视图(View)进行处理。理解这一机制对于开发者来说至关重要,因为它关系到应用的响应性和用户体验。
1、事件分发的起点
View 处理事件的起点在哪呢?答案是 ViewRootImpl 的 setView()
方法内执行的一段初始化 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
,接着执行 Activity
的 dispatchTouchEvent()
方法:
Java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
接着调用到 PhoneWindow
的 superDispatchTouchEvent()
方法:
Java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这里的 mDecor.superDispatchTouchEvent(event)
方法调用了 DecorView
的 superDispatchTouchEvent
方法:
Java
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
最终调用到了 ViewGroup
的 dispatchTouchEvent()
,也就是从这里进入了控件树的事件处理流程。
事件接收整体流程: 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
,但是在我们平时开发中,onTouchListener
和 onClickListener
一般是不会同时设置的,否则可能会导致冲突,比如 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
接口的任务,感兴趣的可以自己点进去看,这里就不展开。到这里我们就知道了为什么 onTouchListener
和 onClickListener
同时设置可能会导致冲突。
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 的事件分发机制就简单记录到这里了。