本文從以下方面分析 Android 的事件分發(fā),旨在梳理和理解放祟,并沒有深度展開
- 什么是事件
- 事件的分類
- 事件的種類
- 一次 Touch 事件流的序列
- Touch 事件的傳遞層級(jí)
- Touch 事件的分發(fā)
- Touch 事件的沖突解決
- 如何確定哪個(gè) View 處理事件
- 事件的來源
- 渲染
部分內(nèi)容為自己的理解例嘱,如果錯(cuò)誤還望指正
1. 什么是事件
與安卓設(shè)備進(jìn)行的一次交互亲桦,不同的安卓設(shè)備交互方式不一樣。
如:
按鍵:Home 鍵己儒,Back 鍵淑玫,音量鍵
觸摸屏幕:手指在屏幕上 按下 / 滑動(dòng) / 抬起
軌跡球:例如黑莓手機(jī)存在軌跡球
在 Android 中統(tǒng)稱為 輸入事件巾腕,API 體現(xiàn)在 InputEvent
2. 事件分類
Android 中的輸入事件 InputEvent 子類有兩個(gè):
- KeyEvent:按鍵 key、按鈕 button 事件
- MotionEvent:鼠標(biāo) mouse混移、畫筆 pen祠墅、手指 finger、軌跡球 trackball 等運(yùn)動(dòng)事件
不同類型的安卓設(shè)備歌径,API 體現(xiàn)在 InputDevice
-
SOURCE_CLASS_POINTER
:指示設(shè)備,eg:觸摸屏幕SOURCE_TOUCHSCREEN
或鼠標(biāo)SOURCE_MOUSE
-
SOURCE_CLASS_TRACKBALL
:軌跡球 -
SOURCE_CLASS_JOYSTICK
:搖桿 - ...
3. 事件的種類
KeyEvent 常見事件有:
KEYCODE_HOME
KEYCODE_MENU
KEYCODE_BACK
-
KEYCODE_MEDIA_PLAY
/KEYCODE_MEDIA_PAUSE
- ...
獲取 KeyEvent 方式:KeyEvent#getKeyCode()
MotionEvent 常見的事件有:
-
ACTION_DOWN
/ACTION_UP
/ACTION_MOVE
ACTION_CANCEL
-
ACTION_POINTER_DOWN
/ACTION_POINTER_UP
- ...
-
ACTION_HOVER_MOVE
/ACTION_HOVER_ENTER
/ACTION_HOVER_EXIT
/ACTION_SCROLL
亲茅,不屬于 TouchEvent
獲取 MotionEvent 方式:MotionEvent#getAction()/getActionMask()
touch event 的界定:
MotionEvent#isTouchEvent
MotionEvent 所有的事件去除上面的 4 個(gè)事件回铛,因?yàn)闆]有真正的按下操作,這 4 個(gè)事件傳遞通過View#onGenericMotionEvent(MotionEvent)
而不是View#onTouchEvent(MotionEvent)
本文主要討論 TouchEvent 的傳遞分發(fā)克锣,屬于 MotionEvent茵肃,沒有 API 的體現(xiàn)
ViewRootImpl.ViewPostImeInputStage#processPointerEvent(QueuedInputEvent)
ViewRootImpl#processPointerEvent(QueuedInputEvent)
View#dispatchPointerEvent(MotionEvnet)
View#dispatchTouchEvent(MotionEvent)
4. 一次 Touch 事件流的序列
序列 1:ACTION_DOWN -> ACTION_UP
序列 2:ACTION_DOWN -> ACTION_MOVE...ACTION_MOVE -> ACTION_UP
序列 3:ACTION_DOWN -> ACTION_MOVE...ACTION_MOVE -> ACTION_CANCEL, 非人為的結(jié)束
每個(gè)事件流都以 ACTION_DOWN 開始
如果前面的事件 dispatchTouchEvent 返回 false袭祟,后面一系列事件都不會(huì)執(zhí)行
5. Touch 事件傳遞層級(jí)
Activity -> PhoneWindow -> DecorView -> ViewGroup -> View
從最下層開始向上層傳遞验残,當(dāng)上層 View 響應(yīng)事件后,下層 View 將不會(huì)再響應(yīng)
6. Touch 事件的分發(fā)
注意以下方法的優(yōu)先級(jí)及返回值
6.1 View
dispatchTouchEvent
mTouchListener.onTouch
onTouchEvent
mOnLongClickListener.onLongClick
onClickListener.onClick
6.2 ViewGroup
dispatchTouchEvent
cancelAndClearTouchTargets()
onInterceptTouchEvent
disallowInterceptTouchEvent
dispatchTransformedTouchEvent()
addTouchTarget
6.3 Activity
dispatchTouchEvent()
onUserInteraction()
onTouchEvent
7. Touch 事件的沖突解決
7.1 ViewGroup 如何攔截
父 ViewGroup 中:重寫 onInterceptTouchEvent()
默認(rèn)返回 false 不攔截
在觸發(fā)事件 MotionEvent.ACTION_DOWN 時(shí)選擇攔截返回 true巾乳,則該事件不會(huì)往子 View 傳遞
DOWN 事件返回 true您没,則子 View 不會(huì)捕獲到 DOWN、MOVE胆绊、UP 等事件
MOVE 事件返回 true氨鹏,則子 View 不會(huì)捕獲到 MOVE、UP 等事件
7.2 View 如何不被攔截
子 View 中:調(diào)用 getParent().requestDisallowInterceptTouchEvent(true)
即使 ViewGroup 在 MOVE 的時(shí)候攔截了压状,子 View 依然能捕獲到 MOVE仆抵、UP 等事件
但是,如果 ViewGroup 在 DOWN 的時(shí)候就攔截了种冬,子 View 無法捕獲到任何事件A统蟆!因?yàn)?DOWN 會(huì)重置
8. 如何確定哪個(gè) View 處理事件
FirstTouchTarget 是一個(gè)鏈表娱两,保存著事件處理的 View
ViewGroup#addTouchTarget
通過 dispatchTouchEvent 的返回值 true 確定
9. 事件的來源
Android 輸入事件的源頭是 /dev/input/ 下的設(shè)備節(jié)點(diǎn)莺匠,然后由 WMS 管理窗口,最終由 View 處理谷婆。
最初的輸入事件為內(nèi)核生成的原始事件慨蛙,而交付給窗口的則是 KeyEvent 或 MotionEvent 對(duì)象辽聊。
輸入事件由 Native層 進(jìn)入到 Java層 的第一個(gè)函數(shù)是 InputEventReceiver.dispatchInputEvent()
-> ActivityThread#handleLaunchActivity
-> ActivityThread#performLaunchActivity
-> ActivityThread#createBaseContextForActivity
-> Instrumentation#newActivity() //反射創(chuàng)建 Activity
-> Activity#attach
-> new PhoneWindow() & window.setCallback(this) // 包含事件分發(fā)的回調(diào)
-> Activity#setTheme
-> Instrumentation#callActivityOnCreate
-> Activity#setContentView
-> PhoneWindow#setContentView
-> DecorView#installDecor
-> DecorView#generateDecor() & generateLayout()
-> ActivityThread#handleResumeActivity
-> WindowManagerImpl#addView(decor, LayoutParams)
-> WindowManagerGlobal#addView
-> new ViewRootImpl() //構(gòu)造函數(shù)
-> ViewRootImpl#setView(decor) // 將 window 和 DecorView 關(guān)聯(lián)
-> IWindowSession#addToDisplay(mWindow,mInputChannel) // InputChannel IPC fd
-> WindowManagerService#addWindow()
-> WindowState#openInputChannel(InputChannel)
-> InputManagerService#registerInputChannel(inputChannel) // native
-> new WindowInputEventReceiver(mInputChannel)
-> InputEventReceiver#dispatchInputEvent(InputEvent) // Called from native code.
-> ViewRootImpl.WindowInputEventReceiver#onInputEvent(InputEvent)
-> mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
-> ViewRootImpl.InputStage#deliver(QueuedInputEvent)
-> ViewRootImpl.ViewPostImeInputStage.onProcess()
-> ViewRootImpl#processPointerEvent
-> DecorView.dispatchPointerEvent(MotionEvent)
-> mWindow.getCallback().dispatchTouchEvent(ev) // Callback為 Activity 的回調(diào)
-> Activity.dispatchTouchEvent(ev)
-> window.superDispatchTouchEvent(ev)
-> mDecor.superDispatchTouchEvent(ev)
事件的傳遞 java 層通過 WMS 和 ViewRootImpl 利用 Callback 回調(diào)來完成期贫,然后 Choreographer 進(jìn)行渲染
10. 渲染 Choreographer
Choreographer 順序處理 input跟匆、animation、drawing 三個(gè) ui 操作
Choreographer 接收顯示系統(tǒng)的時(shí)間脈沖(垂直脈沖信號(hào) VSync 信號(hào))通砍,在下一個(gè) frame 渲染時(shí)控制執(zhí)行這些操作
注意卡頓造成丟幀的情況
參考資料
Android Touch事件分發(fā)超詳細(xì)解析(附源碼)
Android View 事件分發(fā)機(jī)制 來源
Android 點(diǎn)擊事件的來源
Android Choreographer 源碼分析
一文讀懂Android View事件分發(fā)機(jī)制
hencoder 觸摸反饋