Android 事件分發完全解析之為什麼是她

發表於2015-11-06

記得以前教我code的啟蒙老師對我說過,如果你想學習一個新事物只需要三個W:Why、What、How,也就是:為什麼?是什麼?怎麼做?當你搞懂這三個W之後,你對新事物必定會有一個透徹的瞭解。然而對大多數Student來說他們往往最有興趣的是“How”也是他們最先接觸的一個W,其次則是“What”,而“Why”往往會被選擇性地忽略,這也是很多時候我們為什麼無法將現有知識水平提升一個層次的根本原因。

同樣地,對於事件分發,我先不打算直接切入主題教大家如何如何看原始碼如何如何分析Android工程師們複雜的思維邏輯,如何如何去理清錯綜交雜的抽象層次。按照我們上面所說,我們先來試著追尋Android設計師們的思考方式嘗試去理解為什麼要有事件分發,大家都知道事件分發是Android事件處理的一個Breaking Point,難以理解,既然如此為什麼要把這麼複雜的東西作為Android的一部分呢?既然需要,那麼其必定是必不可缺無法撼動的,but why?淡定,跟隨大偵探愛爾摩斯來追尋這不為人知的醜陋祕密。

我們都知道,我們的手機或其他的手持裝置甚至顯示裝置等等其顯示區域都是有限的,即便是股票的大盤螢幕也是有限的,沒有無限的視屏,更何況是巴掌大的一塊顯示區域。假設你是Android的一名設計師,要你來確定一種介面元素的顯示方式,那麼按照人傳統的操作習慣和思維方式,你有且僅有兩種選擇:

Chioce A:

將介面元素以逐個排列的方式顯示在螢幕上,如下圖:

11

該方式可以讓所有的介面元素都有屬於自己獨自的空間,對系統來說管理這些空間要簡便得多,因為它們佔有確定的螢幕區域,只需在顯示時記錄這些Area則可,各個元素互不干預。但是,瞎子也能看出來這樣的顯示方式有個明眼的弊端,隨著介面元素的增加,一旦撐滿螢幕,所有的元素大小都將被不斷調整以便能放入更多的介面元素,而且,如果我們想將元素以疊加的方式排列,這樣的顯示方式是無法做到的,So,pass it。

Chioce B:

將介面元素層疊顯示,每個元素理論上都擁有與顯示裝置尺寸一致的區域,如下圖:

22

這麼一來,我們可以在有限的顯示裝置區域內放下理論上無數個介面元素,同時還可以很好地解決掉Chioce A帶來的很多限制問題。但是,世界並非完美的,即便在無所不能的code世界也總有缺陷。在Chioce A的方案中因為每個介面元素都有自己唯一的獨立區域,每當使用者以觸控顯示裝置的方式與之互動時總會Touch到唯一的一個元素。但是在Chioce B中則不行了……因為介面元素之間的層疊關係,每當我們觸控時都有可能Touch到N個層疊的元素,怎麼辦呢?這時我們就必須引入一套機制,讓系統有能力去處理並判斷使用者想要Touch哪個元素,So,事件機制油然而生。

當然,事件機制的出現並非因為觸控式螢幕,而上述的兩種介面顯示思想也並非來自Android的射雞師,自顯示裝置出現的那天起便已存在,這裡僅作一個引子拋磚引玉而已。

既然Choice B是我們的選擇,那麼我們應該如何對事件進行投遞呢?Suppose我們有如下介面元素構成:

33

注:這裡暫不考慮什麼Window、DecorView等等,我們儘量抽象思維不要讓思維慣性地和code靠在一起令邏輯變得複雜。

如上圖所示,G為我們的顯示裝置螢幕,A、C、E都在G中為同一層級,而B、D、F分別在A、C、E中,其層疊效果就如上圖所示,此時,假設我們Touch螢幕中的某一區域,如下圖中的紅色區域:

44

這時按照人的行為模式F元素是應該獲取到該Touch事件的,也就是說當Touch在螢幕G上發生後,交由F元素:

G—–>F

交由F處理後那麼F就有責任告知系統本次事件我吃掉了。但是這樣會有個問題,如果說我們想讓F下方的E也響應本次的Touch事件呢?那麼我們的傳遞方式就該是這樣的一個模式:

G—–>F—–>E

也就是說E和F都能吃掉該次事件,如果位於F和E下方的DCBA也要吃呢?這就陷入了一個窘境,每次誰要吃我們是事先不知道的,有可能誰一時興起要吃掉該次事件,如果按照我們當前的這種“置頂優先”的方式來傳遞事件肯定是不科學的,同時也不符合我們介面元素層疊結構之間的關係。那麼好辦了,我們從底層開始傳遞事件,還是拿上圖的Touch點來說,當觸控區域在上圖的紅色位置時,我們可以讓系統通過一系列的計算獲知事件點所在區域,一旦發現事件點落在某個介面元素的範圍內(上圖中事件點在所有的元素範圍內)那麼我們就按其層疊順序依次將事件出遞出去,比如上圖中,事件由螢幕G點出發途徑:

G—–>A—–>B—–>C—–>D—–>E—–>F

這樣一次事件就可以依次經過多個可能捕獲它的元素,這樣的傳遞方式有一個專業的稱謂:隧道式傳遞。一旦某個元素想要吃掉該次事件那麼我們只需通過一定的邏輯處理告知系統本次事件我吃掉了,你不需要再傳遞給別人了!這時,我們就該把這個訊號層層遞交返回給系統,這種遞交的方式也有一個有趣的名字:冒泡機制。就像水裡的魚在某個位置吐泡,總會冒回水面的。而這種以鏈式結構傳遞的方式在設計模式中也有跡可循,具體的飯迎大家關注SM哥的SAOS開源組織發起的Android與設計模式系列下Aige出品的責任鏈模式。當然你也可以讓一次事件無序傳遞甚至亂傳,只不過不提倡而已。

在Android的實際處理中事件機制絕非上面我們說的那麼簡單,上面的分析僅僅是一個濃縮版的事件機制結構。本節僅為一個開端,作為詳解事件機制的一個引子沒有程式碼的涉及,本來在編寫自定義系列的互動文章時發現肯定會涉及事件機制,如果一起寫顯得太臃腫,乾脆分開來更好地闡述,本系列作為一箇中間橋樑上連自定義系列,下接GUI框架剖析,So,敬請關注。

相關文章