Android事件分發機制一:事件是如何到達activity的?


事件分發,真的一定從Activity開始嗎?

前言

很高興遇見你~

事件分發,android中一個老生常談的話題了。基本的流程我們也都知道是從Activity開始分發,但有一個關鍵問題是:事件是如何到達Activity的

你以為我接下來要開始講源碼、系統底層了?不不不,本文不講這些。我們要探究的是,一個觸摸信息從系統底層產生之后,是如何一步步到達目標view的。

本文是筆者android觸摸事件系列文章的開篇,主要的內容是分析觸摸事件傳遞的路徑。不會糾結於源碼與底層,而是把觸摸事件來源的大體流程呈現出來,便於對事件分發體系有個更加完整的理解。

管理單位:window

android的view管理是以window為單位的,每個window對應一個view樹。Window機制不僅管理着view的顯示,也負責view的事件分發。關於window的本質,可以閱讀筆者的另一篇文章window機制。研究事件分發的來源,需要從window機制入手。

所以,首先要了解一個概念:view樹。

我們的應用布局,一般是有多層viewGroup和view的嵌套,如下圖:

image

而他們對應的結構關系如下圖所示

image

此時,我們就可以稱該布局是以一個LinearLayout為根的一棵view樹。LinearLayout可以直接訪問FrameLayout和RelativeLayout,因為他們都是LinearLayout的子view,同樣的RelativeLayout可以直接訪問Button。

每一棵view樹都有一個根,叫做ViewRootImpl ,他負責管理這整一棵view樹的繪制、事件分發等。所以可以說,事件分發是從viewRootImpl開始的。

我們的應用界面一般會有多個view樹,我們的activity布局就是一個view樹、其他應用的懸浮窗也是一個view樹、dialog界面也是一個view樹、我們使用windowManager添加的view也是一個view樹等等。最簡單的view樹可以只有一個view。

android中view的繪制和事件分發,都是以view樹為單位。每一棵view樹,則為一個window 。系統服務WindowManagerService,管理界面的顯示就是以window為單位,也可以說是以view樹為單位。而view樹是由viewRootImpl來負責管理的,所以可以說,wms(WindowManagerService的簡寫)管理的是viewRootImpl。如下圖:

window機制

對上圖做個簡單解釋。

  • wms是運行在系統服務進程的,負責管理所有應用的window。應用程序與wms的通信必須通過Binder進行跨進程通信。
  • 每個viewRootImpl在wms中都有一個windowState對應,wms可以通過windowState找到對應的viewRootImpl進行管理。

了解window機制,跟事件分發有什么關系呢?我們要知道,window機制管理的,不僅是view的顯示邏輯,事件分發也是其中的一個重要部分。了解window機制的一個重要原因是:事件分發並不是由Activity驅動的,而是由系統服務驅動viewRootImpl來進行分發 ,甚至可以說,在框架層角度,和Activity沒有任何關系。這將有助於我們對事件分發的本質理解。

那么觸摸信息是如何一步步到達viewRootImpl、viewRootImpl如何對觸摸信息進行分發處理的呢,這是我們接下來要討論的。

觸摸信息是如何到達viewRootImpl的?

我們都知道的是,在我們手指觸摸屏幕時,即產生了觸摸信息。這個觸摸信息由屏幕這個硬件產生,被系統底層驅動獲取,交給Android的輸入系統服務:InputManagerService,也就是IMS。

IMS會對這個觸摸信息進行處理,通過WMS找到要分發的window,隨后發送給對應的viewRootImpl。所以發送觸摸信息的並不是WMS,WMS提供的是window的相關信息。

這一部分涉及到系統底層的邏輯,不是本文的重點,感興趣的讀者推薦閱讀gityuan博主的文章Input系統-事件處理全過程。這里不展開講解。大體的過程如下圖:

事件是如何到達viewRootImpl

