1、背景
Android的触摸事件(MotionEvent)的传递机制还是有些复杂,内容会有点多,所以分为上下两篇来讲解。
《Android MetionEvent事件解析(上)》分析事件的分发流程,当清楚了事件分发流程后,我们需要结合一些实例进一步熟悉Android的事件传递机制,所以在《Android MetionEvent事件解析(下)》会提出几个问题,并通过源码进行分析。
分析事件的分发流程分为两个阶段进行分析。
1. 第一阶段:ViewRootImpl -> Activity
2. 第二阶段:Activity -> View
2、ViewRootImpl -> Activity
ViewRootImpl用于管理View的事件分发,界面绘制等工作。所以触摸事件的接收应该是从这个类开始。ViewRootImpl中定义了一个成员变量
WindowInputEventReceiver mInputEventReceiver;
WindowInputEventReceiver是ViewRootImpl中的内部类,该类继承自InputEventReceiver。通过类名就可知这是一个输入事件接收器。在创建WindowInputEventReceiver对象时会调用Native方法nativeInit()进行初始化
/**
* Creates an input event receiver bound to the specified input channel.
*
* @param inputChannel The input channel.
* @param looper The looper to use when invoking callbacks.
*/
public InputEventReceiver(InputChannel inputChannel, Looper looper) {
...
// 通过Native方法进行初始化
// 创建一个指向WindowInputEventReiver对象的弱引用,并传递到Native层
mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
inputChannel, mMessageQueue);
...
}
Native层通过WindowInputEventReceiver对象的弱引用来进行相应的回调。在InputEventReceiver中有一个函数,通过注释我们知道该函数是Native层回调的,这个就是我们应用程序接收到事件的入口
// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event) {
mSeqMap.put(event.getSequenceNumber(), seq);
onInputEvent(event);
}
找到了事件的接收源头后,接下来就来分析事件如何传递到Activity.dispatchTouchEvent()的
在onInputEvent(event)函数中调用了enqueueInputEvent(event, true)函数进行处理
@Override
public void onInputEvent(InputEvent event) {
enqueueInputEvent(event, this, 0, true);
}
enqueueInputEvent ( )主要从缓存池中获取一个已被回收的QueuedInputEvent对象q,并把q添加到一个用于存放QueuedInputEvent对象的队列中
void enqueueInputEvent(InputEvent event,
InputEventReceiver receiver, int flags, boolean processImmediately) {
// 从缓存池中获取一个QueuedInputEvent对象
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
// 把q添加到队列,mPendingInputEventHead为队列的头部
QueuedInputEvent last = mPendingInputEventTail;
if (last == null) {
mPendingInputEventHead = q;
mPendingInputEventTail = q;
} else {
last.mNext = q;
mPendingInputEventTail = q;
}
mPendingInputEventCount += 1;
if (processImmediately) {
// processImmediately为true,所以执行doProcessInputEvents
doProcessInputEvents();
} else {
scheduleProcessInputEvents();
}
}
void doProcessInputEvents() {
// 遍历队列,并通过deliverInputEvent()方法处理每个QueuedInputEvent对象
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
if (mPendingInputEventHead == null) {
mPendingInputEventTail = null;
}
q.mNext = null;
mPendingInputEventCount -= 1;
deliverInputEvent(q);
}
。。。
}
private void deliverInputEvent(QueuedInputEvent q) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent");
try {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
}
//检测ime相关module是否需要处理该输入事件,比如back键,是需要先
//让IME处理,这个时候需要先交给mFirstPostImeInputStage处理
InputStage stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
if (stage != null) {
// InputStage是一个抽象类,这里需要确认stage对象指向那个实现类
// 并调用deliver()进行处理事件
stage.deliver(q);
} else {
finishInputEvent(q);
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
这里需要介绍一下InputStage的作用,InputStage是一个抽象列,从该类派生出很多的子类,那从InputStage派生出来的子类都是做什么的呢?
InputStage是责任链中的一个节点,系统中有很多种类型的责任链节点,下载我们假定我们已经知道把QueuedInputEvent传递到VIew的处理是由ViewPostImeInputStage节点来处理,在onProgress( )方法中处理
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
// 处理物理按键的点击操作(电源键、放回键等)
return processKeyEvent(q);
} else {
handleDispatchWindowAnimationStopped();
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;
mAttachInfo.mUnbufferedDispatchRequested = false;
// mView为DecorView,开始分发触摸事件
boolean handled = mView.dispatchPointerEvent(event);
if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
mUnbufferedInputDispatch = true;
if (mConsumeBatchedInputScheduled) {
scheduleConsumeBatchedInputImmediately();
}
}
return handled ? FINISH_HANDLED : FORWARD;
}
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
// DecorView重写了该方法
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
// 获取mCallback,并进行回调,这个mCallback就是在前面设定指向Activity对象
// 就这样Activity.dispatchTouchEvent()接收到了触摸事件
final Callback cb = getCallback();
return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
: super.dispatchTouchEvent(ev);
}
MotionEvent事件是如何传递到Activity.dispatchTouchEvent()的,已经分析完毕,下面继续分析MotionEvent从Activity.dispatchTouchEvent()到View流程。
3、Activity -> View
这里先给出事件在Activity和View之间的传递大致过程,下面会分析Activity、ViewGroup、View三个类的dispatchTouchEvent( )方法的实现细节。
先来看Activity.dispatchTouchEvent( )的实现,实现还是比较简单
public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果是DOWN事件,则回调onUserInteraction()
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 把事件分发给DecorView处理,返回true代表DecorView消费了事件
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
// DecorView不处理,则由Activity.onTouchEvent()处理
return onTouchEvent(ev);
}
getWindow().superDispatchTouchEvent(ev) 会传递到DecorView,而DecorView继承FrameLayout,事件最终传递到FrameLayout.dispatchTouchEvent( ),下方的流程图是ViewGroup.dispatchTouchEvent( )的大致流程
我们跟着上方的流程图去阅读以下ViewGroup.dispatchTouchEvent( )的源码,这样会比较清晰些
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// down事件为一个事件周期的开头,所以进行初始化操作
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检查事件是否被拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
// 允许拦截,则通过onInterceptTouchEvent()的返回值看是否需要拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
// 当前View设置了不允许拦截标识
intercepted = false;
}
} else {
// 不是down事件,且没有接受该事件的对象(即mFirstTouchTarget为null)则拦截
intercepted = true;
}
...
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!intercepted) {
...
// 是否是Down事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
// 遍历全部子View
for (int i = childrenCount - 1; i >= 0; i--) {
// 获取子View
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
...
// 判断子View(child)能否接收该事件
// 判断依据:子View的状态为VISIBLE,且触发事件的位置在子View的范围内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
// 查看child在这之前有没有接收过事件,如果接收过,则newTouchTarget就不会为null
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// 通过dispatchTransformedTouchEvent()把事件ev传递给子View(child)
// 如果返回true,说明事件被child处理掉,创建一个新的Target,并加入到Target链表中
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
。。。(省略参数记录)
// 通过addTouchTarget()创建一个Target对象,加入到链表中
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 注意:这里设置标志位,下面会用到这个标志位
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
// 如果没有找到可以接受MotionEvent的Target,那就使用最先加入Target链表中的Target为事件接收对象
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// 如果没有找到Target来接收事件,那么把事件交由ViewGroup来处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 遍历Target链表
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
// 如果已把事件传递给Target,那么这里就不进行分发了
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
...
}
predecessor = target;
target = next;
}
}
...
}
return handled;
}
接下来分析View.dispatchTouchEvent( ),下方的流程图是View.dispatchTouchEvent( )的实现逻辑
对着上方的流程图分析源码
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
if (onFilterTouchEventForSecurity(event)) {
// 这里的逻辑很简单,逻辑如下:
// 如果onTouch()返回true,则不执行onTouchEvent()
// 如果返回false,则执行onTouchEvent()
// 注意:onTouch()方法是通过View.setOnTouchListener()设置的回调
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;
}
}
...
return result;
}
onTouchEvent()内部实现了点击事件的判断,这里就不分析了。
4、总结
触摸事件的分发流程现在已经分析完毕,这里总结一下:
首先事件从ViewRootImpl传递到Activity.dispatchTouchEvent(),事件到达Activity后,把事件传递给DecorView,而DecorView是FrameLayout布局,所以事件传递到ViewGroup.dispatchTouchEvent(),如果ViewGroup不消费事件(即ViewGroup.dispatchTouch( )返回false),则由Activity.onTouchEvent( )来处理。
在ViewGroup.dispatchTouchEvent( )中遍历子View,找到可接收该事件的View,并传递给子View。即调用View.dispatchTouchEvent( )方法,如果View.dispatchTouchEvent( )返回false(即不消费事件),则事件由ViewGroup.onTouchEvent( )来处理。
在View.dispatchTouchEvent( )内部,如果子View添加了Touch事件的监听,则执行onTouch( )方法,如果该方法返回false,继续执行onTouchEvent( ),如果还返回false说明子View不消费事件,事件就会被回传到ViewGroup,让ViewGroup选择下一个View,如果都没有View接受该事件,则由ViewGroup处理,这时会onTouchEvent( )来处理,如果onTouchEvent( )返回false(即ViewGroup也不消费事件),则由回传到Activity.onTouchEvent( )处理。