从源码角度看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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
9
10
11
12
@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); // 处理触摸事件
}
...
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

1
2
3
4
5
6
7
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

1
2
3
4
5
6
@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

1
2
3
4
5
6
7
8
9
10
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

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

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

ViewGroup.dispatchTouchEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
// 1. 初始化
// 点击事件的起始是ACTION_DOWN,终止是ACTION_UP或ACTION_CANCEL
// 当事件为DOWN时,意味着以DOWN->MOVE->MOVE->UP的事件序列开始派发,所以在DOWN时进行reset操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 2. 检查ViewGroup是否要拦截其子View接受事件,不拦截则进入第3步
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 3. 派发事件给子View
if (!canceled && !intercepted) {
...
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int childrenCount = mChildrenCount;
// 开始遍历所有子View
if (newTouchTarget == null && childrenCount != 0) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// 4. 对单个子View进行派发,如果成功则会将View加入到链表中并记录
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
...
}
}
...
}
// 5. 批量派发事件
if (mFirstTouchTarget == null) {
// 如果DOWN事件没有被当前ViewGroup容器的子View响应,那么ViewGroup不会再派发接下来的任何事件给其子View
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 除了之前已被派发的事件除外,批量派发接下来的事件给其子View
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 已经派发则不派发
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
// 派发事件给事件对应的View
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
...
}
predecessor = target;
target = next;
}
}
...
// 返回该ViewGroup最终派发结果
reurn handled;
}

以上代码核心派发逻辑:

  1. 初始化
  2. 检查ViewGroup是否要拦截其子View接受事件,不拦截则进入第3步,否则进入第5步
  3. 派发事件给子View
  4. 对单个子View进行派发,如果成功则会将View加入到链表中并记录
  5. 批量派发事件

View.onTouch

当事件派发到指定View之后,将会执行onTouch方法,根据返回值来判定该View是否消费了事件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
// 先执行onTouch方法
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果onTouch方法未消费事件,则会执行onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}

onTouch方法需要调用setOnTouchListener设置回调才会调用,onTouchEvent方法同时也可以被复写

onTouchEvent的返回值决定了dispatchTouchEvent的返回值,最终决定了事件的派发

事件派发”三部曲”

对比

方法名 意义 返回TRUE 返回FALSE
dispatchTouchEvent 对当前View执行事件派发 派发成功,不再继续派发 派发未成功,停止派发
onInterceptTouchEvent 当前ViewGroup是否要拦截子View接受事件 需要拦截,将不会对子View进行事件派发 不需要拦截,会对子View进行派发
onTouchEvent 处理当前触摸事件 事件被消费 事件未被消费

流转图

如上图所示,ViewGroup中拥有dispatchTouchEvent, onInterceptTouchEvent, onTouchEvent这三个方法,而View只拥有dispatchTouchEvent, onTouchEvent这两个方法。onInterceptTouch的意义在于,ViewGroup存在多个子View,如果该方法返回为TRUE,那么意味着它将不会派发事件给其子View。View中没有该方法是因为它不拥有子View。

View的onTouchEvent方法返回TRUE意味着事件被消费,如果ViewGroup的所有子View未消费事件,那么就会执行它自己的onTouchEvent,如果事件仍未被消费,则事件会继续向上传输。最顶层是DecorView的onTouchEvent方法,如果仍然返回false,那么dispatchTouchEvent会返回false,事件最终会交由Activity的onTouchEvent方法进行处理。

总结

结合着之前对Input底层源码的分析,到这里我们大概能知晓Android对触摸事件的派发整体流程。

到这篇文章的完结,View的显示、绘图、事件相关源码都已经分析完毕,之后的文章会更加则中PMS, WMS, adb 实现原理等知识点

扫码支持0.99元,您的支持将鼓励我继续创作!