Android事件分发分析

本篇所分析的源码为Android 28,可能与其他版本有所出入

流程概括

  1. 输入事件由FrameWork层发送到ViewRootImpl的内部类WindowInputEventReceiver上,根据InputEvent的类型,进行不同的分发方式。
  2. 如果是TouchEventViewRootImpl调用其保存的View.dispatchTouchEvent()方法,这个View一般是DecorView,如果是悬浮窗一类的直接是WindowManagerGlobal.addView()的那个View
  3. DecorView首先将该事件交由Window中的callback进行处理,也就是ActivityDialog
  4. Activity然后在交由Window也就是PhoneWindow处理
  5. PhoneWindow在交由内部保存的DecorView进行处理
  6. DecorView使用ViewGroup分发逻辑向下分发。
  7. ViewGroup首先判断自身是否拦截事件,如果onInterceptTouchEvent返回true,则自身消费事件。
  8. 如果不拦截,则将子View按z-order的顺序排序,根据MotionEvent的x、y寻找子View,将事件分发给它们。
  9. 如果子ViewViewGroup,则同样继续分发给子View
  10. 如果子ViewView,那么通过判断mOnTouchListeneronTouchEvent的返回值看是否事件消费
  11. 如果所有的子View都没有消费这个事件,则事件向上返回,由父ViewGroup决定自身是否消费。
  12. 最终ActivityWindow都不消费,则ViewRootImpl调用finishInputEvent结束本次事件。

事件的产生

事件由底层传入到InputManagerService,再转由WindowManagerServer发送到InputEventReceiver类的dispatchInputEvent()方法中,详细解析见 Android Input事件获取与分发简单总结

InputEventReceiver.java
1
2
3
4
5
6
7
8
9
10
11
// Called from native code.
private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event, displayId);
}
/**
* 这个方法需要子类进行重写,否则则直接结束了事件分发。
*/
public void onInputEvent(InputEvent event, int displayId) {
finishInputEvent(event, false);
}

InputEventReceiver是一个抽象类,具体子类实现在ViewRootImpl中。

ViewRootImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
final class WindowInputEventReceiver extends InputEventReceiver {
public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
super(inputChannel, looper);
}

/**
* 重写了父类方法,处理Input事件
*/
@Override
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
// ...
}

ViewRootImpl.setView()方法中会创建InputChannelWindowInputEventReceiver实例,InputChannel连通了WindowManagerServer与App进程的事件输入通道。

ViewRootImpl.setView会在ActivityDialog或直接调用WindowManagerGlobal.addView()方法时调用。具体可详见Post not found: App启动流程

ViewRootImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
// ...
try {
// 向WindowManagerServer注册时传入mInputChannel
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {

}

if (mInputChannel != null) {
// 实例化Receiver
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper()); // 传入了InputChannel和主线程的Looper
}
// ...
}

当一个InputEvent输入后,便调用到了ViewRootImpl.enqueueInputEvent()中开始了处理分发过程

ViewRootImpl.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
26
27
28
29
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
adjustInputEventForCompatibility(event);
// 将InputEvent封装为QueuedInputEvent,优先从缓存池中复用对象。
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

// Always enqueue the input event in order, regardless of its time stamp.
// We do this because the application or the IME may inject key events
// in response to touch events and we want to ensure that the injected keys
// are processed in the order they were received and we cannot trust that
// the time stamp of injected events are monotonic.
// 将新事件加入到未处理事件链中
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;

if (processImmediately) {
// 处理事件
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}

然后doProcessInputEvents() -> deliverInputEvent()->最后到达ViewPostImeInputStage.onProcess()方法中。

