Android事件分發機制五:面試官你坐啊

一隻修仙的猿發表於2021-01-27

前言

很高興遇見你~

事件分發系列文章已經到最後一篇了,先來回顧一下前面四篇,也當個目錄:

本文是最後一篇,主要是模擬面試情況提出一些問題以及解答,也當是整個事件分發知識的回顧。讀者也可以嘗試一下看看這些問題是否都能解答出來。

面試開始

  1. 學過事件分發嗎,聊聊什麼是事件分發

    事件分發是將螢幕觸控資訊分發給控制元件樹的一個套機制。
    當我們觸控螢幕時,會產生一些列的MotionEvent事件物件,經過控制元件樹的管理者ViewRootImpl,呼叫view的dispatchPointerEvnet方法進行分發。

  2. 那主要的分發流程是什麼:

    在程式的主介面情況下,佈局的頂層view是DecorView,他會先把事件交給Activity,Activity呼叫PhoneWindow的方法進行分發,PhoneWindow會呼叫DecorView的父類ViewGroup的dispatchTouchEvent方法進行分發。也就是Activity->Window->ViewGroup的流程。ViewGroup則會向下去尋找合適的控制元件並把事件分發給他。

  3. 事件一定會經過Activity嗎?

    不是的。我們的程式介面的頂層viewGroup,也就是decorView中註冊了Activity這個callBack,所以當程式的主介面接收到事件之後會先交給Activity。
    但是,如果是另外的控制元件樹,如dialog、popupWindow等事件流是不會經過Activity的。只有自己介面的事件才會經Activity。

  4. Activity的分發方法中呼叫了onUserInteraction()方法,你能說說這個方法有什麼作用嗎?

    好的。這個方法在Activity接收到down的時候會被呼叫,本身是個空方法,需要開發者自己去重寫。
    通過官方的註釋可以知道,這個方法會在我們以任意的方式開始與Activity進行互動的時候被呼叫。比較常見的場景就是屏保:當我們一段時間沒有操作會顯示一張圖片,當我們開始與Activity互動的時候可在這個方法中取消屏保;另外還有沒有操作自動隱藏工具欄,可以在這個方法中讓工具欄重新顯示。

  5. 前面你講到最後會分發到viewGroup,那麼viewGroup是如何分發事件的?

    viewGroup處理事件資訊分為三個步驟:攔截、尋找子控制元件、派發事件。

    事件分發中有一個重要的規則:一個觸控點的一個事件序列只能給一個view處理,除非異常情況。所以如果viewGroup消費了down事件,那麼子view將無法收到任何事件。

    viewGroup第一步會判讀這個事件是否需要分發給子view,如果是則呼叫onInterceptTouchEvent方法判斷是否要進行攔截。
    第二步是如果這個事件是down事件,那麼需要為他尋找一個消費此事件的子控制元件,如果找到則為他建立一個TouchTarget。
    第三步是派發事件,如果存在TouchTarget,說明找到了消費事件序列的子view,直接分發給他。如果沒有則交給自己處理。

  6. 你前面講到“一個觸控點的一個事件序列只能給一個view處理,除非異常情況”,這裡有什麼異常情況呢?如果發生異常情況該如何處理?

    這裡的異常情況主要有兩點:1.被viewGroup攔截,2.出現介面跳轉等其他情況。

    當事件流中斷時,viewGroup會傳送一個ACTION_CANCEL事件給到view,此時需要做一些狀態的恢復工作,如終止動畫,恢復view大小等等。

  7. 那既然說到ACTION_CANCEL型別,那你可以說說還有什麼事件型別嗎?

    除了ACTION_CANCEL,其他事件型別還有:

    • ACTION_MOVE:當我們手指在螢幕上滑動時產生此事件
    • ACTION_UP:當我們手指抬起時產生此事件

    此外多指操作也比較常見:

    • ACTION_POINTER_DOWN: 當已經有一個手指按下的情況下,另一個手指按下會產生該事件
    • ACTION_POINTER_UP: 多個手指同時按下的情況下,抬起其中一個手指會產生該事件。

    一個完整的事件序列是從ACTION_DOWN開始,到ACTION_UP或者ACTION_CANCEL結束。
    一個手指的完整序列是從ACTION_DOWN/ACTION_POINTER_DOWN開始,到ACTION_UP/ACTION_POINTER_UP/ACTION_CANCEL結束。

  8. 哦?說到多指,那你知道ViewGroup是如何將多個手指產生的事件準確分發給不同的子view嗎

    這個問題的關鍵在於MotionEvent以及ViewGroup內部的TouchTarget。

    每個MotionEvent中都包含了當前螢幕所有觸控點的資訊,他的內部用了一個陣列來儲存不同的觸控id所對應的座標數值。

    當一個子view消費了down事件之後,ViewGroup會為該view建立一個TouchTarget,這個TouchTarget就包含了該view的例項與觸控id。這裡的觸控id可以是多個,也就是一個view可接受多個觸控點的事件序列。

    當一個MotionEvent到來之時,ViewGroup會將其中的觸控點資訊拆開,再分別傳送給感興趣的子view。從而達到精準傳送觸控點資訊的目的。

  9. 那view支援處理多指資訊嗎?

    View預設是不支援的。他在獲取觸控點資訊的時候並沒有傳入觸控點索引,也就是獲取的是MotionEvent內部陣列中的第一個觸控點的資訊。多指需要我們自己去重寫方法支援他。

  10. 嗯嗯...那View是如何處理觸控事件的?

    首先,他會判斷是否存在onTouchListener,存在則會呼叫他的onTouch方法來處理事件。如果該方法返回true那麼就分發結束直接返回。而如果該監聽器為null或者onTouch方法返回了false,則會呼叫onTouchEvent方法來處理事件。

    onTouchEvent方法中支援了兩種監聽器:onClickListener和onLongClickListener。View會根據不同的觸控情況來呼叫這兩個監聽器。同時進入到onTouchEvent方法中,無論該view是否是enable,只要是clickable,他的分發方法都是返回true。

    總結一下就是:先呼叫onTouchListener,再呼叫onClickListener和onLongClickListener。

  11. 你前面多次講到分發方法和返回值,那你可以講講主要有什麼方法以及他們之間的關係嗎?

    嗯嗯。核心的方法有三個:dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent。

    簡單來說:dispatchTouchEvent是核心的分發方法,所有分發邏輯都在這個方法中執行;onInterceptTouchEvent在viewGroup負責判斷是否攔截;onTouchEvent是消費事件的核心方法。viewGroup中擁有這三個方法,而view沒有onInterceptTouchEvent方法。

    • viewGroup
      1. viewGroup的dispatchTouchEvent方法接收到事件訊息,首先會去呼叫onInterceptTouchEvent判斷是否攔截事件
        • 如果攔截,則呼叫自身的onTouchEvent方法
        • 如果不攔截則呼叫子view的dispatchTouchEvent方法
      2. 子view沒有消費事件,那麼會呼叫viewGroup本身的onTouchEvent
      3. 上面1、2步的處理結果為viewGroup的dispatchTouchEvent方法的處理結果,沒有消費則返回false並返回給上一層的onTouchEvent處理,如果消費則分發結束並返回true。
    • view
      1. view的dispatchTouchEvent預設情況下會呼叫onTouchEvent來處理事件,返回true表示消費事件,返回false表示沒有消費事件
      2. 第1步的結果就是dispatchTouchEvent方法的處理結果,成功消費則返回true,沒有消費則返回false並交給上一層的onTouchEvent處理

    簡單來說,在控制元件樹中,每個viewGroup在dispatchTouchEvent方法中不斷往下分發尋找消費的view,如果底層的view沒有消費事件則會一層層網上呼叫viewGroup的onTouchEvent方法來處理事件。

    同時,由於Activity繼承了Window.CallBack介面,所以也有dispatchTouchEvent和onTouchEvent方法:

    1. activity接收到觸控事件之後,會直接把觸控事件分發給viewGroup
    2. 如果viewGroup的dispatchTouchEvent方法返回false,那麼會呼叫Activity的onTouchEvent來處理事件
    3. 第1、2步的處理結果就是activity的dispatchTouchEvent方法的處理結果,並返回給上層
  12. 看來你對事件分發瞭解得挺多的,那你在實際中有運用到事件分發嗎?

    嗯嗯,有的。舉兩個例子。

    第一個需求是要設計一個按鈕塊,按下的時候會縮小高度變低同時變得半透明,放開的時候又會回彈。這個時候就可以在這個按鈕的onTouchEvent方法中判斷事件型別:down則開啟按下動畫,up則開啟釋放動畫。同時注意接收到cancel事件的時候要恢復狀態。

    第二個是滑動衝突。解決滑動衝突的核心思路就是把滑動事件根據具體的情況分發給viewGroup或者內部view。主要的方法有外部攔截法和內部攔截法。
    外部攔截法的思路就是在viewGroup中判斷滑動的情況,對符合自身滑動的事件進行攔截,對不符合的事件不攔截,給到內部view。內部攔截法的思路要求viewGroup攔截除了down事件以外的所有事件,然後再內部view中判斷滑動的情況,對符合自身滑動情況的時間設定禁止攔截標誌,對不符合自身滑動情況的事件則取消標誌讓viewGroup進行攔截。

  13. 那外部和內部攔截法該如何選擇呢?

    在一般的情況下,外部攔截法不需要對子view進行方法重寫,比內部攔截法更加簡單,推薦使用外部攔截法。

    但如果需要在子view判斷更多的觸控情況時,則使用內部攔截法可更加方法子view處理情況。

  14. 前面一直聊到觸控事件,那你知道一個觸控事件是如何從觸控螢幕開始產生的嗎?

    額....在螢幕接收到觸控資訊後,會把這個資訊交給InputServiceManager去處理,最後通過WindowManagerService找到符合的window,並把觸控資訊傳送給viewRootImpl,viewRootImpl經過層層封和處理之後,產生一個MotionEvent事件分發給view。

  15. 可以具體講講前面IMS處理的流程嗎?

    啊。。這。。。嗯。。。。不會。。。

  16. 你還有什麼想問的嗎?

    可不可以。。。。給我個小小的點贊再走?

  17. 下次一定。

    =_=....

最後

關於面試,我一直堅持的一個觀點就是:可以面向面試知識點學習,但不可面向面試題目答案學習 。把相關熱門題目的答案背誦下來可以忽悠到一些面試官,但現在基本上都不是簡單的詢問什麼是事件分發,而會給一個具體的需求讓我們思考等等。背誦面試題短期可能會讓我們好像學到了很多,但事實上,我們什麼都沒學到。

事件分發系列文章到此完結。有疑問歡迎評論區交流,希望文章對你有幫助~

都看到這了,要不給作者留下個點贊再走?

全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。
筆者才疏學淺,有任何想法歡迎評論區交流指正。
如需轉載請評論區或私信交流。

另外歡迎光臨筆者的個人部落格:傳送門

相關文章