前面的博客中,我们通过例子分析了一下Android中事件传递的流程,
详细内容可以参考:Android触摸事件传递机制简要分析
贯穿整个Android的触摸事件分发的流程,基本可以抽象成以下的伪代码:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean handle = false;
if(onInterceptTouchEvent(ev)){
handle = onTouchEvent(ev);
}else{
handle = child.dispatchTouchEvent(ev);
}
return handle;
}
如果一个事件传递到了ViewGroup处,首先会判断当前ViewGroup是否要拦截事件,
即调用onInterceptTouchEvent()方法。
如果返回true,则表示ViewGroup拦截事件,
那么ViewGroup就会调用自身的onTouchEvent来处理事件;
如果返回false,表示ViewGroup不拦截事件,
此时事件会分发到它的子View处,即调用子View的dispatchTouchEvent方法。
如此反复直到事件被消耗掉。
本篇博客,我们以Android O的代码为例,看看对应流程的源码。
考虑到事件分发的流程较为繁琐,本篇博客主要针对Activity和ViewGroup部分。
一、Activity部分
系统有一个线程在循环收集屏幕硬件信息。
当用户触摸屏幕时,该线程会把从硬件设备收集到的信息,
封装成一个MotionEvent对象,然后把该对象存放到一个消息队列中。
与此对应,系统的另一个线程循环的读取消息队列中的MotionEvent,然后交给WMS去派发。
WMS把该事件派发给当前处于Active状态的Activity,即处于活动栈最顶端的Activity。
我们就从Activity的dispatchTouchEvent入手,看看整个事件分发的流程:
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空实现,其用途注释已经说的比较清楚了
//若需要了解用户点击界面,可以自行实现该接口
//Implement this method if you wish to know that the user has
//interacted with the device in some way while your activity is running.
onUserInteraction();
}
//首先进行事件分发,事件被消费掉就会返回true
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果事件没有没消费掉,那么就会调用Activity的onTouchEvent
return onTouchEvent(ev);
}
从上面的代码容易看出,Activity收到触摸事件后首先会进行分发;
如果事件在分发的过程中没被消费掉,最终会被Activity的onTouchEvent处理。
在继续分析之前,我们先看看Activity的getWindow函数:
public Window getWindow() {
//返回Activity持有的mWindow对象
return mWindow;
}
mWindow对象是Activity显示在界面上时创建的,如下所示:
final void attach(.....) {
........
//实际上是一个PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
........
}
由上面的代码易知,Activity分发的事件实际上交给了PhoneWindow。
对应分发事件的函数如下:
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor的类型为DecorView,在PhoneWindow初始化时得到
return mDecor.superDispatchTouchEvent(event);
}
DecorView继承FrameLayout,后者继承ViewGroup,
于是上述代码最终会将触摸事件递交给ViewGroup处理。
二、ViewGroup部分
接下来我们跟进ViewGroup的dispatchTouchEvent函数。
2.1 处理Accessibility Focus事件
public boolean dispatchTouchEvent(MotionEvent ev) {
.......
// 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);
}
.......
ViewGroup收到触摸事件后,首先需要处理特殊情况,
即携带FLAG_TARGET_ACCESSIBILITY_FOCUS的MotionEvent。
该标志位的含义可以参考注释:
/**
* 1、 Private flag indicating that this event was synthesized by the system and
* should be delivered to the accessibility focused view first.
*
* 2、 When being dispatched such an event is not handled by predecessors of the accessibility
* focused view and after the event reaches that view the flag is cleared and
* normal event dispatch is performed.
* .......
* /
从上述注释可以看出,当MotionEvent具有该Flag时,
若点击区域存在accessibility focused view(或ViewGroup),那么事件会优先被递交给这种View处理。
即其父View或ViewGroup,无法消费该MotionEvent(从代码来看,ViewGroup仍可以拦截)。
当accessibility focused view收到该MotionEvent后,就会调用上面的代码,
清除掉FLAG_TARGET_ACCESSIBILITY_FOCUS标签。
此后,MotionEvent就会按照普通的逻辑被分发和消费。
从上面的代码也可以看出,当MotionEvent携带Flag时,
会利用isAccessibilityFocusedViewOrHost函数,判断当前View是否有能力处理accessibility focused motion event。
如果满足条件,将会调用setTargetAccessibilityFocus函数,取消掉FLAG_TARGET_ACCESSIBILITY_FOCUS。
我们看看isAccessibilityFocusedViewOrHost函数:
//实际上定义在ViewGroup的父类View中
boolean isAccessibilityFocusedViewOrHost() {
//判断View是否具有PFLAG2_ACCESSIBILITY_FOCUSED
//或者利用view root判断当前View是否具有ACCESSIBILITY_FOCUSED能力
return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
.getAccessibilityFocusedHost() == this);
}
实际上当一个View调用requestAccessibilityFocus后,就会具有accessibility focus:
public boolean requestAccessibilityFocus() {
//ViewManager enable 且 View可视时,就可以申请accessibility focus
AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
if (!manager.isEnabled() || !manager.isTouchExplorationEnabled()) {
return false;
}
if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) {
return false;
}
if ((mPrivateFlags2 & PFLAG2_ACCESSIBILITY_FOCUSED) == 0) {
//置标志位
mPrivateFlags2 |= PFLAG2_ACCESSIBILITY_FOCUSED;
//在ViewRoot中记录
ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
viewRootImpl.setAccessibilityFocus(this, null);
}
//刷新
invalidate();
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
return true;
}
return false;
}
2.2 过滤事件
处理完特殊事件后, 需要进行安全性检测,如下代码所示:
//用于记录事件最终是否被处理
boolean handled = false;
//满足条件的事件才能被继续处理
if (onFilterTouchEventForSecurity(ev)) {
.....
onFilterTouchEventForSecurity实际上也定义于ViewGroup的父类View中:
public boolean onFilterTouchEventForSecurity(MotionEvent event) {
//判断View是否具有FILTER_TOUCHES_WHEN_OBSCURED
if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0
//判断事件是否具有FLAG_WINDOW_IS_OBSCURED
&& (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) {
// Window is obscured, drop this touch.
//该标志位的含义表示:当View所在的Window被遮挡且事件携带对应标志时,应该过滤收到的触摸事件
return false;
}
return true;
}
为了理解增加过滤判断的原因,我们需要看看FLAG_WINDOW_IS_OBSCURED的注释:
/**
* 1、window被覆盖时,收到其上window的触摸事件时,就会添加该标志位
* This flag indicates that the window that received this motion event is partly
* or wholly obscured by another visible window above it. This flag is set to true
* even if the event did not directly pass through the obscured area.
*
* 2、添加标志位的原因: 防止恶意应用覆盖在正常应用之上,截取用户信息或误导用户
* 例如:需要输入密码时,恶意应用添加透明界面,就可能截取用户输入的信息
* A security sensitive application can check this flag to identify situations in which
* a malicious application may have covered up part of its content for the purpose
* of misleading the user or hijacking touches. An appropriate response might be
* to drop the suspect touches or to take additional precautions to confirm the user's
* actual intent.
*/
2.3 拦截事件
如果触摸事件没有被过滤掉,那么就可以开始正常处理了,对应源码如下:
............
//首先得到触摸事件的类型
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//Handle an initial down.
//所有的触摸事件都以ACTION_DOWN开始
//因此,当收到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);
//一个MotionEvent从ACTION_DOWN开始,到ACTION_UP结束
//其间会有多个类型的事件传递到View
//这些事件传递的对象,会用一个链表TouchTargets记录起来
//当一个ACTION_DOWN发生时,该函数中会调用clearTouchTargets,
//清除旧有的记录
resetTouchState();
}
// Check for interception.
final boolean intercepted;
//如果收到新的ACTION_DOWN事件
//或者其它类型的事件,但之前已经找到传递View
//则进入常规拦截流程
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//mFirstTouchTarget指向前一个事件传递的View
//不为null, 说明之前已经有事件未被拦截
//首先需要判断ViewGroup是否允许拦截事件
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//允许拦截时调用onInterceptTouchEvent函数
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.
// 进入这个分支,以为收到非ACTION_DOWN事件,且mFirstTouchTarget = null
// 那么说明这个MotionEvent的ACTION_DOWN事件就被拦截了
// 那么之后的UP、MOVE等操作,都会被拦截
intercepted = true;
}
//If intercepted, start normal event dispatch.
//Also if there is already a view that is handling the gesture, do normal event dispatch.
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
..........
除了特殊的MotionEvent以外,默认情况下,
ViewGroup的onInterceptTouchEvent将返回false,
即不拦截MotionEvent。
2.4 查找处理ACTION_DOWN等事件的View
若触摸事件没有被截取,就需要查找处理事件的View了。
// Check for cancelation.
// 首先判断该事件是否需要被取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//判断是否可以将事件分发给多个子View
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0
//用于记录本次分发事件的目标
TouchTarget newTouchTarget = null;
//记录是否成功分发
boolean alreadyDispatchedToNewTouchTarget = false;
//如果事件没有被拦截或取消,就可以开始进行分发
if (!canceled && !intercepted) {
//与前面第一部分对应, 对于一个特殊的事件来说
//如果当前ViewGroup可以处理,在第一部分已经清除掉了flag
//若无法处理,则在这里查找是否存在可以处理的child view
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//开始处理ACTION_DOWN类型的事件
..................
从上面代码可以看出,当MotionEvent没有被拦截或取消时,才会执行后续的流程。
否则,将直接进入下一部分(即2.5小节)。
//对于以下类型的MotionEvent,才需要重新查找target View
//其中比较有代表性的就是ACTION_DOWN
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
//如果需要将事件分发给多个子View, 则得到actionIndex对应的pointerId
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.
// 清除idBitsToAssign对应target的旧有状态
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//开始进行查找
if (newTouchTarget == null && childrenCount != 0) {
//得到触摸事件对应的坐标,根据坐标才能得到可以处理该事件的child view
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.
// 看函数名就应该能知道含义
// 此处按顺序得到ViewGroup的child view
// 从变量名来看,得到的是先序遍历的结果
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
//此处对应child view按特定顺序绘制的情况
//需要参考getChildDrawingOrder和setChildrenDrawingOrderEnabled
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//逆序开始查找Child View
//这么做的原因是:
//后添加的View作为子View,被绘制在父View的上层
//于是,按照先序遍历时,后添加的子View肯定处于List的后端
//我们点击界面时,按照逻辑,肯定应该让最上面的View优先进行处理
//结合上面的原因,就应该让处于List末端的子View,优先处理MotionEvent
for (int i = childrenCount - 1; i >= 0; i--) {
//从preorderedList取出View
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//需要处理Accessibility Focus事件时,优先找出对应的View
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
//一旦找到, 就将触摸事件递交给该View
//此处置为null, 表明不再寻找具有Accessibility Focus能力的View
//因此,只有第一个具有Accessiliblity focus能力的View有机会处理这种类型的事件
childWithAccessibilityFocus = null;
//此处,将i重置为childrenCount - 1
//意味着若具有Accessibility Focus能力的View, 无法处理该MotionEvent
//那么将按照正常流程, 重新找出能够处理该MotionEvent的View
i = childrenCount - 1;
}
//判断View能否处理MotionEvent
//canViewReceivePointerEvents主要判断View是否可见(可见或在播放动画)
//isTransformedTouchPointInView主要判断MotionEvent的坐标是否落在View内
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
// 如果不能处理该View, 则跳过
//代码进入到这里,意味着:
//没有Accessibility Focus View
//或有Accessibility Focus View, 但无法处理该MotionEvent
//无论哪种情况,都去掉标志,将MotionEvent当作普通事件处理
ev.setTargetAccessibilityFocus(false);
continue;
}
//判断View是否已经接收过该事件(其它的类型)
//如果接收过该事件,那么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, 退出查找过程
break;
}
//清除child view的PFLAG_CANCEL_NEXT_UP_EVENT
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent的主要功能,是按需调整MotionEvent,
//然后递交给child view的dispatchTouchEvent处理
//当child为null时,由ViewGroup自己处理
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//一旦事件被消耗掉,则更新相关的状态
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//新的TouchTarget被记录到整个链表中, 并作为新的mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
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);
}
if (preorderedList != null) preorderedList.clear();
}
//处理没有找到newTouchTarget的情况
//将idBitsToAssign付给前一次的TouchTargets
//这么做的目的不是很清楚
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;
}
}
............
2.5 其它
对于ACTION_DOWN等类型的事件来说,尝试查找负责处理的View后,
需要开始进行后续的收尾工作。
对于其它类型的事件而言,这里是处理的起点。
//Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
// 分发MotionEvent失败,没有找到mFirstTouchTarget, 该事件将被递交给ViewGroup处理
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.
// 进入到这个分支时,可能是ACTION_DOWN事件,也可能是其它类型的事件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//轮询链表,找到处理事件的target
while (target != null) {
final TouchTarget next = target.next;
//这个if针对的是已经处理的ACTION_DOWN事件, 将handled置为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//针对未处理的ACTION_DOWN或其它类型事件
//判断是否取消
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//分发给子视图
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果某个视图cancel, 就将其从链表中移除
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
//更新“指针”
predecessor = target;
target = next;
}
}
// 后续收尾工作,主要根据事件类型、处理的结果,更新一些状态
...............
三、总结
至此,我们已经结合源码大致了解ViewGroup的事件分发逻辑。
这部分内容细节比较多,但整体逻辑比较清晰,大致如下图所示:
ViewGroup有拦截事件的能力,同时也有分发事件给Child View的能力。
这导致ViewGroup的事件处理流程比较复杂。
相对而言,View处理触摸事件的流程较为简单,我们在下一篇博客中再来分析。