Android事件分發機制一:事件是如何到達activity的?
阿新 • • 發佈:2021-01-16
> 事件分發,真的一定從Activity開始嗎?
## 前言
很高興遇見你~
事件分發,android中一個老生常談的話題了。基本的流程我們也都知道是從Activity開始分發,但有一個關鍵問題是:**事件是如何到達Activity的** ?
你以為我接下來要開始講原始碼、系統底層了?不不不,本文不講這些。我們要探究的是,一個觸控資訊從系統底層產生之後,是如何一步步到達目標view的。
本文是筆者android觸控事件系列文章的開篇,主要的內容是分析觸控事件傳遞的路徑。不會糾結於原始碼與底層,而是把觸控事件來源的大體流程呈現出來,便於對事件分發體系有個更加完整的理解。
## 管理單位:window
android的view管理是以window為單位的,每個window對應一個view樹。Window機制不僅管理著view的顯示,也負責view的事件分發。關於window的本質,可以閱讀筆者的另一篇文章[window機制](https://juejin.cn/post/6888688477714841608)。研究事件分發的來源,需要從window機制入手。
所以,首先要了解一個概念:view樹。
我們的應用佈局,一般是有多層viewGroup和view的巢狀,如下圖:
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5301713e67a34cab8887cf5f00bd45e0~tplv-k3u1fbpfcp-zoom-1.image)
而他們對應的結構關係如下圖所示
![image](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/754f43ce75e841229cfc44392fdaa3a5~tplv-k3u1fbpfcp-zoom-1.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機制](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af6d0eeb6fe5488690a8a6bed48d95bd~tplv-k3u1fbpfcp-zoom-1.image)
對上圖做個簡單解釋。
- 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系統-事件處理全過程](http://gityuan.com/2016/12/31/input-ipc/)。這裡不展開講解。大體的過程如下圖:
![事件是如何到達viewRootImpl](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1497277cd15b4230af087535f01078ef~tplv-k3u1fbpfcp-zoom-1.image)
當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重寫了該方法,邏輯比較簡單,僅僅做了一個判斷:
```java
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對事件的分發流程](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ce4115c10663451ab8add7741175279c~tplv-k3u1fbpfcp-watermark.image)
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。
## 總結
最後我們對整個流程進行一次回顧:
![整體流程](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6f1723736fc34cedbcb654e194e91b73~tplv-k3u1fbpfcp-zoom-1.image)
1. IMS從系統底層接收到事件之後,會從WMS中獲取window資訊,並將事件資訊傳送給對應的viewRootImpl
2. viewRootImpl接收到事件資訊,封裝成motionEvent物件後,傳送給管理的view
3. view會根據自身的型別,對事件進行分發還是自己處理
4. 頂層viewGroup一般是DecorView,DecorView會根據自身callBack的情況,選擇呼叫callBack或者呼叫父類ViewGroup的方法
到這裡,雖然觸控事件的“去脈”我們還不清楚,但是他的“來龍”就已經非常清晰了。後續Activity、Dialog等callBack,viewGroup,其他頂層ViewGroup物件如何對觸控事件進行處理,我將會在下一篇文章進行分析。
事件分發的來源遠沒有這麼簡單,原始碼的細節有非常多的內容值得我們去學習,而本文只是把整體的流程抽了出來。這裡對於有興趣讀者推薦一本書:《深入理解android卷Ⅲ》。
感謝閱讀,希望文章對你有幫助!
> 全文到此,原創不易,覺得有幫助可以點贊收藏評論轉發。
> 筆者才疏學淺,有任何想法歡迎評論區交流指正。
> 如需轉載請評論區或私信交流。
>
> 另外歡迎光臨筆者的個人部落格:[傳送門](https://qwerhuan.gi