ViewPostImeInputStage是一个责任链模式中的一环,在事件处理责任链中完整的责任链为:mSyntheticInputStage -> viewPostImeStage -> nativePostImeStage –> earlyPostImeStage –> imeStage –> viewPreImeStage –> viewPreImeStage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ----------------- InputStage的子类 ----------------------------
// 将预先输入事件提供给视图层次结构。
final class ViewPreImeInputStage extends InputStage {}
// 执行事后输入事件的早期处理。
final class EarlyPostImeInputStage extends InputStage {}
// 将后期输入事件提供给视图层次结构。
final class ViewPostImeInputStage extends InputStage {}
// 从未处理的输入事件执行新输入事件的合成。
final class SyntheticInputStage extends InputStage {}
// 用于实现支持输入事件的异步和无序处理的输入流水线级的基类。
abstract class AsyncInputStage extends InputStage {}
// ----------------- AsyncInputStage的子类----------------------------
// 将预先输入事件提供给 NativeActivity。
final class NativePreImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}
// 将预先输入事件提供给视图层次结构。
final class ImeInputStage extends AsyncInputStage
implements InputMethodManager.FinishedInputEventCallback {}
// 将事后输入事件提交到 NativeActivity
final class NativePostImeInputStage extends AsyncInputStage
implements InputQueue.FinishedInputEventCallback {}

责任链在ViewRootImpl.setView()中进行拼接。

ViewPostImeInputStage中,对InputEvent事件进行了区分,如果是按键事件则走按键的分发逻辑,具体解析见Android焦点机制中。而触摸点按事件则首先调用了mView.dispatchPointerEvent()方法。

ViewRootImpl.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
26
27
28
29
30
31
final class ViewPostImeInputStage extends InputStage {

@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);
}
}
}

private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;

// ...
// 交由ViewRootImpl中的View开发分发点击事件
boolean handled = mView.dispatchPointerEvent(event);
// ...
return handled ? FINISH_HANDLED : FORWARD;
}
}

此时,则开始了事件分发逻辑。

ViewRootImpl分发逻辑

View.java
1
2
3
4
5
6
7
8
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
// 在View中开始分发
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}

如果是普通View,则直接开始了事件分发流程,但如果是DecorView,因其重写了dispatchTouchEvent()方法,会将事件首先交由Windowcallback处理

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上
}

Windowcallback则是在创建PhoneWindow时传入的,即是ActivityDialog实例。

此处拿Activity进行分析。ActivityMotionEvent事件又转交由Window进行处理。

Activity.java
1
2
3
4
5
6
7
8
9
10
11
12
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
// 可供子类做一些事情
onUserInteraction();
}
// 交由window处理分发事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// 如果window分发过后并没有消费事件,则activity自身则尝试处理事件。
return onTouchEvent(ev);
}

WindowMotionEvent事件通过调用DecorView.superDispatchTouchEvent()方法转交DecorView进行处理

PhoneWindow.java
1
2
3
4
5
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// 交由DecorView
return mDecor.superDispatchTouchEvent(event);
}

DecorView中则调用父类的dispatchTouchEvent()方法进行分发,而DecorViewFrameLayout的子类,则分发防止遵循ViewGroup的分发逻辑

DecorView.java
1
2
3
4
public boolean superDispatchTouchEvent(MotionEvent event) {
// 调用了ViewGroup.dispatchTouchEvent
return super.dispatchTouchEvent(event);
}

这样,MotionEvent事件则沿着ViewRootImpl->DecorView->Activity->Window->DecorView->ViewGroup->View的顺序分发。这样设计可以保证逻辑上的大容器先接受事件,小容器后接受事件,大容器可以直接将touch事件进行拦截不分发。而如果Window中直接添加了View的话,例如常见的悬浮窗,那么事件传递则为ViewRootImpl->ViewGroup->View

ViewGroup的分发逻辑

每个MotionEvent存储了当前这个手指事件的索引,以及多指情况下其他全部手指的触摸点坐标集合,高8位存储触摸点索引,低8位是存储Action类型。多指操作时,除第一个手指触发的是ACTION_DOWN,其余都会触发ACTION_POINTER_DOWN。分发时,先判断是否是事件的开始ACTION_DOWN事件或已经子View已消费ACTION_DOWN事件,然后自身是否拦截,如果已有目标子View,则直接分发给目标View,否则,寻找触摸坐标对应的View,判断其是否进行消费。如果最终没有子View消费,则判断自身是否消费。

