当用户触摸屏幕或者按键操做,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点交由内核处理, 产生最原生态的内核事件。内核会将触摸事件包装成EVENT做为文件存储到"/dev/input/event[x]"
目录中。java
而后,InputReaderThread
会不断的从"/dev/input/event[x]"
目录中读取事件,并会把事件交给InputDispatch
处理。InputDispatch
会把事件分发到须要的地方。android
以上的这些步骤都是C/C++代码实现,只须要对流程作一些简单的了解便可。而真正java开始拿到事件的起点是在ViewRootImpl
的内部类WindowInputEventReceiver
之中。git
WindowInputEventReceiver.onInputEvent()
经过InputDispatch
调用接受到事件以后,首先将事件传给了enqueueInputEvent()
,将新接受的事件插入到事件队列中,接着调用doProcessInputEvents()
。缓存
class ViewRootImpl {
...
final class WindowInputEventReceiver extends InputEventReceiver {
...
@Override
public void onInputEvent(InputEvent event, int displayId) {
enqueueInputEvent(event, this, 0, true);
}
}
void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) {
...
QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
QueuedInputEvent last = mPendingInputEventTail;
last.mNext = q;
...
doProcessInputEvents();
}
}
复制代码
doProcessInputEvents()
循环着取出全部的事件,按照顺序将事件交给deliverInputEvent()
,而它的工做就是将事件发送到InputStage的表头中。安全
void doProcessInputEvents() {
...
while (mPendingInputEventHead != null) {
QueuedInputEvent q = mPendingInputEventHead;
mPendingInputEventHead = q.mNext;
...
deliverInputEvent(q);
}
}
private void deliverInputEvent(QueuedInputEvent q) {
...
InputStage stage;
if (q.shouldSendToSynthesizer()) {
stage = mSyntheticInputStage;
} else {
stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
}
stage.deliver(q);
...
}
复制代码
InputStage
是一个单向链表结构,这个链表中有一系列的InputStage
,他们都能对事件作出立,事件就在它们上面传递,若是事件没有被前面的InputStage
标记Finished
,当前的InputStage
就会尝试消费它。其中ViewPostImeInputStage
就会将事件发送到View处理处理。markdown
ViewPostImeInputStage.onProcess()
拿到事件后,交给ProcessPointerEvent()
,这就调用到mView.dispatchPointerEvent()
,而ViewRootImpl.mView
众所周知,也就是DecorView
,也就是终于走到了View中进行处理了!(这里还有疑问的翻阅一下上一篇文章)。ide
final class ViewPostImeInputStage extends InputStage {
...
@Override
protected int onProcess(QueuedInputEvent q) {
...
return processPointerEvent(q);
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
boolean handled = mView.dispatchPointerEvent(event);
...
return handled ? FINISH_HANDLED : FORWARD;
}
}
复制代码
首先ViewRootImpl
将事件传递到DecorView.dispatchPointerEvent()
,DecorView
没有实现这个方法,实际调用View.dispatchPointerEvent()
,而它又调用了DecorView.dispatchTouchEvent()
将事件传回DecorView
。函数
紧接着DecorView
经过Window.getCallback()
将事件传递到了Acitivity.dispatchTouchEvent()
。oop
Activity
又将事件又传给了PhoneWindow
,PhoneWindow
将事件直接透传回DecorView
,最终调用到super.dispatchTouchEvent()
也就是ViewGroup.dispatchTouchEvent()
。post
///View
public final boolean dispatchPointerEvent(MotionEvent event) {
...
return dispatchTouchEvent(event);
}
///DecorView
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
...
return cb.dispatchTouchEvent(ev);
}
///Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
...
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
///PhoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
///DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
复制代码
这一步看起来会有点疑惑行为的感受:
第一点,为何DecorView
要把事件传递给Activity
而后又传递回来?
Activity.dispatchTouchEvent
中首先将事件又交给View树,若是没有被消费,Activity
就能够拿到这个事件并作处理,官方注释写到在其中能够处理在窗口范围外发生的触摸事件效果最佳。如此,就能够理解为,Activity
但愿作事件分发的起点,因此须要传给Activity
由它来启动。
第二个问题,传递的目标是交给Activity
而后再传递给DecorView
,为何ViewRootImpl
将事件传给了DecorView
而不是直接传递给Activity
?而Activity
为何经过Window
去传递?并且PhoneWindow
中就只是将事件直接透传给DecorView
。
这个问题在上一篇文章就讨论过,这样设计的目的是单一原则以及解耦,ViewRootImpl
并不知道Activity的存在,它只有DecorView
的对象,同理Activity
并不知道DecorView
的存在,它只有Window
的对象,上一次说到Window
存在的意义就是承载和处理视图,Activity
并不关心它内部是怎样实现的,只须要将事件交给他就好了,另外一方面,这几个类尽可能最少的持有对方,最大程度上的实现解耦,而解耦是为了什么,就么有必要再累赘了。
上一节分析到,事件被分发到了DecorView
,而且调用了super.dispatchTouchEvent()
,也就是ViewGroup.dispatchTouchEvent()
。这一节主要分析一下ViewGroup.dispatchTouchEvent()
都作了那些事情,又是如何完成了事件的向下传递。
ViewGroup.dispatchTouchEvent()
比较长,将这个方法大体拆分为三个步骤来进行分析:1. 拦截 2. 寻找分发目标 3. 分发
首先先了解一下流程中十分重要的两个概念:
PointerId
:触摸点Id,多指操做时,每根手指从按下、移动到离开屏幕,都会拥有一个固定PointerId与对应的手指进行绑定。Id的范围是0..31,官方目前假设这是安全的,由于底层的管道也是这样设计的。
32个数字范围的优势就是能够用32位的int
来存储一组pointerId
,而且方便去计算和查询。
TouchTarget
:缓存触摸的child
以及它所消费的触摸点Id集合,也就表示事件派发目标。
TouchTarget
是一个单向链表结构,ViewGroup经过mFirstTouchTarget
持有表头。
TouchTarget
自身维护了一个复用池,池子的容量也是32个。
分发最首先的操做是看本身是否要拦截这一次事件,若是自身选择了拦截,事件就再也不下发,而是交给本身处理。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
///消费标志
boolean handled = false;
///onFilterTouchEventForSecurity():安全检查
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
///若是是ACTION_DOWN,就表示一组新的事件的开始,清除旧的TouchTarget以及重置滚动状态
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 拦截标志
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
///判断是否容许进行拦截
///子view调用parent.requestDisallowInterceptTouchEvent()影响的就是这里
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
///onInterceptTouchEvent():拦截的方法,子类通常经过实现这个方法来进行事件拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
///不是ACTION_DOWN而且没有touchTarget
///也就表示已经以前全部的view都接受到以前ACTION_DOWN事件,但都没有消费
///也就是没有视图会愿意接受这个事件,直接拦截掉
intercepted = true;
}
...
}
}
复制代码
子view经过调用parent.requestDisallowInterceptTouchEvent()
阻止parent
拦截事件分析一下前置条件actionMasked == MotionEvent.ACTION_DOWN
和mFirstTouchTarget != null
。
ACTION_DOWN
事件,那么在执行判断以前就会先执行clear
操做,不只清除了TouchTarget
,也清除了mGroupFlags
标志位,而又能够看到阻止拦截就是经过mGroupFlags
标志,因此child
设置的拦截标志直接被重置掉了。也就是说当是ACTION_DOWN
事件的时候,子view的阻止拦截会直接失效,还能够说,child
阻止parent
拦截最多到下一次ACTION_DOWN
事件的时候。mFirstTouchTarget
不为空,而且不是ACTION_DOWN
事件,这个时候才会根据标志判断是否走拦截的方法。整体分析,若是想要进行拦截,首先就是child
要拿到拿到ACTION_DOWN
或者稍后的事件,这个时候再调用parent.requestDisallowInterceptTouchEvent()
更改标志位来阻止parent
拦截这个ACTION_DOWN
同一组的事件事件。
当事件不是ACTION_CANCEL
而且没有被拦截,并且必须是一个DOWN
事件,才会去寻找派发的目标View。首先若是是ACTION_CANCEL
或者被拦截,事件都没有了继续传递的必要,其次,DOWN
之类的事件标记着一组事件的起始,若是想要对一组事件进行处理,必须在DOWN
产生的时候就拿到并处理过它,而这种状况就已经存在了TouchTarget
,也是没有查找派发目标的必要。
其次就是遍历全部的子view,寻找符合条件的view。而遍历时的顺序首先遵循Z轴的顺序,也就是视觉上的从前面到后面,其次是遵循绘制顺序,最后绘制的优先级最高,这样在视图有重叠的时候,也是视觉上的从前面到后面。
遍历的时候,须要根据多个条件来筛选判断当前view是否会接受这个事件,筛选的前后顺序:
TouchTarget
链表是否已经有了child
的TouchTarget
,若是有了只须要将触摸点Id存入这个TouchTarget
,而后跳出循环,走后续的派发流程就可。这种状况会发生在多指操做的时候,第一根手指落在一个view上面以后,第二根手指也落在这个view上面,就只须要继续讲这个事件发送给这个view便可。child
去处理,即将事件向下传递,若是当前child
或者它的child
处理了这个事件,那么当前这个child
就是派发目标,新建一个TouchTarget
存入到TouchTarget
链表的头部。当遍历完全部的child
没有找到派发的目标,而且TouchTarget
链表不是空的,那么就会将最先添加的TouchTarget
做为派发目标。
public boolean dispatchTouchEvent(MotionEvent ev) {
...
///消费标志
boolean handled = false;
///onFilterTouchEventForSecurity():安全检查
if (onFilterTouchEventForSecurity(ev)) {
...
///ACTION_CANCEL事件标志
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
///分裂标志(是否支持多指操做)
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;///已经分配给新的TouchTarget标志
///1. 不是取消而且没被拦截才进行派发
///2. 是一个DOWN事件(ACTION_HOVER_MOVE是指针悬浮在view上)
/// 表示是一个新的事件序列开始了,须要从新找Target
if (!canceled && !intercepted) {
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
///触摸点ID转换为32位标志
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
///清除TouchTarget列表中的相同的触摸点Id,由于是一个新的事件队列,以前的就已经失效了
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
///构建一个子view的列表在原有的顺序基础上,优先根据z轴排序
///这个列表会在全部子view都没有z轴返回空值
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
///view是否按规定正常绘制的标志
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
///preorderedList可能为null,经过这两个方法获的实际的index和child
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
///判断是否要将事件发给child
///canViewReceivePointerEvents():子类是否能够接收,条件是view可见 或者 正在播放动画
///isTransformedTouchPointInView():触摸点是否命中view范围
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
///查询已有的TouchTarget列表中是否已经有该child的TouchTarget
///若是有的话只须要给触摸点Id集合添加新的Id便可
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
///重置child的detached标志
///(child未将事件处理完成就detached,就会将后续的事件设为CANCEL)
resetCancelNextUpFlag(child);
///dispatchTransformedTouchEvent():将事件向下传递交给view去处理
///返回true就是view成功消费了这个事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
///若是返回了true,更新一下相关数据,中止循环
///而且建立一个TouchTarget,插入到mFirstTouchTarget前面做为表头
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;///已经派发了事件的标志
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
///若是没有找到派发的目标,可是存在旧的TouchTarget
///将事件派发给最先添加的Target
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
...
}
}
复制代码
须要注意的是,当是一次ACTION_DOWN
事件的时候,大几率在这个过程当中并不会存在TouchTarget
,那么在查找派发目标的过程当中,就已经完成了派发的动做,也就是说这个事件到这里已经完成了它的分发流程。
分发的逻辑比较清晰,遍历TouchTraget
,尝试给它分发事件。有几个比较特殊的状况:
TouchTarget
链表为空,就直接将事件发送给本身处理。TouchTarget
发送取消事件。public boolean dispatchTouchEvent(MotionEvent ev) {
...
///消费标志
boolean handled = false;
///onFilterTouchEventForSecurity():安全检查
if (onFilterTouchEventForSecurity(ev)) {
...
if (mFirstTouchTarget == null) {
///没找到派发目标就发给本身来处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
///给TouchTarget派发事件
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
///已经在上一个步骤派发过
handled = true;
} else {
///派发给对应的TouchTarget
///若是拦截了事件,就会给全部的TouchTarget发送CANCEL事件
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;
}
}
/// 当是取消事件或者是ACTION_UP之类的事件,执行清除的操做。
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);
}
}
return handled;
}
复制代码
这里发觉到的一个比较有意思的逻辑是,事件按理说应该只交给一个child
去处理,为何要给全部的TouchTarget
分发?奥秘就在TouchTarget
存储的触摸点Id集合中,在查找分发目标的过程当中,首先的步骤就是清除了TouchTarget
链表中相同的触摸点Id,而分发的函数dispatchTransformedTouchEvent()
中,又会根据Id集合是否包含当前事件的触摸点Id去选择是否下发,因此实际上只将事件发送到了惟一的持有当前事件触摸点Id的TouchTarget
上面。而对于拦截了的状况,就会给全部的TouchTarget
发送取消事件而且清除掉TouchTarget
。
在上一节中,时常会提到这个方法ViewGroup.dispatchTransformedTouchEvent()
,当肯定了分发目标的时候,就会调用这个方法进行分发,作一些预处理以后进行分发。主要的步骤:
ACTION_CANCEL
事件直接下发。TouchTarget
分发的问题。若是没有交集,说明这个TouchTarget
不是分发目标,直接返回。TouchTarget
表示这个child只接受其中的一部分,也就是,多个手指落在了不一样的view上面,须要拆开进行分发。private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) {
final boolean handled;
///取消事件直接下发
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
///触摸点id过滤
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
///取和等与0,没有交集,不须要下发,直接返回
if (newPointerIdBits == 0) {
return false;
}
///根据状况,对事件进行必定的分裂
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
///当触摸点id不相等的时候,须要对事件进行过滤
///只拿到须要下发的触摸点id的事件进行下发
transformedEvent = event.split(newPointerIdBits);
}
///分发
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
复制代码
这个方法中,有不少的child == null
判断,而后调用super.dispatchTouchEvent()
,而这时候ViewGroup
执行super.dispatchTouchEvent()
调用的是View.dispatchTouchEvent()
,也就是事件交给了本身来处理。
认真看代码还会发现一个奇怪的事情,在分裂的时候,有一段和后面分发彻底一致的代码,也就是在分裂直接进行了分发。为何重复的代码要写两遍?
分析一下走第一部分不走第二部分的条件:newPointerIdBits == oldPointerIdBits
和child.hasIdentityMatrix()==true(变换矩阵是单位矩阵,也就是没有变化)
。
简单分析不考虑多指操做不一样的view,也就是newPointerIdBits == oldPointerIdBits
的状况下, 剩下的条件就只有child.hasIdentityMatrix()
。
没有变换矩阵走第一段代码,有变换矩阵走第二段代码,两段代码的区别也就是后面的那段代码使用的是复制的MotionEvent
,进行了矩阵映射,而且使用结束了就直接回收了;而前面的那段代码是直接使用的传入的MotionEvent
,使用后进行了坐标系的复原。
而这就体现出两端代码的区别:若是有矩阵映射的状况下,第一段代码会多一个反映射的复原操做,而第二段代码多的是一个MotionEvent
建立销毁的操做。我猜想,这可能就是在变换矩阵反映射和MotionEvent
建立销毁之间取得的一点性能上得优化吧。
走到这也就到了一次触摸事件分发的尾声了,当View.dispatchTouchEvent()
执行的时候,只须要将事件分发给自身处理便可,自身又不止一个处理方法,因此也就会有个优先级来决定前后顺序:
onTouchListener
处理,也就是经过外部设置的监听。onTouchEvent()
处理。public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
///滚动条处理
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
///onTouchListener处理
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
///onTouchEvent()处理
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
复制代码
此次尝试将手指触摸到屏幕上到控件获取到此次触摸的流程作一次完整的剖析,没有想到工做量居然如此大,文章长度达到了5000词。收获颇丰,不只学到了不少源码设计的巧妙之处,同时是源码的阅读能力仍是事件分发的理解,都上了一个大台阶。
入行浅,起步要稳,一步一个脚印,作大作强,再创辉煌。