View事件分发
InputDispatcher通过与对应窗口建立连接通道,将事件信息封装成InputMessgae,通过InputChannel将信息发送到窗口端socket,looper监听到fd有数据写入,执行NativeInputEventReceiver::handleEvent函数,读取窗口socket端数据,在返回到java端进行处理
InputEventReceiver.java
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
执行InputEventReceiver的重写函数onInputEvent
在ViewRootImpl中定义了InputEventReceiver的子类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);
}
}
- 创建processedEvents集合
- processInputEventForCompatibility考虑在发送app前做适配
- 判断适配处理后的事件是否存在
- 不存在代表事件处理完毕 将事件反馈给到InputDispatcher
- 存在 遍历processedEvents执行enqueueInputEvent入队派发
- 适配处理后的事件不存在 代表当前事件不需要适配,直接执行enqueueInputEvent入队派发
ViewRootImpl.java
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
if (event instanceof MotionEvent) {
MotionEvent me = (MotionEvent) event;
if (me.getAction() == MotionEvent.ACTION_CANCEL) {
EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Motion - Cancel",
getTitle().toString());
}
} else if (event instanceof KeyEvent) {
KeyEvent ke = (KeyEvent) event;
if (ke.isCanceled()) {
EventLog.writeEvent(EventLogTags.VIEW_ENQUEUE_INPUT_EVENT, "Key - Cancel",
getTitle().toString());
}
}
// 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;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
if (processImmediately) {
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
- 将event receiver flag 封装为QueuedInputEvent
- 判断event能否转为MotionEvent
- 判断MotionEvent.action是MotionEvent.ACTION_CANCEL
EventLog写入cancel事件 - 判断KeyEvent.action是KeyEvent.isCanceled
EventLog写入cancel事件 - input事件队列追加事件
- 这里processImmediately为true 执行doProcessInputEvents
ViewRootImpl.java
void doProcessInputEvents() {
// Deliver all pending input events in the queue.
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
mPendingInputEventCount);
mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
deliverInputEvent(q);
}
// We are done processing all input events that we can process right now
// so we can clear the pending flag immediately.
if (mProcessInputEventsScheduled) {
mProcessInputEventsScheduled = false;
mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
}
}
- 从缓存事件队列头部取出事件
- 缓存事件数量-1
- deliverInputEvent 分发事件
ViewRootImpl.java
private void deliverInputEvent(QueuedInputEvent q) {
Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
q.mEvent.getId());
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x"
+ Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano="
+ q.mEvent.getEventTimeNano() + " id=0x"
+ Integer.toHexString(q.mEvent.getId()));
}
try {
if (mInputEventConsistencyVerifier != null) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "verifyEventConsistency");
try {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
if (q.mEvent instanceof KeyEvent) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "preDispatchToUnhandledKeyManager");
try {
mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
if (stage != null) {
handleWindowFocusChanged();
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
- 验证输入事件的一致性。如果事件存在异常,则会抛出异常并终止程序的执行
- 输入事件的处理流程包括多个 Stage,每个 Stage 负责一些特定的任务,shouldSendToSynthesizer用于处理,mSyntheticInputStage主要负责处理需要进行输入法合成的输入事件
- q.shouldSendToSynthesizer() false 代表不是合成的输入事件,根据shouldSkipIme确认选择mFirstPostImeInputStage 处理输入法事件还是mFirstInputStage处理输入事件的第一阶段
- 判断mEvent的类型是否为KeyEvent,是的话执行 mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
- 上述选择的stage不为空,检查窗口焦点是否发生变化来调整事件的派发,之后调用Stage.deliver来派发
在研究Stage.deliver派发事件前要先了解stage的概念
ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
CharSequence counterSuffix = attrs.getTitle();
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;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}
- 可以看到在执行setView方法
- 构建了7个阶段
AsyncInputStage:这个阶段是输入系统的异步处理阶段。在用户输入事件发生后,首先会经过硬件驱动层的处理,然后传递给应用程序的主线程。在主线程中,输入事件会被封装并发送到 AsyncInputStage 进行异步处理。这个阶段的任务包括事件的解析、转换、分发等操作,确保输入事件能够被正确地传递给相应的目标视图进行处理。
第一阶段:
SyntheticInputStage 是 Android 系统中的一个输入阶段,它属于系统输入处理的一部分。具体来说,SyntheticInputStage 用于处理合成输入事件。
在 Android 中,合成输入事件是由系统模拟生成的输入事件,而不是由用户实际触发的。合成输入事件可以用于模拟用户操作,例如自动化测试、辅助功能等场景。SyntheticInputStage 负责接收和处理这些合成输入事件,并将它们传递给目标视图进行相应的处理。
在 SyntheticInputStage 阶段,系统会将合成输入事件按照特定的顺序进行处理,通常包括以下几个步骤:
合成输入事件的生成:系统根据需要生成合成输入事件,这些事件可以模拟用户的点击、滑动、长按等操作。生成合成输入事件的方式可以通过调用相应的 API 或使用相关的工具类来实现。
合成输入事件的分发:生成的合成输入事件会被系统分发到相应的 SyntheticInputStage 阶段进行处理。系统会按照一定的规则确定目标视图,然后将合成输入事件传递给目标视图进行处理。
合成输入事件的处理:SyntheticInputStage 接收到合成输入事件后,会将事件传递给目标视图进行处理。目标视图可以是应用程序中的任何视图,例如按钮、文本框等。目标视图根据接收到的合成输入事件来执行相应的操作,如执行点击操作、滚动操作等。
需要注意的是,SyntheticInputStage 阶段是系统的一部分,一般情况下开发者无需直接操作或干预该阶段的过程。这个阶段在 Android 系统内部进行,主要目的是处理合成输入事件,以模拟用户操作或实现辅助功能。
第二阶段:
ViewPostImeInputStage 是 Android 系统中的一个输入阶段,它属于系统输入处理的一部分。具体来说,ViewPostImeInputStage 用于处理 IME(输入法)相关的输入事件。
在 Android 中,IME 输入事件是由输入法软件发送给应用程序的。当用户在文本框或其他可编辑视图中进行输入时,输入法软件会生成相应的输入事件,并将其发送到应用程序中。ViewPostImeInputStage 阶段负责接收和处理这些输入事件,并将它们传递给目标视图进行相应的处理。
在 ViewPostImeInputStage 阶段,系统会按照以下步骤处理 IME 相关的输入事件:
IME 输入事件的生成:当用户在输入法软件中输入文字时,输入法软件会生成相应的输入事件,如文本变化事件、光标位置变化事件等。
IME 输入事件的分发:输入法软件将生成的输入事件发送到当前活动的应用程序中。系统会将这些输入事件分发给适当的 ViewPostImeInputStage 阶段进行处理。
IME 输入事件的处理:ViewPostImeInputStage 接收到 IME 输入事件后,会将事件传递给目标视图进行处理。目标视图通常是当前正在与用户交互的文本框或可编辑视图。目标视图根据接收到的输入事件来更新文本内容、光标位置等,并可能触发相应的监听器或回调方法。
需要注意的是,ViewPostImeInputStage 阶段是系统的一部分,开发者无需直接操作或干预该阶段的过程。这个阶段在 Android 系统内部进行,主要目的是处理与输入法相关的输入事件,以确保用户的输入能够正确地显示和处理。
第三阶段:
nativePostImeStage是在提到原生(native)层面的输入处理,可以理解为与输入法相关的底层操作和事件。
在底层的原生开发中,涉及到输入法的处理通常包括以下几个方面:
输入法管理器(InputMethodManager):在原生开发中,可以使用输入法管理器来控制输入法的显示、隐藏以及切换。通过输入法管理器,可以获取当前的输入法信息、打开或关闭输入法窗口、获取输入法视图等。
输入法事件处理:原生开发可以处理与输入法相关的事件,例如键盘按键事件(KeyEvent)、触摸事件(MotionEvent)等。通过监听和处理这些事件,可以实现与输入法交互的功能,如捕获按键输入、响应触摸操作等。
输入框处理:原生开发还可以针对输入框进行处理,包括输入框的创建、位置和大小的调整、内容的显示和编辑等。通过与输入框的交互,可以实现与用户的输入交互和数据处理。
第四阶段:
接下来我们看其中一个stage的构造函数
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
这里可以看到将ViewPostImeInputStage关联的下一阶段stage,放到父类InputStage的构造函数中
public InputStage(InputStage next) {
mNext = next;
}
这里看到下个阶段的stage赋值到mNext中
那么在执行父类InputStage的方法时比如
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
这里调用 mNext.deliver(q)就来到了下个阶段的deliver函数中
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函数,将处理结果和事件给到
apply(q, result)
protected void apply(QueuedInputEvent q, int result) {
if (result == FORWARD) {
forward(q);
} else if (result == FINISH_HANDLED) {
finish(q, true);
} else if (result == FINISH_NOT_HANDLED) {
finish(q, false);
} else {
throw new IllegalArgumentException("Invalid result: " + result);
}
}
这里根据处理结果执行不同函数 关于apply来说由于stage不同分为了同步异步俩个部分 这里主要看同步
protected void forward(QueuedInputEvent q) {
onDeliverToNext(q);
}
执行onDeliverToNext方法
protected void onDeliverToNext(QueuedInputEvent q) {
if (DEBUG_INPUT_STAGES) {
Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
}
if (mNext != null) {
mNext.deliver(q);
} else {
finishInputEvent(q);
}
}
这里可以看到要分发到下下个阶段了,同时如果所有阶段都执行完就执行finishInputEvent给InputDispatcher反馈了
再看下finish函数
protected void finish(QueuedInputEvent q, boolean handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED;
if (handled) {
q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;
}
forward(q);
}
handled为true代表事件处理完成事件加上QueuedInputEvent.FLAG_FINISHED_HANDLED flag ,分发下个阶段,下个阶段一看事件已经处理完了,那直接跳过平台来给InputDispatcher反馈
经过AsyncInputStage ViewPostImeInputStage阶段
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
@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);
}
}
}
- 当前是KeyEvent执行processKeyEvent
- source是InputDevice.SOURCE_CLASS_POINTER 执行processPointerEvent
- source是InputDevice.SOURCE_CLASS_TRACKBALL执行processTrackballEvent事件
- 其他执行processGenericMotionEvent
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
mHandwritingInitiator.onTouchEvent(event);
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
- 获取event事件
- 执行 mView.dispatchPointerEvent(event)
- 根据结果确认当前是FINISH_HANDLED还是FORWARD
View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
1 检查事件是否为touch事件 是的话执行DecorView.dispatchTouchEvent
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);
}
- 获取phoneWindow的callback对象即窗口的Activity
- 执行Activity的dispatchTouchEvent方法
关于为什么phoneWindow的callback对象即窗口的Activity
Activity.java
final void attach(......){
......
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this); // 这里的 this 就是 Activity
......
}
接下来执行Activity的dispatchTouchEvent方法
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
- 首先ACTION_DOWN事件执行onUserInteraction这个空方法,通知用户,Activity已经接收到事件了
- 当前Activity的窗口执行superDispatchTouchEvent方法,派发成功返回true
- 如果上述都失败了,就执行Activity的onTouchEvent方法
接下来看当前Activity的窗口执行superDispatchTouchEvent方法
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
- 执行DecorView的superDispatchTouchEvent方法
接下来看DecorView的superDispatchTouchEvent方法
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
- 执行父类的superDispatchTouchEvent方法,这里的super是ViewGroup
为什么这里的super是ViewGroup
public class DecorView extends FrameLayout
public class FrameLayout extends ViewGroup
- 可以看到最终的父类是ViewGroup
在查看ViewGroup的dispatchTouchEvent方法前,先做一个java端事件派发总结:
可以看到在输入事件通过NativeInputEventReciver的InputConsumer从InputChannel中读出由JNI ->java端 ->ViewRootImpl -> InputStage -> DecorView -> Activity -> PhoneWindow -> DecorView -> ViewGroup
接下来看ViewGroup的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
// If the event targets the accessibility focused view and this is it, start
// normal event dispatch. Maybe a descendant is what will handle the click.
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
boolean handled = false;
// 判断此次触摸事件是否被过滤掉,条件由两个 flag 决定,FILTER_TOUCHES_WHEN_OBSCURED
// 和 MotionEvent.FLAG_WINDOW_IS_OBSCURED,这两个 flag 用来表示
// 当前接收触摸事件的 View 是否被遮挡或者隐藏,只有未被遮挡或隐藏才能
// 进一步处理事件
//事件安全检查
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
//事件action为MotionEvent.ACTION_DOWN代表一次新的事件序列到来,需要清空上一次
//事件序列的相关状态
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) {
//Action为ACTION_DOWN或者mFirstTouchTarget(接收事件的目标)不为空
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//检查mGroupFlags是否为FLAG_DISALLOW_INTERCEPT
//FLAG_DISALLOW_INTERCEPT这个为不允许拦截事件Flag
if (!disallowIntercept) {
//父view调用onInterceptTouchEvent拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//设置intercepted(父View拦截Flag为false)
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//当前Action不是ACTION_DOWN或者没有mFirstTouchTarget(接收事件的目标)
//事件要被拦截
intercepted = true;
}
// Check for cancelation.
//resetCancelNextUpFlag(this)为true或者Action为ACTION_CANCEL
//会设置canceled为true
//resetCancelNextUpFlag方法指的View是否存在PFLAG_CANCEL_NEXT_UP_EVENT标志
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
//是否为鼠标事件
final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
//mGroupFlags是否包含FLAG_SPLIT_MOTION_EVENTS并且不是鼠标事件
//检查View是否支持事件拆分
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
&& !isMouseEvent;
//定义新的接收事件的目标
TouchTarget newTouchTarget = null;
//事件是否派发到新的接收事件的目标的标志位
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
//进入该判断的事件 未被取消并且拦截
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//新事件序列开始的行为则进入该判断
//获取事件Index
final int actionIndex = ev.getActionIndex(); // always 0 for down
//根据是否支持事件拆分获取触控点位
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.
//移除上次事件序列的触控点位
removePointersFromTouchTargets(idBitsToAssign);
//获取子视图数量
final int childrenCount = mChildrenCount;
//新的接收事件目标为空并且子视图不为0
if (newTouchTarget == null && childrenCount != 0) {
//获取x
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
//获取y
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
//获取预排续(按照Zorder顺序)子view集合
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
//是否满足子view集合自定义排序
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//子view数组
final View[] children = mChildren;
//根据zOrder由高到底遍历预排续view数组
for (int i = childrenCount - 1; i >= 0; i--) {
//获取子view的index
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
//获取子view对象
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount;
}
//子视图是否可以接收事件或者当前事件的坐标是否在子视图中
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
//不满足条件则开启下一个View的遍历循环
ev.setTargetAccessibilityFocus(false);
continue;
}
//如果找到了事件所在的子视图
//getTouchTarget会根据子视图获取的newTouchTarget
//newTouchTarget不为空代表子视图之前处理过事件(多指触碰)
//对于单指down行为 newTouchTarget一定为null
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;
//跳出循环 当前已找到对应处理该事件的view
break;
}
//如果有设置 PFLAG_CANCEL_NEXT_UP_EVENT,在此清除
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent方法为事件找到真正处理它的子控件View
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//事件派发成功找到子控件View
//记录最后一次down时间
mLastTouchDownTime = ev.getDownTime();
//存在预排序view集合
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
//遍历预排续View数组获取处理该事件的View的childIndex
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
//最后一次down事件index是原始子视图的index
mLastTouchDownIndex = childIndex;
}
//获取最后一次down事件的 x y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//根据子视图和触控点创建newTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//flag设置为true
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
//清空预排序view集合
if (preorderedList != null) preorderedList.clear();
}
//如果没找到事件所对应的子view
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;
}
//将最近一次处理事件的TouchTarget更新为处理该事件的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary 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;
//遍历TouchTarget集合
while (target != null) {
//保存下一个TouchTarget到next
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//如果已经将事件派发到新的TouchTarget
handled = true;
} else {
//检查View是否包含PFLAG_CANCEL_NEXT_UP_EVENT这个flag
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//将事件派发到目标视图中,这里一般是处理Move事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
//处理成功
handled = true;
}
//如果需要去掉子视图,也需要调整链表的中该视图对于的touchTarget
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) {
//处理up事件,清空touchTarget链表
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//可拆分并且存在多指抬起
//根据actionIndex获取触控点idBitsToRemove然后调整touchTarget的触控点
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
总结:
- 关于ViewGroup的事件派发,首先onFilterTouchEventForSecurity方法检查接收事件的View是否被遮挡,遮挡的话是要cancel的,
- 对于一次down行为,可以理解为一次新的事件序列,要清空TouchTarget链表。
- 对于一次down行为,一定会被ViewGroup拦截进行onInterceptTouchEvent方法检查是否拦截成功,对于单指操作来说一旦down事件被拦截 该次事件序列的MOVE UP都会被拦截但不在执行onInterceptTouchEvent方法,子 View 可以通过 requestDisallowInterceptTouchEvent 方法请求父 View 不要拦截
同样关于onInterceptTouchEvent方法方法也不一定会拦截掉down事件,也要做条件判断,后续细看 - 检查事件是否被cancel
- 检查事件资源是否为鼠标事件
- 检查事件是否可拆分
- 定义事件派发标志位
- 在事件不被cancel和intercept时,单指Down行为,多指可拆分的Pointer_Down行为,以及鼠标悬浮MOVE行为,这种新事件序列行为
以单指Down行为来说
首先获取 x,y坐标
获取Zorder预排续的View集合
遍历View集合 检查View是否可以接收事件,事件坐标是否在View内
不满足 轮询下一个View
满足 执行dispatchTransformedTouchEvent方法 派发给子View处理该事件
处理成功,构建newTouchTarget来保存View,后续事件序列的事件可以直接派发到该View
如果没找到子View处理该事件,android的机制是让最近一次的TouchTarget来处理该事件
接下来单指Move行为
就执行下述mFirstTouchTarget链表遍历,找到合适的子View处理行为事件
接下来单指Up行为
执行resetTouchState()方法 清空链表
接下来看一下cancelAndClearTouchTargets方法
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
1 检查mFirstTouchTarget是否为空
2 如果事件为空 根据当前时间生成Cancel事件,设置syntheticEvent为true
3 遍历mFirstTouchTarget链表,执行resetCancelNextUpFlag方法,向上次事件序列的子View发送cancel事件
4 清空touchTarget链表
接下来先看resetCancelNextUpFlag方法
private static boolean resetCancelNextUpFlag(@NonNull View view) {
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
去掉视图的 PFLAG_CANCEL_NEXT_UP_EVENT flag
接下来看dispatchTransformedTouchEvent方法
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;
}
}
因为 上述中cancel为true会向View发送cancel事件取消上次事件序列
接下来看下clearTouchTargets方法
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
清空mFirstTouchTarget 触控目标列表 将触控目标的view设置为null。
接下来看
private void resetTouchState() {
//清空touchTarget
clearTouchTargets();
//如果View的PFLAG_CANCEL_NEXT_UP_EVENT存在则去除
resetCancelNextUpFlag(this);
//去除FLAG_DISALLOW_INTERCEPT flag
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
接下来看下onInterceptTouchEvent方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
可以看到事件满足下述条件才会返回true或者
或者actionMasked != MotionEvent.ACTION_DOWN才会设置 ,如果onInterceptTouchEvent失败 intercepted = false。
接下来看下removePointersFromTouchTargets方法
private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if ((target.pointerIdBits & pointerIdBits) != 0) {
target.pointerIdBits &= ~pointerIdBits;
if (target.pointerIdBits == 0) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
这里遍历TouchTarget链表的pointerIdBits与新事件的idBitsToAssign进行比较,来清除TouchTarget链表的之前TouchTarget的pointerIdBits
接下来看下dispatchTransformedTouchEvent方法
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.
//获取事件Action
final int oldAction = event.getAction();
//如果存在cancel 则向View派发cancel事件
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.
//获取事件的触控点集合
final int oldPointerIdBits = event.getPointerIdBits();
//根据事件触控点集合和期待触控点集合做运算得到新的触控点集合
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.
//新的触控点不存在的话派发失败
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) {
//子视图为空 分发事件到viewGroup中
handled = super.dispatchTouchEvent(event);
} else {
//子视图不为空并且由单位矩阵
//计算子视图坐标偏移量
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
//发送事件到子view
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {、
//根据newPointerIdBits拆分event
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
//由ViewGroup处理事件
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//坐标转换适配子view
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//事件派发给子View
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
//回收transformedEvent
transformedEvent.recycle();
return handled;
}
总结:
1.检查 even的Action是否为cancel,是的话直接发送cancel事件
2. 根据event的触控点集合和期待的触控点集合进行位运算获取新的触控点集合
3. 根据子视图是否存在以及子视图是否由单位矩阵
来决定事件的坐标是否需要转换成view坐标系坐标,事件是由父View处理还是子View处理/子ViewGroup
4. 派发完转换事件,回收转换事件
存在子View的情况,执行child.dispatchTouchEvent方法
接下来看下View的dispatchTouchEvent
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
//这里检查mListenerInfo是否存在
//mOnTouchListener是否存在
//mViewFlags是ENABLED状态
//上述条件满足执行li.mOnTouchListener.onTouch(this, event)
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//上述方式执行失败,执行onTouchEvent
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;
}
事件处理顺序mOnTouchListener.onTouch(this, event) -> onTouchEvent(event) -> onLongclick -> onClick
总结 :
关于JAVA层事件的派发主要有三个方法
View/ViewGroup
dispatchTouchEvent 事件派发
onInterceptTouchEvent 事件拦截
onTouchEvent 事件处理
提出几点疑问
- onclick 事件都是在 ACTION_UP 以后才被调用的
- mOnTouchListener.onTouch(this, event) return true的话 是不会执行onClick事件
- 子 View 可以通过 requestDisallowInterceptTouchEvent 方法干预父 View 的事件分发过程 但是不包含down事件
接下来继续分析源码,同时也在分析源码中解开上述问题
上面View的dispatchTouchEvent方法会先检查li.mOnTouchListener.onTouch(this, event)是否为true,true的话直接返回,不会在执行onTouchEvent 中的onClick
如果是false的话执行 View.onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE
.......
//支持点击
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
.....
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
//ACTION_UP执行performClickInternal方法
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
case MotionEvent.ACTION_DOWN:
.......
else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
case MotionEvent.ACTION_MOVE:
final boolean deepPress =
motionClassification == MotionEvent.CLASSIFICATION_DEEP_PRESS;
if (deepPress && hasPendingLongPressCallback()) {
// process the long click action immediately
removeLongPressCallback();
checkForLongClick(
0 /* send immediately */,
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS);
}
break;
}
return true;
}
可以看到事件只要进入到View的onTouchEvent方法,只要ViewFlag是支持CLICKABLE / LONG_CLICKABLE 事件就一定会被处理且返回true
在ACTION_UP的时候执行performClickInternal函数
在ACTION_MOVE/ ACRION_DOWN 会执行checkForLongClick
接下来看下performClickInternal函数
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
//执行performClick函数
return performClick();
}
接下来看performClick函数
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
//ListenerInfo和li.mOnClickListener不为空
//播放click声音
playSoundEffect(SoundEffectConstants.CLICK);
//执行View的onclick函数
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
接下来看checkForLongClick函数
private void checkForLongClick(long delay, float x, float y, int classification) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
mPendingCheckForLongPress.setClassification(classification);
postDelayed(mPendingCheckForLongPress, delay);
}
}
构建CheckForLongPress对象 延时post mPendingCheckForLongPress runnable函数
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
recordGestureClassification(mClassification);
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
runnable的run方法 执行performLongClick函数
最后会执行到该函数
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
可以看到这里执行li.mOnLongClickListener.onLongClick(View.this)
这里的ListenerInfo.mOnLongClickListener是从哪里出现的
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
可以看到View的setOnClickListener这里实际就是我们应用自己定义实现的OnClickListener对象,同样onClick / onLongClick也会回调到我们自己重写的方法中
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