Android 事件分发机制详解
2014-10-08 14:10
459 查看
更多内容请参照我的个人站点: http://stackvoid.com/
网上很多关于Android事件分发机制的解释,大多数描述的都不够清晰,没有吧来龙去脉搞清楚,本文将带你从Touch事件产生到Touch事件被消费这一全过程作全面的剖析。
这部分牵扯到硬件和Linux内核部分;我们简单讲述一下这部分内容,如果有兴趣的话可以参考这篇文章。
触摸事件是由Linux内核的一个Input子系统来管理的(InputManager),Linux子系统会在
Android系统的一个系统Service:
WindowState,通过IWindow代理将此消息发送到IWindow服务端(IWindow.Stub子类),这个IWindow.Stub属于ViewRoot(这个类继承Handler,主要用于连接PhoneWindow和WindowManagerService),所以事件就传到了ViewRoot.dispatchPointer()中.
我们来看一下ViewRoot的dispatchPointer方法:
dispatchPointer方法就是把这个事件封装成Message发送出去,在ViewRoot Handler的handleMessage中被处理,其调用了mView.dispatchTouchEvent方法(mView是一个PhoneWindow.DecorView对象),PhoneWindow.DecorView继承FrameLayout(FrameLayout继承ViewGroup,ViewGroup继承自View),DecorView里的dispatchTouchEvent方法如下. 这里的Callback的cb其实就是Activity的attach()方法里的设置回调。
也就是说,正常情形下,当前的Activity就是这里的cb,即调用了Activity的dispatchTouchEvent方法。
下面来分析一下从Activity到各个子View的事件传递和处理过程。
首先先分析Activity的dispatchTouchEvent方法。
onUserInteraction() 是一个空方法,开发者可以根据自己的需求覆写这个方法(这个方法在一个Touch事件的周期肯定会调用到的)。如果判断成立返回True,当前事件就不在传播下去了。 superDispatchTouchEvent(ev) 这个方法做了什么呢? getWindow().superDispatchTouchEvent(ev) 也就是调用了
mDecor.superDispatchTouchEvent(event),在内部类 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中调用super.dispatchTouchEvent(event),而DecorView继承自ViewGroup(通过FrameLayout,FrameLayout没有dispatchTouchEvent),最终调用的是ViewGroup的dispatchTouchEvent方法。
小结一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通过 CallBack 调用了 Activity 的 dispatchTouchEvent 方法,在 Activity 这里,我们可以重写 Activity 的dispatchTouchEvent 方法阻断 touch事件的传播。接着在Activity里的dispatchTouchEvent 方法里,事件又再次传递到DecorView,DecorView通过调用父类(ViewGroup)的dispatchTouchEvent
将事件传给父类处理,也就是我们下面要分析的方法,这才进入网上大部分文章讲解的touch事件传递流程。
为什么要从 PhoneWindow.DecorView 中 传到 Activity,然后在传回 PhoneWindow.DecorView 中呢? 主要是为了方便在Activity中通过控制dispatchTouchEvent 来控制当前Activity 事件的分发, 下一篇关于数据埋点文章就应用了这个机制
OK,我们要重点分析的就是ViewGroup中的dispatchTouchEvent方法。
我们来总结一下 ViewGroup 的 dispatchTouchEvent 的调用过程。
首先判断此 MotionEvent 能否被拦截,如果是的话,能调用我们覆写 onInterceptTouchEvent来处理拦截到的事件;如果此方法返回TRUE,表示需要拦截,那么事件到此为止,就不会传递到子View中去。这里要注意,onInterceptTouchEvent 方法默认是返回FALSE。
若没有拦截此Event,首先找到此ViewGroup中所有的子View,通过方法 canViewReceivePointerEvents和isTransformedTouchPointInView,对每个子View通过坐标(Event事件坐标和子View坐标比对)计算,找到坐标匹配的View。
调用dispatchTransformedTouchEvent方法,处理Event事件。
假设这个子View是一个Button,会调用Button.dispatchTouchEvent 方法,Button和它的父类TextView都没有dispatchTouchEvent方法,只能继续看父类View了,其实最终调用的还是View.dispatchTouchEvent 方法。
我们继续分析View.dispatchTouchEvent 方法。mOnTouchListener 是OnTouchListener对象,由setOnTouchListener 方法设置;
若当前ListenerInfo 方法初始化并且 li.mOnTouchListener 的值不为空且ENABLE掩码为Enable,那么调用mOnTouchListener(this,event)方法。
若onTouch方法返回true,则表示被消费,不会继续传递下去;返回false,表示时间还没被消费,继续传递到 onTouchEvent 这个方法里。
若状态不是CLICKABLE,那么会直接跳过判断执行return false,这意味着后续的touch事件不会再传递过来了。而大家注意看,只要是CLICKABLE,那么无论case哪个节点,最后都是return true,这样就保证了后续事件可以传递过来。 很明显在onTouchEvent 方法里面,主要就是判断应该执行哪个操作,是长按还是单击,然后去执行对应的方法。我们看看如果是单击,执行的方法:
其实就是调用了我们OnClickListener里面的onClick方法。所以说,当onTouch() 和 onClick()都存在时候,肯定是先执行onTouch,之后再执行onClick;如果onTouch 把事件截获直接return true,那么 onClick 方法就不会执行了。 到这里,整个touch事件的传递过程我们就分析完了。
Touch事件一般调用过程总结
用户点击屏幕产生Touch(包括DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent
-> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消费结束
Android
FrameWork——Touch事件派发过程详解
android的窗口机制分析------事件处理
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
网上很多关于Android事件分发机制的解释,大多数描述的都不够清晰,没有吧来龙去脉搞清楚,本文将带你从Touch事件产生到Touch事件被消费这一全过程作全面的剖析。
产生Touch事件
这部分牵扯到硬件和Linux内核部分;我们简单讲述一下这部分内容,如果有兴趣的话可以参考这篇文章。
传递Touch事件
触摸事件是由Linux内核的一个Input子系统来管理的(InputManager),Linux子系统会在/dev/input/这个路径下创建硬件输入设备节点(这里的硬件设备就是我们的触摸屏了)。当手指触动触摸屏时,硬件设备通过设备节点像内核(其实是InputManager管理)报告事件,
InputManager经过处理将此事件传给
Android系统的一个系统Service:
WindowManagerService。
WindowManagerService调用dispatchPointer()从存放WindowState的z-order顺序列表中找到能接收当前touch事件的
WindowState,通过IWindow代理将此消息发送到IWindow服务端(IWindow.Stub子类),这个IWindow.Stub属于ViewRoot(这个类继承Handler,主要用于连接PhoneWindow和WindowManagerService),所以事件就传到了ViewRoot.dispatchPointer()中.
我们来看一下ViewRoot的dispatchPointer方法:
1 public void dispatchPointer(MotionEvent event, long eventTime, 2 boolean callWhenDone) { 3 Message msg = obtainMessage(DISPATCH_POINTER); 4 msg.obj = event; 5 msg.arg1 = callWhenDone ? 1 : 0; 6 sendMessageAtTime(msg, eventTime); 7 }
dispatchPointer方法就是把这个事件封装成Message发送出去,在ViewRoot Handler的handleMessage中被处理,其调用了mView.dispatchTouchEvent方法(mView是一个PhoneWindow.DecorView对象),PhoneWindow.DecorView继承FrameLayout(FrameLayout继承ViewGroup,ViewGroup继承自View),DecorView里的dispatchTouchEvent方法如下. 这里的Callback的cb其实就是Activity的attach()方法里的设置回调。
1 //in file PhoneWindow.java 2 public boolean dispatchTouchEvent(MotionEvent ev) { 3 final Callback cb = getCallback(); 4 if (mEnableFaceDetection) { 5 int pointCount = ev.getPointerCount(); 6 7 switch (ev.getAction() & MotionEvent.ACTION_MASK) { 8 case MotionEvent.ACTION_POINTER_DOWN: 9 10 ....... 11 12 return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) 13 : super.dispatchTouchEvent(ev); 14 } 15 //in file Activity.java -> attach() 16 mFragments.attachActivity(this, mContainer, null); 17 mWindow = PolicyManager.makeNewWindow(this); 18 mWindow.setCallback(this);//设置回调
也就是说,正常情形下,当前的Activity就是这里的cb,即调用了Activity的dispatchTouchEvent方法。
下面来分析一下从Activity到各个子View的事件传递和处理过程。
首先先分析Activity的dispatchTouchEvent方法。
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 3 onUserInteraction(); 4 } 5 if (getWindow().superDispatchTouchEvent(ev)) { 6 return true; 7 } 8 return onTouchEvent(ev); 9 }
onUserInteraction() 是一个空方法,开发者可以根据自己的需求覆写这个方法(这个方法在一个Touch事件的周期肯定会调用到的)。如果判断成立返回True,当前事件就不在传播下去了。 superDispatchTouchEvent(ev) 这个方法做了什么呢? getWindow().superDispatchTouchEvent(ev) 也就是调用了
PhoneWindow.superDispatchTouchEvent方法,而这个方法返回的是
mDecor.superDispatchTouchEvent(event),在内部类 DecorView(上文中的mDecor) 的superDispatchTouchEvent 中调用super.dispatchTouchEvent(event),而DecorView继承自ViewGroup(通过FrameLayout,FrameLayout没有dispatchTouchEvent),最终调用的是ViewGroup的dispatchTouchEvent方法。
小结一下。Event事件是首先到了 PhoneWindow 的 DecorView 的 dispatchTouchEvent 方法,此方法通过 CallBack 调用了 Activity 的 dispatchTouchEvent 方法,在 Activity 这里,我们可以重写 Activity 的dispatchTouchEvent 方法阻断 touch事件的传播。接着在Activity里的dispatchTouchEvent 方法里,事件又再次传递到DecorView,DecorView通过调用父类(ViewGroup)的dispatchTouchEvent
将事件传给父类处理,也就是我们下面要分析的方法,这才进入网上大部分文章讲解的touch事件传递流程。
为什么要从 PhoneWindow.DecorView 中 传到 Activity,然后在传回 PhoneWindow.DecorView 中呢? 主要是为了方便在Activity中通过控制dispatchTouchEvent 来控制当前Activity 事件的分发, 下一篇关于数据埋点文章就应用了这个机制
OK,我们要重点分析的就是ViewGroup中的dispatchTouchEvent方法。
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent ev) { 3 //...... 4 5 // Check for interception. 6 final boolean intercepted;//是否被拦截 7 if (actionMasked == MotionEvent.ACTION_DOWN 8 || mFirstTouchTarget != null) {//Touch按下事件 9 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 10 if (!disallowIntercept) { 11 intercepted = onInterceptTouchEvent(ev);//判断消息是否需要被viewGroup拦截,这个方法我们可以覆写, 12 //覆写生效的前提是 disallowIntercept 为FALSE,否则写了也没用 13 ev.setAction(action); // restore action in case it was changed 14 } else {//不允许拦截 15 intercepted = false; 16 } 17 } else { 18 // There are no touch targets and this action is not an initial down 19 // so this view group continues to intercept touches. 20 //这个操作不是一开始down事件,我们把它置为TRUE,拦截之 21 intercepted = true; 22 } 23 24 // Check for cancelation. 25 final boolean canceled = resetCancelNextUpFlag(this) 26 || actionMasked == MotionEvent.ACTION_CANCEL; 27 28 //......... 29 30 final int childrenCount = mChildrenCount;//ViewGroup中子View的个数 31 if (newTouchTarget == null && childrenCount != 0) { 32 final float x = ev.getX(actionIndex);//获取坐标,用来比对 33 final float y = ev.getY(actionIndex); 34 // Find a child that can receive the event. 35 // Scan children from front to back. 36 final View[] children = mChildren;//获取viewgroup所有的子view 37 38 final boolean customOrder = isChildrenDrawingOrderEnabled();//子View的绘制顺序 39 ////从高到低遍历所有子View,找到能处理touch事件的child View 40 for (int i = childrenCount - 1; i >= 0; i--) { 41 final int childIndex = customOrder ? 42 getChildDrawingOrder(childrenCount, i) : i;//根据Order获取子view 43 final View child = children[childIndex]; 44 //判断是不是我们需要的View 45 if (!canViewReceivePointerEvents(child) 46 || !isTransformedTouchPointInView(x, y, child, null)) { 47 continue; 48 } 49 50 newTouchTarget = getTouchTarget(child);//从链表里找子view 51 if (newTouchTarget != null) {//找到子view 52 // Child is already receiving touch within its bounds. 53 // Give it the new pointer in addition to the ones it is handling. 54 //已经找到,循环结束,目标就是newTouchTarget 55 newTouchTarget.pointerIdBits |= idBitsToAssign; 56 break; 57 } 58 59 //....... 60 } 61 } 62 } 63 } 64 65 // Dispatch to touch targets. 66 if (mFirstTouchTarget == null) { 67 // No touch targets so treat this as an ordinary view. 68 /*dispatchTransformedTouchEvent方法中,如果child是null,那么就调用super.dispatchTouchEvent, 69 *也就是ViewGroup的父类View的dispatchTouchEvent(如果我们在前面拦截了touch事件,那么就会这样处理), 70 *如果不是null,则调用child.dispatchTouchEvent。 71 **/ 72 handled = dispatchTransformedTouchEvent(ev, canceled, null, 73 TouchTarget.ALL_POINTER_IDS); 74 } else { 75 //.... 76 } 77 //........ 78 return handled; 79 } 80 /** 81 * Transforms a motion event into the coordinate space of a particular child view, 82 * filters out irrelevant pointer ids, and overrides its action if necessary. 83 * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. 84 */ 85 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, 86 View child, int desiredPointerIdBits) { 87 final boolean handled; 88 89 //…… 90 91 // Perform any necessary transformations and dispatch. 92 if (child == null) { 93 handled = super.dispatchTouchEvent(transformedEvent); 94 } else { 95 //…… 96 handled = child.dispatchTouchEvent(transformedEvent); 97 } 98 99 // Done. 100 //…… 101 return handled; 102 }
我们来总结一下 ViewGroup 的 dispatchTouchEvent 的调用过程。
首先判断此 MotionEvent 能否被拦截,如果是的话,能调用我们覆写 onInterceptTouchEvent来处理拦截到的事件;如果此方法返回TRUE,表示需要拦截,那么事件到此为止,就不会传递到子View中去。这里要注意,onInterceptTouchEvent 方法默认是返回FALSE。
若没有拦截此Event,首先找到此ViewGroup中所有的子View,通过方法 canViewReceivePointerEvents和isTransformedTouchPointInView,对每个子View通过坐标(Event事件坐标和子View坐标比对)计算,找到坐标匹配的View。
调用dispatchTransformedTouchEvent方法,处理Event事件。
1 //ViewGroup.java dispatchTransformedTouchEvent方法截取 2 if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { 3 event.setAction(MotionEvent.ACTION_CANCEL); 4 if (child == null) {//Event事件被截获,调用父类View的dispatchTouchEvent方法 5 handled = super.dispatchTouchEvent(event); 6 } else { 7 handled = child.dispatchTouchEvent(event);//调用子View的dispatchTouchEvent方法 8 } 9 event.setAction(oldAction); 10 return handled; 11 }
假设这个子View是一个Button,会调用Button.dispatchTouchEvent 方法,Button和它的父类TextView都没有dispatchTouchEvent方法,只能继续看父类View了,其实最终调用的还是View.dispatchTouchEvent 方法。
我们继续分析View.dispatchTouchEvent 方法。mOnTouchListener 是OnTouchListener对象,由setOnTouchListener 方法设置;
1 public boolean dispatchTouchEvent(MotionEvent event) { 2 if (mInputEventConsistencyVerifier != null) { 3 mInputEventConsistencyVerifier.onTouchEvent(event, 0); 4 } 5 6 if (onFilterTouchEventForSecurity(event)) { 7 //noinspection SimplifiableIfStatement 8 ListenerInfo li = mListenerInfo;//View 内部类,管理一些listener 9 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED 10 && li.mOnTouchListener.onTouch(this, event)) { 11 return true; 12 } 13 14 if (onTouchEvent(event)) { 15 return true; 16 } 17 } 18 19 if (mInputEventConsistencyVerifier != null) { 20 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 21 } 22 return false;//没有消费掉,只能返回false,让调用者来处理了。 23 } 24 /** 25 * Register a callback to be invoked when a touch event is sent to this view. 26 * @param l the touch listener to attach to this view 27 */ 28 public void setOnTouchListener(OnTouchListener l) { 29 getListenerInfo().mOnTouchListener = l; 30 }
若当前ListenerInfo 方法初始化并且 li.mOnTouchListener 的值不为空且ENABLE掩码为Enable,那么调用mOnTouchListener(this,event)方法。
boolean onTouch(View v, MotionEvent event)这个方法是在View的内部接口 OnTouchListener中的,是一个空方法,需要用户自己来实现。拿一个Button来举例;我们覆写的onTouch()方法在这里被调用。
1 button.setOnTouchListener(new OnTouchListener() { 2 @Override 3 public boolean onTouch(View v, MotionEvent event) { 4 //实现自己的功能 5 return true; 6 } 7 });
若onTouch方法返回true,则表示被消费,不会继续传递下去;返回false,表示时间还没被消费,继续传递到 onTouchEvent 这个方法里。
1 public boolean onTouchEvent(MotionEvent event) { 2 //…… 3 if (((viewFlags & CLICKABLE) == CLICKABLE || 4 (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { 5 switch (event.getAction()) { 6 case MotionEvent.ACTION_UP: 7 //…… 8 //如果没有触发长按事件,手指动作是up,则执行performClick()方法 9 if (!mHasPerformedLongPress) { 10 // This is a tap, so remove the longpress check 11 removeLongPressCallback(); 12 13 // Only perform take click actions if we were in the pressed state 14 if (!focusTaken) { 15 // Use a Runnable and post this rather than calling 16 // performClick directly. This lets other visual state 17 // of the view update before click actions start. 18 //这里判断并去执行单击事件 19 if (mPerformClick == null) { 20 mPerformClick = new PerformClick(); 21 } 22 if (!post(mPerformClick)) { 23 performClick(); 24 } 25 } 26 } 27 28 break; 29 case MotionEvent.ACTION_DOWN: 30 //…… 31 //是否触发长按事件是在这里判断的,具体细节我就不贴出来了 32 checkForLongClick(0); 33 //…… 34 break; 35 //…… 36 } 37 return true; 38 } 39 40 return false; 41 }
若状态不是CLICKABLE,那么会直接跳过判断执行return false,这意味着后续的touch事件不会再传递过来了。而大家注意看,只要是CLICKABLE,那么无论case哪个节点,最后都是return true,这样就保证了后续事件可以传递过来。 很明显在onTouchEvent 方法里面,主要就是判断应该执行哪个操作,是长按还是单击,然后去执行对应的方法。我们看看如果是单击,执行的方法:
1 public boolean performClick() { 2 //…… 3 4 ListenerInfo li = mListenerInfo; 5 if (li != null && li.mOnClickListener != null) { 6 //播放点击音效 7 playSoundEffect(SoundEffectConstants.CLICK); 8 //执行onClick方法 9 li.mOnClickListener.onClick(this); 10 return true; 11 } 12 13 return false; 14 }
其实就是调用了我们OnClickListener里面的onClick方法。所以说,当onTouch() 和 onClick()都存在时候,肯定是先执行onTouch,之后再执行onClick;如果onTouch 把事件截获直接return true,那么 onClick 方法就不会执行了。 到这里,整个touch事件的传递过程我们就分析完了。
Touch事件一般调用过程总结
用户点击屏幕产生Touch(包括DOWN、UP、MOVE,本文分析的是DOWN)事件 -> InputManager -> WindowManagerService.dispatchPointer() -> IWindow.Stub -> ViewRoot.dispatchPointer() -> PhoneWindow.DecorView.dispatchTouchEvent() -> Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent
-> PhoneWindow.DecorView.superDispatchTouchEvent -> ViewGroup.dispatchTouchEvent() -> ViewGroup.dispatchTransformedTouchEvent() -> 子View.dispatchTouchEvent() -> 子View.onTouch() -> 子View.onTouchEvent() -> 事件被消费结束
有用的参考
AndroidFrameWork——Touch事件派发过程详解
android的窗口机制分析------事件处理
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
相关文章推荐
- Android 事件分发机制详解
- Android 事件分发机制详解--真正的解析
- Android事件分发机制详解
- Android 事件分发机制详解
- Android ViewGroup/View 事件分发机制详解
- Android 事件分发机制详解
- Android:ViewGroup事件分发机制详解
- android事件分发机制详解
- Android View 事件分发机制源码详解(ViewGroup篇)
- Android事件分发机制详解
- Android ViewGroup 事件分发机制详解
- Android事件分发机制详解(2)----分析ViewGruop的事件分发
- Android 事件分发机制详解
- android 事件分发机制,最简单详解
- Android事件分发机制详解
- android事件分发机制详解
- Android中事件分发机制详解
- Android 事件分发机制详解
- Android 滑动冲突,事件分发机制的详解
- Android触摸事件分发机制详解