1 Touch事件底层传递过程
Touch的整体派发流程,遵循如下逻辑:
- Android中Touch事件的分发分服务端和应用端。Server端由WindowManagerService(WMS,窗体管理服务)负责采集和分发,client端则是由ViewRootImpl(内部有个mView变量指向View树的根 ,负责控制View树的UI绘制和事件消息的分发)负责分发的。
- WMS在启动之后,会在native层启动两个线程:InputReaderThread和InputDispatchThread。前者用来读取输入事件,后者用来分发输入事件,输入事件经过nativie层的层层传递,终于会传递到java层的ViewRootImpl中。调用ViewPostImeInputStage
(ViewRootImpl的内部类)中的各个方法来分发不同的事件,而Touch事件是在processPointerEvent方法进行分发的。 -
processPointerEvent方法中调用mView.dispatchPointerEvent(event)方法,进行判断event类型,如果是Touch事件则调用dispatchTouchEvent将该事件分发DecorView。
① Touch事件从server端传递到client端採用的IPC方式并非Binder,而是共享内存和管道。至于为什么不採用Binder,应该是共享内存的效率更高,而管道(两个管道,分别负责不同方向的读和写)仅仅负责通知是否有事件发生,传递的仅仅是一个非常easy的字符串,因此并不会太多地影响到IPC的效率。
② 在sever端中,InputReader和InputDispatcher是native 层的两个线程。InputReader不断地从EventHub中读取事件,InputDispatcher则不断地分发InputReader读取到的事件,而实际的分发操作时在InputPublish中进行的。InputPublish里面保存的有一个指向server端InputChannel端的指针和一个指向ShareMemory(共享内存)的指针。
③ 当有事件要分发时,它会将事件写入到ShareMemory中,而且传递一个特定的字符串给InputChannel,由inutChannel将该字符串写入到管道中。在client端,一旦InputChannel从管道中读取到有事件分发过来,便会通知InPutConsumer从ShareMemory中读取详细的事件,并传递到framework层的InputQueue中。一旦事件消费完毕,client端会通过管道告诉server端,事件已经消费完毕,流程与上面的相似。
2 Touch事件应用层传递过程
2.1 基础信息概念
2.1.1 MotionEvent
包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等,分为如下事件:
- MotionEvent.ACTION_DOWN
当我们手指按下屏幕的第一个事件便是ACTION_DOWN了,也就是意味着事件的开始。 - MotionEvent.ACTION_MOVE
当我们手指按下屏幕后,在屏幕上滑动的过程,此事件就会不断的触发。 - MotionEvent.ACTION_UP
此事件在我们手指从屏幕抬起的时候会触发。 - MotionEvent.ACTION_CANCEL
这个事件说起来稍微复杂一点,举个栗子:当我们的外层View将事件传递给内层View去处理时,外层View的拦截方法一般会返回false。但是当某个条件触发后,外层View想自己处理接下来的事件,就拦截了事件分发,此时内层View就会收到ACTION_CANCEL的事件。 - MotionEvent.ACTION_OUTSIDE
这个事件我们不常用到,考虑这种场景:我们有一个Diallog弹出,当我们按Dialog以外的屏幕将Dialog消失掉。这个时候可以考虑监听这个事件,要想使用这个事件我们必须对当前的Window设置一个Flag:FLAG_WATCH_OUTSIDE_TOUCH。
2.1.2 处理方法
- dispatchTouchEvent(MotionEvent event)
这个方法是用来处理向下分发事件逻辑的,会调用onIntercepteTouchEvent和onTouchEvent方法。 - onInterceptTouchEvent(MotionEvent event)
用来申明是否拦截事件继续向下分发,如果返回true,事件将不会继续向下分发,而是交由自己的onTouchEvent方法处理。 - onTouchEvent(MotionEvent event)
事件处理的方法。 - onTouch(MotionEvent event)
这个方法是在我们对某一个setOnTouchListener时回调,也就是在传递事件的时候,在交给View本身的onTouchEvent处理之前判断是否有监听的TouchListener,如果有优先调用TouchListener的onTouch方法处理。
2.1.3 大概处理流程
消息分发流程
从上到下,从父到子:Activity -> PhoneWindow -> DecorView -> ViewGroup1->ViewGroup1的子ViewGroup2->…->Target View消息响应流程
从下到上,从子到父:Target View->…->ViewGroup1的子ViewGroup2->ViewGroup1-> DecorView -> PhoneWindow -> Activity
2.2 源码分析
2.2.1 Activity中分发过程
Activity.dispatchTouchEvent(..)
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
代码会走到PhoneWindow.superDispatchTouchEvent(...),看一下代码:
PhoneWindow.superDispatchTouchEvent(..)
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
调用了DecorView的superDispatchTouchEvent方法,再进去看看:
DecorView.superDispatchTouchEvent(..)
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
DecorView的dispatchTouchEvent终于都会调用到自己父亲FrameLayout的dispatchTouchEvent方法。而我们在FrameLayout中找不到dispatchTouchEvent方法,所以会去运行ViewGroup的dispatchTouchEvent方法。
2.2.2 Activity中事件消费
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
PhoneWindow.shouldCloseOnTouch(..)
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
&& isOutOfBounds(context, event) && peekDecorView() != null) {
return true;
}
return false;
}
假设设置了mCloseOnTouchOutside属性为true(相应xml中的android:windowCloseOnTouchOutside属性),且当前事件为down事件,且down事件发生在该Activity范围之外,而且DecorView不为null,就返回true。非常明显dialog形式的Activity可能会发生这种情况。
2.2.3 ViewGroup.dispatchTouchEvent(...)
public boolean dispatchTouchEvent(MotionEvent ev) {
//调试用的
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//handled为返回值,true表示是否有view消费了该事件
boolean handled = false;
//是否要过滤掉该Touch事件
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//1、清空掉曾经消费事件的目标view,这里主要指清空掉mFirstTouchTarget链表(保存接受Touch事件的单链表),并将mFirstTouchTarget置为null。
//2、重置触摸状态,重置了disallowIntercept和mPrivateFlags相应的标志位
// 一般在发生app的切换或者ANR等情况时代码会走到这里,这一点源代码的注释里也有。
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 标记是否要拦截该Touch事件:true表示拦截,false表示不拦截
final boolean intercepted;
//假设当前事件为down事件或者可接受Touch事件的链表不为空,就运行if语句里的逻辑。这里注意:
//1、因为down事件是一个完整事件序列的的起点,因此当发生down事件时,逻辑走到这里。由于还没有找到消费down事件的view,因此mFirstTouchTarget为null。
//2、对于后面的move和up事件,假设前面的down事件被某个view消费掉了,则mFirstTouchTarget不为null。
// 上面两种情况都会使代码进入if分支中来。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
//是否不同意拦截,默认得false,可以通过requestDisallowInterceptTouchEvent方法来设置其值
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
// 假设同意拦截,则onInterceptTouchEvent有效,依据我们覆写的该方法的返回值来推断是否拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// 假设当前事件不是down事件,且之前在分发down事件的时候没有找到消费down事件的目标view,也即mFirstTouchTarget为null,则直接拦截该事件。
intercepted = true;
}
// 检查当前事件是否被取消
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;
//保存消费事件的目标View所相应的 TouchTarget对象
TouchTarget newTouchTarget = null;
//事件是否已经分发到了目标View中
boolean alreadyDispatchedToNewTouchTarget = false;
// 假设没有被取消而且没有被拦截,就分发该事件。
// 注意仅仅有down事件才会走到这里去分发,对于move和up实践,则会跳过这里。直接从 mFirstTouchTarget链表中找到之前消耗down事件的目标View,直接将move和up事件传递给它
if (!canceled && !intercepted) {
// 仅仅有down事件会走到这里
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//Touch事件的index,对于单点触控,一直为0,这里不用深究
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);
//该ViewGroup中子View的个数
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//当前事件发生的位置
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//保存该ViewGroup中子View
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
//遍历子View,找到能消费该down事件的子View,对于类型为ViewGroup的子View,在分发的时候会递归调用到它的dispatchTouchEvent方法继续进行分发。
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//假设当前子View能够消费该down事件而且该down事件发生的位置在当前子View的范围内,则继续运行。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//接受down事件的child是否已经在mFirstTouchTarget链表中,假设在的话,说明child已经消费掉了该down事件,直接跳出循环。
// 我在写demo跟代码时,没有一次走到这里的,临时不是非常清楚
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//假设该child还没有消费掉该down事件,就直接调用dispatchTransformedTouchEvent方法将该down事件传递给该child,该方法里面会调用到child的dispatchTouchEvent方法
//假设该方法返回true,则说明child消费掉了该down事件,那么就运行if语句里的逻辑,将child添加到mFirstTouchTarget链表的表头,而且将该表头赋值给newTouchTarget(參见addTouchTarget方法)
//同时将alreadyDispatchedToNewTouchTarget置为true,说明有子view消费掉了该down事件。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = childIndex;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//假设newTouchTarget为null而且 mFirstTouchTarget不为null,即没找到子View来消耗该事件,可是保存Touch事件的链表不为空,
//则把newTouchTarget赋值为最早加进(Least Recently added)mFirstTouchTarget链表的target。临时没全然搞明确这里的详细意思,跟代码都没有走到这里。
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;
}
}
}
//后面在处理MOVE和UP事件时会直接依据上次的DOWN是否被消费掉来直接进行相应的处理
if (mFirstTouchTarget == null) {
//假设没有子view接受该事件,则直接把当前的ViewGroup当作普通的View看待,把事件传递给自己(详见dispatchTransformedTouchEvent方法,注意第三个參数传递的是null)
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
//假设之前的DOWN事件被子view消费掉了,就会直接找到该子View相应的Target,将MOVE或UP事件传递给它们。
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//假设该事件已经被消费掉了,则不再进行分发(该分支主要针对DOWN事件)
handled = true;
} else {
//直接将DOWN或UP事件分发给目标Target(之前消费DOWN事件的view相应的target,注意dispatchTransformedTouchEvent的第三个參数为target.child)
//这里要注意的是,假设intercepted为true,也就是MOVE或UP事件被拦截了,则cancelChild为true,则会分发一次CANCLE事件(注意dispatchTransformedTouchEvent的第二个參数)。
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;
}
}
// 假设当前事件是CANCLE或UP会调用resetTouchState方法。清空Touch状态,同时清空mFirstTouchTarget链表,并将mFirstTouchTarget置为null
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);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
整体流程可以概括如下图所示:
2.2.4 View.dispatchTouchEvent(...)
public boolean dispatchTouchEvent(MotionEvent event) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
if (onTouchEvent(event)) {
return true;
}
}
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
return false;
}
先推断该View有没有绑定OnTouchListener监听器,如果绑定且消费掉事件则直接返回,所以Touch事件的先后顺序:onTouch先于onTouchEvent。
2.2.5 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();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
//view如果是disabled状态,该view仍然消费该事件,但是不会做出UI的相应
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
switch (action) {
case MotionEvent.ACTION_UP:
...
break;
case MotionEvent.ACTION_DOWN:
...
}
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
2.3 Touch事件派发模型
- dispatchTouchEvent(...)方法
- return true:消费掉事件,终止传递。
- return false: 事件停止往子View传递,将事件传递给上一级View的onTouchEvent()方法。如果是Activity的dispatchTouchEvent()方法,则也是消费掉事件,终止传递。
- return super:如果是Activity,则传给下一级view(viewGroup)的dispatchTouchEvent;如果是ViewGroup,则传给自己的onInterceptTouchEvent();如果是View,则传给自己的onTouchEvent()。
- onTouchEvent(...)方法
- return true:消费掉事件,终止传递。
- return false/super:将事件传递给上一级view的onTouchEvent()方法。
- onInterceptTouchEvent(...)方法
- return true:将事件传递给ViewGroup自己的onTouchEvent()方法处理。
- return false/super:将事件传递给下一级View的dispatchTouchEvent()。
说明:为什么只有ViewGroup有onInterceptTouchEvent()方法呢,从上面的整个触摸事件分发传递机制我们可以发现,ViewGroup本身的dispatchTouchEvent()方法无论返回什么都不能将事件传递给自己的onTouchEvent()方法处理,那就只好设计了一个这样子的方法,作为拦截器,拦截事件交给自己处理了。只要onInterceptTouchEvent()return true就可以实现触摸事件拦截。
3 举例说明
3.2 自定义视图
自定义视图ViewGroup-1,ViewGroup-2,View,每个自定义类复写其父类方法。视图层级结构如下:
按照上面的分析,其流程如下:
说明:只有调用super方法,on每种类型的分析过程可以参考链接[5]
3.2 OnTouchListener接口
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
return true;
}
结论:onTouchListener的onTouch方法优先级比onTouchEvent高,会先触发。假若onTouchListener中的onTouch方法返回true,表示此次事件已经被消费了,那onTouchEvent是接收不到消息的。假如onTouch方法返回false,会接着触发onTouchEvent。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。
参考链接:
[1] Android Touch事件传递机制全面解析 ★
[2] 进阶必备-Android事件分发机制 ★
[3] Android touch 事件传递机制 ★
[4] View事件传递 touch事件分发 √
[5] 一步步探索学习Android Touch事件分发传递机制(一) ★
[6] Android Deeper(00) - Touch事件分发响应机制 ★
[7] 图解 Android 事件分发机制 ★√
[8] Activity touch事件传递流程分析
[9] View事件分发机制 √