當viewRootImpl接收到觸摸信息時,也正是應用程序進程事件分發的開始。

viewRootImpl是如何分發事件的?

前面我們講到,viewRootImpl管理一棵view樹,view樹的最外層是viewGroup, 而viewGroup繼承於view。因此整一棵view樹,從外部可以看做一個view。viewRootImpl接收到觸摸信息之后,經過處理之后,封裝成MotionEvent對象發送給他所管理的view,由view自己進行分發。

前面我們講到,view樹的根節點可以是一個viewGroup,也可以是一個單獨的view,因此,這里的派發就會有兩種不同的方式:直接給view進行處理 or viewGroup進行事件分發。viewGroup繼承自view,view中有一個方法用於分發事件:dispatchTouchEvent 。子類可重寫該方法來實現自己的分發邏輯,ViewGroup重寫了該方法。

我們的應用布局界面或者dialog的布局界面,頂層的viewGroup為DecorView,因此會調用DecorView的 dispatchTouchEvent 方法進行分發。DecorView重寫了該方法,邏輯比較簡單,僅僅做了一個判斷:

DecorView.java api29
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
  1. 如果window callBack對象不為空,則調用callBack對象的分發方法進行分發
  2. 如果window callBack對象為空,則調用父類ViewGroup的事件分發方法進行分發

而Activity實現了Window.CallBack接口,並在創建布局的時候,把自己設置給了DecorView,因此在Activity的布局界面中,DecorView會把事件分發給Activity進行處理。同理,在Dialog的布局界面中,會分發給實現了callBack接口的Dialog。

而如果頂層的viewGroup不是DecorView,那么對調用對應view的dispatchTouchEvent方法進行分發。例如,頂層的view是一個Button,那么會直接調用Button的 dispatchTouchEvent 方法;如果頂層viewGroup子類沒有重寫 dispatchTouchEvent 方法,那么會直接調用ViewGroup默認的 dispatchTouchEvent 方法。

整體的流程如下圖:

viewRootImpl對事件的分發流程

  1. viewRootImpl會直接調用管理的view的 dispatchTouchEvent 方法,根據具體的view的類型,調用具體的方法。
  2. view樹的根view可能是一個view,也可能是一個viewGroup,view會直接處理事件,而viewGroup則會進行分發。
  3. DecorView重寫了 dispatchTouchEvent 方法,會先判斷是否存在callBack,優先調用callBack的方法,也就是把事件傳遞給了Activity。
  4. 其他的viewGroup子類會根據自身的邏輯進行事件分發。

因此,觸摸事件一定是從Activity開始的嗎?不是,Activity只是其中的一種情況,只有Activity自己負責的那一棵view樹,才一定會到達activity,而其他的window,則不會經過Activity。觸摸事件是從viewRootImpl開始,而不是Activity。

總結

最后我們對整個流程進行一次回顧:

整體流程

  1. IMS從系統底層接收到事件之后,會從WMS中獲取window信息,並將事件信息發送給對應的viewRootImpl
  2. viewRootImpl接收到事件信息,封裝成motionEvent對象后,發送給管理的view
  3. view會根據自身的類型,對事件進行分發還是自己處理
  4. 頂層viewGroup一般是DecorView,DecorView會根據自身callBack的情況,選擇調用callBack或者調用父類ViewGroup的方法

到這里,雖然觸摸事件的“去脈”我們還不清楚,但是他的“來龍”就已經非常清晰了。后續Activity、Dialog等callBack,viewGroup,其他頂層ViewGroup對象如何對觸摸事件進行處理,我將會在下一篇文章進行分析。

事件分發的來源遠沒有這么簡單,源碼的細節有非常多的內容值得我們去學習,而本文只是把整體的流程抽了出來。這里對於有興趣讀者推薦一本書:《深入理解android卷Ⅲ》。

感謝閱讀,希望文章對你有幫助!

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

另外歡迎光臨筆者的個人博客:傳送門


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM