參考資料
鴻洋版事件分發(fā)機制
郭霖版事件分發(fā)機制
Android開發(fā)藝術探索
Android事件傳遞整體流程簡介
Android輸入事件的源頭是位于/dev/input/下的設備節(jié)點奕翔,而輸入事件的終點是由WMS管理的某個窗口铡俐,最終由窗口中的View處理。最初的輸入事件為內(nèi)核生成的原始事件嘹叫,而最終交付給窗口的則是KeyEvent(鍵盤)或MotionEvent(鼠標和觸摸屏)對象。輸入事件由Native層進入到Java層的第一個函數(shù)是InputEventReceiver.dispatchInputEvent()坦报,這樣我們的手指觸摸事件(MotionEvent)就傳遞到Java層馏鹤,到應用層的傳遞過程遵循如下順序:Activity->Window->View;即View事件最先傳遞給Activity,然后由Activity傳遞給Window瑟捣,最后由Window傳遞給View;頂級View(DecorView)接收到事件后栅干,就會按照事件分發(fā)機制去分發(fā)事件迈套;事件到達Java層以后是誰將事件傳遞給Activity?
//事件傳遞整體流程:底層生成原始事件后非驮,經(jīng)過一列加工處理之后交汤,將事件封裝成MotionEvent雏赦、keyEvent劫笙,然后傳遞到Java層的InputEventReceiver.dispatchTouchEvent中,事件到達Java層之后星岗,如何一步步傳遞到Activity填大?以下調用堆棧可以看到事件如何用從InputEventReceive傳遞到Activity俏橘;事件傳遞到Activity之后允华,由Activity傳遞給Window(PhoneWindow),最后由Window傳遞給頂級View寥掐;頂級View(DecorView)接收到事件后靴寂,就會按照事件分發(fā)機制去分發(fā)事件;本文主要是從Activity開始來分析事件分發(fā)機制召耘;
//事件詳細信息百炬,action表示事件類型,x,y表示事件發(fā)生的位置污它,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方法中剖踊,進行事件類型的判斷庶弃,然后根據(jù)不同的事件類型調用不同的處理方法
System.err: at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4644)
//在deliver中完成事件處理之后,調用finishInputEvent給輸入系統(tǒng)一個反饋德澈;
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將事件分發(fā)到了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);
}
}
}
}
事件分發(fā)整體流程:底層生成原始事件后,經(jīng)過一列加工處理之后煎源,將事件封裝成MotionEvent色迂、keyEvent,然后傳遞到Java層InputEventReceiver.dispatchTouchEvent中手销,事件到達Java層之后歇僧,如何一步步傳遞到Activity?通過調用堆椃嫱希可以看到事件如何用從InputEventReceive傳遞到Activity诈悍;事件傳遞到Activity之后,由Activity傳遞給Window(PhoneWindow)兽埃,最后由Window傳遞給頂級View侥钳;頂級View(DecorView)接收到事件后,就會按照事件分發(fā)機制去分發(fā)事件柄错;本文主要是從Activity開始來分析事件分發(fā)機制舷夺;
與事件分發(fā)過程相關的幾個方法主要有:
(1)dispatchTouchEvent:用來進行事件分發(fā),如果事件能夠傳遞給當前View售貌,那么次方法一定會被調用给猾,返回值受當前View的onTouchEvent和下級View的dispatchTouchEvent方法影響;
(2)onInterceptTouchEvent:在dispatchTouchEvent方法內(nèi)部調用颂跨,用來判斷是否攔截某個事件敢伸,如果當前VIew攔截了某個事件,那么在同一事件序列中毫捣,此方法不會再被調用详拙,返回結果表示是否攔截當前事件帝际;
(3)onTouch:在dispatchTouchEvent方法中調用,其調用優(yōu)先級高于onTouchEvent饶辙;
(4)onTouchEvent:在dispatchTouchEvent方法中調用蹲诀,用來處理點擊事件,反饋結果表示是否消耗當前事件弃揽,如果不消耗脯爪,則在同一事件序列中,當前View無法再次接受事件矿微;
(5)onclick:在發(fā)生點擊事件時痕慢,會調用該方法,表示有點擊事件涌矢;onclick發(fā)生的前提是當前View可以點擊掖举,并且它收到down和up事件;
(6)onLongClick:當長按事件發(fā)生時會回調該方法娜庇,手指或鼠標按下500ms之后就會發(fā)生長按事件塔次,如果onLongClick返回true,就 不會在調用onClick方法名秀,返回false励负,就會調用onClick;
1.Activity對事件的分發(fā)過程
Activity.dispatchTouchEvent代碼如下:
/**
* 一個點擊事件產(chǎn)生后匕得,它的傳遞過程遵循如下順序:
* <h3>Activity——>Window——>View</h3>
* 即事件總是先傳遞給Activity继榆,由Activity的dispatchTouchEvent方法來進行事件派發(fā),
* 具體的工作是由Activity內(nèi)部的Window來完成的汁掠。Activity傳遞給Window后略吨,Window再
* 傳遞給頂級View。頂級View接受事件后调塌,就會按照事件分發(fā)機制去分發(fā)事件晋南。
* @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)對事件的分發(fā)過程:
// 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后續(xù)會專門進行講解偶妖,目前事件傳遞已經(jīng)傳遞到DecorView這里姜凄,及事件已經(jīng)從Activity傳遞到View中。
3.View事件分發(fā)機制
當事件傳遞到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的優(yōu)先級要高于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();
//移除長按監(jiān)測
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事件發(fā)生時吵护,會觸發(fā)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的狀態(tài)馅而,以及更新View的drawable state,例如button按下譬圣、懸浮時顯示不同的背景瓮恭,即刷新背景
setPressed(true, x, y);
//開始長按監(jiān)測,所以View的長按監(jiān)測位于
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的長按監(jiān)測屯蹦;
...
}
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之內(nèi)手指彈起登澜,會發(fā)生ACTION_UP事件,此時會移出長按監(jiān)測飘庄,這樣就不會發(fā)生長按事件
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
public boolean performClick() {
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
// 如果設置了OnClickListener監(jiān)聽器脑蠕,就回調onClick方法。
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
通過以上代碼可以看出:
(1)當發(fā)生ACTION_DOWN的時候,開始長按監(jiān)測谴仙,如果用戶手指按下時間超過500ms才會發(fā)生長按事件迂求,少于500ms就是onClick事件;可以得知晃跺,是否是長按事件是在Java層進行判斷的揩局,并且和ACTION_MOVE事件沒有關系。
(2)onLongClick方法返回true之后會將mHasPerformedLongPress屬性置為true哼审,在Up的時候就不會執(zhí)行onClick谐腰,如果onlongClick返回false在Up事件到來時就會執(zhí)行onClick
(3)onClick是否會發(fā)生的前提是當前View是可點擊的,并且它收到了Down和Up事件涩盾,
(4)只要View的CLICKABLE和LONG_CLICKABLE有一個為TRUE十气,那么該View就會消耗掉該事件,可以看出以上無論是Down春霍、Move還是Up事件砸西,最終都return true;
5.總結:
(1)整個View的事件分發(fā)流程:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中會進行OnTouchListener的判斷址儒,如果OnTouchListener不為null且返回true芹枷,則表示事件被消費,onTouchEvent不會被執(zhí)行莲趣;否則執(zhí)行onTouchEvent鸳慈。
(2)onTouchEvent中的DOWN,MOVE,UP
DOWN時:
a、首先設置標志為PREPRESSED喧伞,設置mHasPerformedLongPress=false ;然后發(fā)出一個115ms后的mPendingCheckForTap走芋;
b、如果115ms內(nèi)沒有觸發(fā)UP潘鲫,則將標志置為PRESSED翁逞,清除PREPRESSED標志,同時發(fā)出一個延時為500-115ms的溉仑,檢測長按任務消息挖函;
c、如果500ms內(nèi)(從DOWN觸發(fā)開始算)浊竟,則會觸發(fā)LongClickListener:
此時如果LongClickListener不為null怨喘,則會執(zhí)行回調,同時如果LongClickListener.onClick返回true逐沙,才把mHasPerformedLongPress設置為true;否則mHasPerformedLongPress依然為false;
MOVE時:
主要就是檢測用戶是否劃出控件哲思,如果劃出了:
115ms內(nèi),直接移除mPendingCheckForTap吩案;
115ms后,則將標志中的PRESSED去除帝簇,同時移除長按的檢查:removeLongPressCallback();
UP時:
a徘郭、如果115ms內(nèi)靠益,觸發(fā)UP,此時標志為PREPRESSED残揉,則執(zhí)行UnsetPressedState胧后,setPressed(false);會把setPress轉發(fā)下去,可以在View中復寫dispatchSetPressed方法接收抱环;
b壳快、如果是115ms-500ms間,即長按還未發(fā)生镇草,則首先移除長按檢測眶痰,執(zhí)行onClick回調;
c梯啤、如果是500ms以后竖伯,那么有兩種情況:
i.設置了onLongClickListener,且onLongClickListener.onClick返回true因宇,則點擊事件OnClick事件無法觸發(fā)七婴;
ii.沒有設置onLongClickListener或者onLongClickListener.onClick返回false,則點擊事件OnClick事件依然可以觸發(fā)察滑;
d打厘、最后執(zhí)行setPressed刷新背景,然后將PRESSED標識去除贺辰;
本篇博文完成了對View的事件分發(fā)機制的整個流程的說明户盯,并且對源碼進行了分析;下篇會講解ViewGroup的事件分發(fā)機制魂爪;
本文部分內(nèi)容直接從其他文章直接Copy而來先舷,感謝本文內(nèi)容所參考文章的作者;