参考资料
鸿洋版事件分发机制
郭霖版事件分发机制
Android开发艺术探索
Android事件传递整体流程简介
Android输入事件的源头是位于/dev/input/下的设备节点,而输入事件的终点是由WMS管理的某个窗口,最终由窗口中的View处理。最初的输入事件为内核生成的原始事件,而最终交付给窗口的则是KeyEvent(键盘)或MotionEvent(鼠标和触摸屏)对象。输入事件由Native层进入到Java层的第一个函数是InputEventReceiver.dispatchInputEvent(),这样我们的手指触摸事件(MotionEvent)就传递到Java层,到应用层的传递过程遵循如下顺序:Activity->Window->View;即View事件最先传递给Activity,然后由Activity传递给Window,最后由Window传递给View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;事件到达Java层以后是谁将事件传递给Activity?
//事件传递整体流程:底层生成原始事件后,经过一列加工处理之后,将事件封装成MotionEvent、keyEvent,然后传递到Java层的InputEventReceiver.dispatchTouchEvent中,事件到达Java层之后,如何一步步传递到Activity?以下调用堆栈可以看到事件如何用从InputEventReceive传递到Activity;事件传递到Activity之后,由Activity传递给Window(PhoneWindow),最后由Window传递给顶级View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;本文主要是从Activity开始来分析事件分发机制;
//事件详细信息,action表示事件类型,x,y表示事件发生的位置,deviceId是硬件设备的id值
EventTest: MainActivity dispatchTouchEvent: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=408.94913, y[0]=416.38184, toolType[0]=TOOL_TYPE_MOUSE, buttonState=BUTTON_PRIMARY, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=498914, downTime=498914, deviceId=13, source=0x2002 }
System.err: java.lang.Exception: EventTest2
System.err: at .*********************.MainActivity.dispatchTouchEvent(MainActivity.java:68)
System.err: at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:71)
System.err: at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:434)
System.err: at android.view.View.dispatchPointerEvent(View.java:12029)
System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4834)
//在onProcess方法中,进行事件类型的判断,然后根据不同的事件类型调用不同的处理方法
System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4644)
//在deliver中完成事件处理之后,调用finishInputEvent给输入系统一个反馈;
System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)
System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4229)
System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4195)
System.err: at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4322)
System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4203)
System.err: at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4379)
System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)
System.err: at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4229)
System.err: at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4195)
System.err: at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4203)
System.err: at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4176)
System.err: at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6707)
System.err: at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6681)
System.err: at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6642)
System.err: at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6810)
//事件由native层正式进入到Java层
System.err: at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:187)
System.err: at android.os.MessageQueue.nativePollOnce(Native Method)
System.err: at android.os.MessageQueue.next(MessageQueue.java:325)
System.err: at android.os.Looper.loop(Looper.java:142)
System.err: at android.app.ActivityThread.main(ActivityThread.java:6627)
System.err: at java.lang.reflect.Method.invoke(Native Method)
System.err: at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
ViewPostImeInputStage.onProcess():
final class ViewPostImeInputStage extends InputStage {
@Override
protected int onProcess(QueuedInputEvent q) {
// 当onProcess被回调时,processKeyEvent、processPointerEvent、processTrackballEvent、
// processGenericMotionEvent至少有一个方法就会被调用,这些方法都是属于ViewPostImeInputStage的。
// 这些方法中都有一句很关键的一句代码处理按键事件
// mView.dispatchKeyEvent(event)
// mView.dispatchPointerEvent(event)
// mView.dispatchTrackballEvent(event)
// mView.dispatchGenericMotionEvent(event)
// mView的实例化在ViewRootImpl的setView方法中,其实就是DecorView
// 这样一来,可以知道ViewPostImeInputStage将事件分发到了DecorView
if (q.mEvent instanceof KeyEvent) {
// Key事件会调用到processKeyEvent,处理key事件,如键盘事件
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
//触摸屏事件(MotionEvent),鼠标事件(MotionEvent),鼠标事件到应用层是MotionEvent,不是KeyEvent
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
//轨迹球事件
return processTrackballEvent(q);
} else {
//其它motion事件,如游戏手柄
return processGenericMotionEvent(q);
}
}
}
}
事件分发整体流程:底层生成原始事件后,经过一列加工处理之后,将事件封装成MotionEvent、keyEvent,然后传递到Java层InputEventReceiver.dispatchTouchEvent中,事件到达Java层之后,如何一步步传递到Activity?通过调用堆栈可以看到事件如何用从InputEventReceive传递到Activity;事件传递到Activity之后,由Activity传递给Window(PhoneWindow),最后由Window传递给顶级View;顶级View(DecorView)接收到事件后,就会按照事件分发机制去分发事件;本文主要是从Activity开始来分析事件分发机制;
与事件分发过程相关的几个方法主要有:
(1)dispatchTouchEvent:用来进行事件分发,如果事件能够传递给当前View,那么次方法一定会被调用,返回值受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响;
(2)onInterceptTouchEvent:在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前VIew拦截了某个事件,那么在同一事件序列中,此方法不会再被调用,返回结果表示是否拦截当前事件;
(3)onTouch:在dispatchTouchEvent方法中调用,其调用优先级高于onTouchEvent;
(4)onTouchEvent:在dispatchTouchEvent方法中调用,用来处理点击事件,反馈结果表示是否消耗当前事件,如果不消耗,则在同一事件序列中,当前View无法再次接受事件;
(5)onclick:在发生点击事件时,会调用该方法,表示有点击事件;onclick发生的前提是当前View可以点击,并且它收到down和up事件;
(6)onLongClick:当长按事件发生时会回调该方法,手指或鼠标按下500ms之后就会发生长按事件,如果onLongClick返回true,就 不会在调用onClick方法,返回false,就会调用onClick;
1.Activity对事件的分发过程
Activity.dispatchTouchEvent代码如下:
/**
* 一个点击事件产生后,它的传递过程遵循如下顺序:
* <h3>Activity——>Window——>View</h3>
* 即事件总是先传递给Activity,由Activity的dispatchTouchEvent方法来进行事件派发,
* 具体的工作是由Activity内部的Window来完成的。Activity传递给Window后,Window再
* 传递给顶级View。顶级View接受事件后,就会按照事件分发机制去分发事件。
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// 传递给Window对象。getWindow()返回的是PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
2.Window(PhoneWindow)对事件的分发过程:
// This is the top-level view of the window, containing the window decor.
//DecorView是顶级View,也叫根View
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
// PhoneWindow将事件直接传递给了DecorView
return mDecor.superDispatchTouchEvent(event);
}
通过getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0)这种方法可以获取Activity所设置的View,setContentView所设置的View是DecorView的子View,关于DecorView后续会专门进行讲解,目前事件传递已经传递到DecorView这里,及事件已经从Activity传递到View中。
3.View事件分发机制
当事件传递到View中时,首先进入View的dispatchTouchEvent
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* 如果事件能够传递给当前View,那么此方法一定会被调用,
* 返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
* View是一个单独的元素,它没有子元素因此无法向下传递事件,
* 所以它只能自己处理事件,所以View的onTouchEvent方法默认返回true。
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
// 首先会判断当前View有没有设置OnTouchListener,
// 如果OnTouchListener中的onTouch方法返回true,那么onTouchEvent方法就不会被调用,
// 可见OnTouchListener的优先级要高于onTouchEvent,这样做的好处是方便在外界处理点击事件。
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;
}
4.View的onTouchEvent
接下来是View的onTouchEvent:
public boolean onTouchEvent(MotionEvent event) {
// 对点击事件的具体处理。只要CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件,因为返回了true
// View的LONG_CLICKABLE属性默认为false,而CLICKABLE属性默认为true,不过具体的View的CLICKABLE又不一定,
// 确切来说是可点击的View其CLICKABLE属性true,比如Button,不可点击的View的CLICKABLE为false,比如TextView。
// 通过setClickable和setLongClickable可以设置这两个属性。
// 另外setOnClickListener和setOnLongClickListener会自动将View的这两个属性设为true。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
//移除长按监测
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
// 当up事件发生时,会触发performClick方法
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
// 如果View设置OnClickListener,那么performClick就会调用View的onClick方法。
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
//设置当前View的状态,以及更新View的drawable state,例如button按下、悬浮时显示不同的背景,即刷新背景
setPressed(true, x, y);
//开始长按监测,所以View的长按监测位于
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();//当移出当前View时,会删除当前View的长按监测;
...
}
break;
}
return true;
}
return false;
}
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
//ViewConfiguration.getLongPressTimeout()时间为500ms,所以当用户按下去500ms之后就会回OnLongClickListener.onLongClick方法;
//如果500ms之内手指弹起,会发生ACTION_UP事件,此时会移出长按监测,这样就不会发生长按事件
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
public boolean performClick() {
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 如果设置了OnClickListener监听器,就回调onClick方法。
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
通过以上代码可以看出:
(1)当发生ACTION_DOWN的时候,开始长按监测,如果用户手指按下时间超过500ms才会发生长按事件,少于500ms就是onClick事件;可以得知,是否是长按事件是在Java层进行判断的,并且和ACTION_MOVE事件没有关系。
(2)onLongClick方法返回true之后会将mHasPerformedLongPress属性置为true,在Up的时候就不会执行onClick,如果onlongClick返回false在Up事件到来时就会执行onClick
(3)onClick是否会发生的前提是当前View是可点击的,并且它收到了Down和Up事件,
(4)只要View的CLICKABLE和LONG_CLICKABLE有一个为TRUE,那么该View就会消耗掉该事件,可以看出以上无论是Down、Move还是Up事件,最终都return true;
5.总结:
(1)整个View的事件分发流程:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
(2)onTouchEvent中的DOWN,MOVE,UP
DOWN时:
a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false ;然后发出一个115ms后的mPendingCheckForTap;
b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;
c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:
此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;
MOVE时:
主要就是检测用户是否划出控件,如果划出了:
115ms内,直接移除mPendingCheckForTap;
115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();
UP时:
a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
c、如果是500ms以后,那么有两种情况:
i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
d、最后执行setPressed刷新背景,然后将PRESSED标识去除;
本篇博文完成了对View的事件分发机制的整个流程的说明,并且对源码进行了分析;下篇会讲解ViewGroup的事件分发机制;
本文部分内容直接从其他文章直接Copy而来,感谢本文内容所参考文章的作者;
网友评论