Android O: 触摸事件传递流程源码分析(上)
前面的博客中,我们通过例子分析了一下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;//判断是否可以将事件分发给多个子Viewfinal 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_DOWNif (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 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处理触摸事件的流程较为简单,我们在下一篇博客中再来分析。
更多相关文章
- Android 上层界面到内核代码的完整的流程分析,以alarm为例子
- java/android 使用swig编译c/c++ 代码类型转换
- 解决android listview中OnItemClickListener事件和里面button点
- Android 启动Activity面流程(Android 9.0)
- Android触摸屏事件派发机制详解与源码分析
- Android事件分发机制练习---打造属于自己的瀑布流
- Android 开机启动流程分析
- Android事件处理的两种模型
- Android 监听软键盘弹起和收起事件