Android - 事件分發(fā)

本文從以下方面分析 Android 的事件分發(fā),旨在梳理和理解放祟,并沒有深度展開

  1. 什么是事件
  2. 事件的分類
  3. 事件的種類
  4. 一次 Touch 事件流的序列
  5. Touch 事件的傳遞層級(jí)
  6. Touch 事件的分發(fā)
  7. Touch 事件的沖突解決
  8. 如何確定哪個(gè) View 處理事件
  9. 事件的來源
  10. 渲染

部分內(nèi)容為自己的理解例嘱,如果錯(cuò)誤還望指正

1. 什么是事件

與安卓設(shè)備進(jìn)行的一次交互亲桦,不同的安卓設(shè)備交互方式不一樣。

如:
按鍵:Home 鍵己儒,Back 鍵淑玫,音量鍵
觸摸屏幕:手指在屏幕上 按下 / 滑動(dòng) / 抬起
軌跡球:例如黑莓手機(jī)存在軌跡球

在 Android 中統(tǒng)稱為 輸入事件巾腕,API 體現(xiàn)在 InputEvent

2. 事件分類

Android 中的輸入事件 InputEvent 子類有兩個(gè):

  1. KeyEvent:按鍵 key、按鈕 button 事件
  2. MotionEvent:鼠標(biāo) mouse混移、畫筆 pen祠墅、手指 finger、軌跡球 trackball 等運(yùn)動(dòng)事件

不同類型的安卓設(shè)備歌径,API 體現(xiàn)在 InputDevice

  1. SOURCE_CLASS_POINTER:指示設(shè)備,eg:觸摸屏幕 SOURCE_TOUCHSCREEN 或鼠標(biāo)SOURCE_MOUSE
  2. SOURCE_CLASS_TRACKBALL:軌跡球
  3. SOURCE_CLASS_JOYSTICK:搖桿
  4. ...

3. 事件的種類

KeyEvent 常見事件有:

  1. KEYCODE_HOME
  2. KEYCODE_MENU
  3. KEYCODE_BACK
  4. KEYCODE_MEDIA_PLAY / KEYCODE_MEDIA_PAUSE
  5. ...

獲取 KeyEvent 方式:KeyEvent#getKeyCode()

MotionEvent 常見的事件有:

  1. ACTION_DOWN / ACTION_UP / ACTION_MOVE
  2. ACTION_CANCEL
  3. ACTION_POINTER_DOWN / ACTION_POINTER_UP
  4. ...
  5. 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)核生成的原始事件慨蛙,而交付給窗口的則是 KeyEventMotionEvent 對(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 觸摸反饋

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末玛臂,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子封孙,更是在濱河造成了極大的恐慌迹冤,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虎忌,死亡現(xiàn)場(chǎng)離奇詭異泡徙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)膜蠢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門堪藐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人挑围,你說我怎么就攤上這事礁竞。” “怎么了杉辙?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵模捂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蜘矢,道長(zhǎng)狂男,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任硼端,我火速辦了婚禮并淋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘珍昨。我一直安慰自己县耽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布镣典。 她就那樣靜靜地躺著兔毙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兄春。 梳的紋絲不亂的頭發(fā)上澎剥,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音赶舆,去河邊找鬼哑姚。 笑死祭饭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的叙量。 我是一名探鬼主播倡蝙,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼绞佩!你這毒婦竟也來了寺鸥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤品山,失蹤者是張志新(化名)和其女友劉穎胆建,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體肘交,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笆载,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酸些。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宰译。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖魄懂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情闯第,我是刑警寧澤市栗,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站咳短,受9級(jí)特大地震影響填帽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咙好,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一篡腌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧勾效,春花似錦嘹悼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至萌腿,卻和暖如春限匣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毁菱。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工锌历, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人峦筒。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓究西,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親勘天。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怔揩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355