ViewGroup.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
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
// action的高9-16位表示索引值
// 低1-8位表示事件类型
final int action = ev.getAction();
// 取出事件类型
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Handle an initial down.一次新的点击事件,则重制View状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}

// Check for interception.
final boolean intercepted;
// 如果是事件的开始或之前向下分发过事件,则判断自身是否拦截事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
// 子View可以通过requestDisallowInterceptTouchEvent更改flag使得父控件始终不拦截事件
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 {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}

// Check for cancelation. 判断事件是否是Cancel
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;

// Update list of touch targets for pointer down, if needed.
// 判断是否是多指情况下需要分割索引
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;

TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {

// 只有Down事件才去寻找子View,其他事件直接在TouchTarget链中寻找View分发
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
// 获取当前的event事件所对应的索引
final int actionIndex = ev.getActionIndex(); // always 0 for down
// 找出对应的触摸点id值
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;

// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
// down事件表示该触控点事件序列是一个新的序列
// 清除之前绑定到到该触控id的TouchTarget
removePointersFromTouchTargets(idBitsToAssign);

final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
// View中可以配置z属性导致children数组中的顺序并不是实际的从前到后
// 根据z轴将子View从前到后排序成List
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
// 依次取出View
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);

// 判断Child的可见性和动画状态是否能接受事件
if (!canViewReceivePointerEvents(child)
// 判断这个点是否在View中
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
// 如果View不符合条件直接跳出本次循环,找下一个View
continue;
}
// 如果这个View已经在TouchTarget链中,则更新新的触控点id
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}

resetCancelNextUpFlag(child);
// 将事件发送给这个child,如果是多点则根据id拆分Event
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;
}

}
if (preorderedList != null) preorderedList.clear();
}

if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}

// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 没有子View想消费事件,则自己判读是否消费
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 分发事件
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 如果是Down事件,可能已经被消费
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}

// Update list of touch targets for pointer up or cancel, if needed.
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
// 如果是Up事件,则根据id将View移出链
removePointersFromTouchTargets(idBitsToRemove);
}
}

return handled;
}

确定分发的View后,通过调用dispatchTransformedTouchEvent()方法进行分发。如果是多指情况,则会通过id将拆分重组新的MotionEvent

ViewGroup.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
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
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;

// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}

// Calculate the number of pointers to deliver.
// 获取存放全部的点id的int
final int oldPointerIdBits = event.getPointerIdBits();
// 取出desired的那一位的点id
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
// 如果全部的点信息中与desired的那一位的点id不同,则丢弃该事件
if (newPointerIdBits == 0) {
return false;
}

// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
// 如果全部点信息与目标点信息相同,说明当前只有一个点触摸
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);

handled = child.dispatchTouchEvent(event);

event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
// 否则则拆分Event,从全部触摸事件集合中单独取出对应id的事件并组装成Event
transformedEvent = event.split(newPointerIdBits);
}

// Perform any necessary transformations and dispatch.
if (child == null) {
// 没有目标child,则分发给自己
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);
}

// Done.
transformedEvent.recycle();
return handled;
}

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public boolean dispatchTouchEvent(MotionEvent event) {

boolean result = false;

if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
// 获取action类型
final int actionMasked = event.getActionMasked();
// 如果是down事件,停止正在的滚动
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;
// 先判断mOnTouchListener是否消费
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
// 如果mOnTouchListener不消费的话则判断onTouchEvent是否消费
if (!result && onTouchEvent(event)) {
result = true;
}
}

// 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;
}

参考文章

Android Input事件获取与分发简单总结

Android事件分发机制二:核心分发逻辑源码解析

View·InputEvent事件投递源码分析(一)

作者

Hanani

发布于

2022-08-16

更新于

2022-08-19

许可协议