Android事件分发剖析
前言
Android的事件分发机制在面试中是经常被问到的一个知识点,最近在看源码的过程中,看到了这一点,这里咱们就把Android的事件分发对照源码进行一下简单的剖析。
一、Activity的事件分发
说到事件分发不得不说一下Touch事件,Touch事件一共有四种事件类型,包括:MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP、MotionEvent.ACTION_MOVE、MotionEvent.CANCEL,分别对应按下、抬起、移动、和取消事件。
而事件分发是从Activity的dispatchTouchEvent方法开始的,下面我们以dispatchTouchEvent方法为入口,看下源码:
//Activity中的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
//如果当前事件为按下事件,那么就会执行onUserInteraction()方法
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我们看Activity 中的dispatchTouchEvent方法,如果过为按下事件执行onUserInteraction()方法,我们来看下onUserInteraction方法,发现这个方法是个空实现的方法:
public void onUserInteraction() {
}
紧接着看dispatchTouchEvent方法中的getWindow.superDispatchTouchEvent(ev),如果这个getWindow.superDispatchTouchEvent(ev)值返回true,那么事件传递结束。如果返回false,那么就会执行Activity的onTouchEvent方法,不管Activity的onTouchEvent方法返回true还是false,事件传递都会结束:
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
getWindow.superDispatchTouchEvent(ev)方法:
我们都知道每一个Activity都持有一个Window对象,而这里就是调用Activity所持有的Window对象的superDispatchTouchEvent,而Window类是一个抽象类,它的唯一的子类是PhoneWindow:
public abstract class Window {
...
}
所以,getWindow.superDispatchTouchEvent(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的superDispatchTouchEvent方法,又调用了其父类的dispatchTouchEvent方法,而DecorView是继承于FrameLayout的,而FrameLayout中没有dispatchTouchEvent方法,进而看FrameLayout的父类ViewGroup,所以找到了:DecorView其实是调用了ViewGroup里面的DispatchTouchEvent方法。所以到这事件就已经从Activity传递到了ViewGroup上。
在这里借用某大神的流程图:
二、ViewGroup的事件传递
ViewGroup的dispatchTouchEvent方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
...
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
...
if (!canceled && !intercepted) {
...
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();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
...
}
}
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
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;
}
}
...
}
...
return handled;
}
在这里,我们省略了一部分代码,关键看几个地方,一个是intercepted变量,他是通过onInterceptTouchEvent方法赋值的:
//ViewGroup中事件拦截的方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
如果onInterceptTouchEvent方法返回false那么就执行这里面的方法:
if (!canceled && !intercepted) {
...
}
我们看看当onInterceptTouchEvent为false时,这里面到底执行了什么方法?
大家注意到有这么一段代码:
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
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;
}
通过dispatchTransformedTouchEvent方法来判断,如果这个方法的返回值为true,那么就执行里面的代码,如果为false,就不执行,这里面有个addTouchTarget方法:
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
这个addTouchTarget方法中有一句关键的代码:mFirstTouchTarget = target;通过这个,使mFirstTouchTarget变量的值不为空。返回到上面的代码中去,如果onInterceptTouchEvent方法不返回true(ViewGroup拦截事件),那么就不会执行到mFirstTouchTarget = target这行代码,那么mFirstTouchTarget 就是初始化值为null。
为什么说mFirstTouchTarget的值那么重要呢?我们看接下来的代码就会知晓:
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
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;
}
}
根据mFirstTouchTarget变量是否为空,执行了不同的代码逻辑,不管mFirstTouchTarget是否为null都执行了dispatchTransformedTouchEvent这个方法。但是,
当mFirstTouchTarget==null时:
dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
当mFirstTouchTarget不等于null时:
dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)
关键看 dispatchTransformedTouchEvent这个方法的第三个参数,当mFirstTouchTarget==null时,第三个参数为null,当mFirstTouchTarget不等于null时,第三个参数为target.child不为null。
再看dispatchTransformedTouchEvent方法的实现:
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;
}
...
return handled;
}
由dispatchTransformedTouchEvent方法可以看出,
如果第三个参数child为null,那么执行:
handled = super.dispatchTouchEvent(event);
即执行当前ViewGroup父类的dispatchTouchEvent方法,因为ViewGroup的父类是View,那么就是执行View的dispatchTouchEvent方法。而ViewGroup的父类View又调用了onTouchEvent方法,即调用的就是子类ViewGroup的onTouchEvent方法。所以当onInterceptTouchEvent方法返回true的时候,就会调用ViewGroup的onTouchEvent方法,而不会再向下调用子View的方法。
如果第三个参数child不为null,那么执行:
handled = child.dispatchTouchEvent(event);
child即当前ViewGroup的子View(上面省略的代码部分,有for循环,获取到当前ViewGroup的子View),这里即执行ViewGroup的子View的dispatchTouchEvent方法。
到这里,ViewGroup就把事件传递给了View。
总结,如果onInterceptTouchEvent方法返回true,事件被拦截,那么接着就会调用ViewGroup的onTouchEvent方法,事件不会再向ViewGroup的子View进行传递。如果onInterceptTouchEvent方法返回false,事件未被拦截,那么接着会调用ViewGroup子View的dispatchTouchEvent,进而调用子View的onTouchEvent。
借用大神的流程图:
三、 View的事件传递
由ViewGroup传递过来的事件,首先会调用View的DispatchTouchEvent方法:
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
return result;
}
在这里我们重点看一下这个:
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
通过这个我们看到这里通过判断onTouch,如果设置了onTouchListener,那么result就会为true,那么就不会执行View的,onTouchEvent方法了,所以onTouch方法的优先级要高于onTouchEvent。而且通过这个我们可以知道,View中的事件传递,就是通过调用View的DispatchTouchEvent方法,紧接着调用View的onTouchEvent方法。
总结
通过以上的源码剖析,大概了解了事件从Activity到View分发的流程,下面借用大神的流程图,展示下,从Activity到View整个事件分发的流程:
上一篇: Linux内存管理源码剖析(三)