Android 事件分發完全解析之事件從何而來

發表於2015-11-07

上一節Android事件分發完全解析之為什麼是她中我們簡略地分析了事件分發機制的由來,這裡要說明一點,Android(或者說任何的驅動系統)都包含大量不同型別的事件,比如按鍵啦、軌跡球啦、滑鼠啦、觸控啦、紅外線啦等等等,這裡為了簡化問題也為了切合實際,我們只針對觸控事件進行分析,至於其他的一些雜七雜八的事件其實都很好理解就不多說了。

那麼在Android中一個觸控事件究竟是從何而來的呢?對事件分發稍有了解的童鞋一定知道dispatchtouchevent方法,都知道View對觸控事件進行分發的起點,但是傳入dispatchtouchevent方法中的觸控事件又是從何而來的呢?往上一步步追蹤你會發現程式碼呼叫無窮無盡找不到頭……有時候盲目地去read fuck source code反而會讓你更困惑,其實用腦子想想理清邏輯就可以很快找到答案,我們都知道一個事件的產生肯定需要使用者的互動,也就是說,只有當使用者觸控螢幕或按下某個按鍵之類的操作之後系統才能做出事件響應,而每一個這樣的操作我們都可將其當作事件的“源頭”。

那麼捕獲這些最原始互動資訊的獵手應該是誰呢?還會是View?還會是Activity?還會是ViewRootImpl還會是WMS嗎?這些framework中的構件相對於更底層的機制來說還是太“高階”了,我們知道Android是基於Linux的一款作業系統,Linux其本身就有一個很Perfect的Input子系統架構,Android雖然也實現了幾個屬於自己的機制,但是大部分底層的呼叫還是基於Linux所提供的操作介面,比如對Input驅動的編寫就是基於Linux Input系統字元驅動的操作介面,關於Linux的這部分大家如果有興趣可以去看看私房菜,這裡就不多扯了,這裡你僅需要知道在Android中Linux的Input子系統會在/dev/input/路徑下讀寫以event[NUMBER]為名的硬體輸入裝置節點。

這些節點都是跟具體硬體有關的,所以呢可能每一款裝置的具體節點名都是不一樣的,比如在我的mx3中/dev/input/event0為mxhub-key而/dev/input/event1為gp2ap。具體的節點資訊可通過Android提供的getevent工具檢視,如果你的裝置已經連線了PC或者模擬器已啟動,adb shell後getevent即可獲取事件讀寫的實時狀態,當然各個裝置是不一樣的,比如mx3中通過getevent檢視所有Input節點:

可見mx3中有8個Input子系統,分別為:

  • 位於event0節點下讀寫魅族呼吸燈按鈕也就是螢幕下方圓形的那個發光主鍵的“mxhub-keys”子系統
  • 位於event4節點下讀寫重力感測器的“lsm330dlc_gyr”子系統
  • 位於event3節點下讀寫加速度感測器的“lsm330dlc_acc”子系統
  • 位於event1節點下讀寫紅外線感測器的“gp2ap”子系統(魅族mx3是用紅外線來測定光感和距離的)
  • 位於event5節點下讀寫螢幕觸控的“mx_ts”子系統
  • 位於event6節點下讀寫物理按鍵的“gpio-keys”子系統
  • 位於event7節點下讀寫耳機按鍵的“Headset”子系統(有些手機監控線控裝置的系統常以hook為名,這裡魅族使用不多見Headset來表示該類不知是否是有佈局頭戴式裝置的意義)
  • 位於event2節點下讀寫羅盤的“compass”子系統

而mx3(不能說是Android哈這裡針對mx3)就是從這些系統節點中讀寫裝置的事件資訊,以上資訊我是在mx3滅屏時也就是按下電源鍵關閉螢幕後獲取的,如果我們再次按下電源點亮螢幕,核心驅動就會不斷地監控一些必要的讀寫事件,這裡我們不想讓我們的Terminal一直輸出,使用getevent的-c引數設定最大的輸出條數檢視即可:

11

這裡我設定了最大16條輸出,亮屏後可見如上資訊顯示,如果不作輸出限制,Terminal就會一直輸出……也就是說加速度和紅外線感測器的子系統會不斷檢測外部環境的變化,至於為什麼,想想加速度感應和紅外感應我想大家都應該能心知肚明。如果我們在getevent後在螢幕上快速Touch一下,那麼event5節點下的子系統就回立即作出迴應:

22

如上圖中我們快速接觸螢幕後得到的資訊,可能不好懂對吧,給getevent加上-l引數格式化輸出看看:

33

注:因為硬體裝置、觸控區域力度、持續時間等因素的影響你的輸出結果可能跟我不大一樣,以具體你具體的輸出為準,但輸出資訊大致是類似的。

這裡拿第一條資訊“/dev/input/event5: EV_ABS       ABS_MT_TRACKING_ID   000008e0”來說,其中/dev/input/event5上面我們說了表示裝置節點;EV_ABS表示type事件型別;ABS_MT_TRACKING_ID表示code事件的掃描碼;000008e0則表示具體的事件值。這些資訊的定義都在kernel/include/linux/input.h檔案中作出了宣告,比如type輸入裝置型別包括如下這些:

具體它們都代表什麼就不多說了,都是些Linux的東西,一般來說比較常用的是EV_REL表示相對座標型別、EV_ABS表示絕對座標型別、EV_KEY表示物理鍵盤事件型別,EV_SYN表示同步事件型別等等,一個裝置可以支援多個不同的事件型別而每個事件型別呢又可以設定不同的事件碼,比如EV_SYN同步事件型別的事件碼如下:

其它的就不一一列舉了都可以在input.h檔案中找到相應的定義。上面圖例中的一次快速觸屏後的反饋資訊可以做如下描述:

如上過程只是一次快速觸碰所產生的節點讀取,如果我們做出更復雜的手勢操作比如多點切西瓜那樣的效果尼瑪光是採集這些資訊都不得了!不過值得慶幸的是,對這些原始資訊的採集用不著應用層的開發者來做,對於應用開發來說我們往往更關心一次事件是單擊呢還是雙擊還是長按等等,而不是面對這些龐大而又複雜的原始資訊,So,Android在獲取到這些原始資料後會對其進行一定的轉化便於使用,當然如果你需要做驅動開發涉及到這些原始資料的操作也可以直接獲取其使用亦可。

可見,Linux中Input子系統對輸入裝置資訊的捕獲可以說是Android事件來源的老祖宗,當然這些玩意對於應用開發者來說沒必要深入理解,僅作了解即可。文章開頭我們曾這樣說過,一次事件的源頭必定來自於使用者的互動,那麼事實上是不是如此呢?早年打過遊戲的童鞋肯定對按鍵精靈這玩意很熟悉吧,至少不陌生,我們使用按鍵精靈來模擬使用者對鍵位的操作,也就是說我們並不一定需要使用者真實的互動,模擬也行。同樣地Android也給我們提供了另外一個很酷的工具sendevent來向/dev/input/寫入事件資訊模擬事件的產生,具體用法跟getevent很類似,就不多說了,自行嘗試。

 

相關文章