Android中MotionEvent的來源和ViewRootImpl

發表於2016-03-07

前言

很久沒有發表文章了,今天來一篇,大家撒花~~~

本文打算分析下Android中點選事件的來源,順便提及下ViewRootImpl。

Android中點選事件的來源

這個問題,也許你會說“這還用你說嗎?我可是看過藝術探索的人”,我知道藝術探索中的確是詳細介紹了點選事件的傳遞流程,反正大致就是點選事件從Activity傳遞給PhoneWindow,然後PhoneWindow再傳遞給DecorView,接著DecorView就進行後續的遍歷式的傳遞。這都沒錯,但是點選事件是誰傳遞給Activity的呢?這個大家可能不清楚吧?那本文就是分析這個問題的。

首先看Activity的實現,如下,Activity實現了一個特殊的介面:Window.Callback。

那麼Window.Callback到底是什麼東西呢?如下:

然後我們似乎看出了一些端倪,難道這個介面和點選事件的傳遞有關?嗯,你猜對了。在藝術探索這本書中,並沒有描述事件是如何傳遞給Activity的,但是這裡我們可以猜測,如果外界想要傳遞點選事件給Activity,那麼它就必須持有Activity的引用,這沒錯,在Activity的attach方法中,有如下一段:

顯然,mWindow持有了Activity的引用,它通過setCallback方法來持有Activity,因此,事件是從Window傳遞給了Activity。也許你會說:“我不信,這理由不充分!”,沒關係,我們繼續分析。

我們知道,Activity啟動以後,在它的onResume以後,DecorView才開始attach給WindowManager從而顯示出來。(什麼?你不知道?回去看藝術探索第8章),請看Activity的makeVisible方法,程式碼如下:

接著,系統就會完成新增Window的過程,看WindowManagerGlobal的addView方法,如下:

可以看到,ViewRootImpl建立了,在ViewRootImpl的setView方法(此方法執行在UI執行緒)中,會通過跨程式的方式向WMS(WindowManagerService)發起一個呼叫,從而將DecorView最終新增到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此向關聯,同時會建立InputChannel、InputQueue和WindowInputEventReceiver來接受點選事件的訊息。

好了,言歸正傳,下面來說,點選事件到底怎麼傳遞給Activity的。首先要明白,點選事件是由使用者的觸控行為所產生的,因此它必須要通過硬體來捕獲,然後點選事件會交給WMS來處理。

在ViewRootImpl中,有一個方法,叫做dispatchInputEvent,如下:

那麼什麼是InputEvent呢?InputEvent有2個子類:KeyEvent和MotionEvent,其中KeyEvent表示鍵盤事件,而MotionEvent表示點選事件。在上面的程式碼中,mHandler是一個在UI執行緒建立的Handder,所以它會把執行邏輯切換到UI執行緒中。

這個訊息的處理如下:

除此之外,WindowInputEventReceiver也可以來接收點選事件的訊息,同樣它也有一個dispatchInputEvent方法,注意,WindowInputEventReceiver中的Looper為UI執行緒的Looper。

可以發現,不管是ViewRootImpl的dispatchInputEvent方法,還是WindowInputEventReceiver的dispatchInputEvent方法,它們本質上都是呼叫deliverInputEvent方法來處理點選事件的訊息,如下:

在ViewRootImpl中,有一系列類似於InputStage(輸入事件舞臺)的概念,每種InputStage可以處理一定的事件型別,比如AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。當一個InputEvent到來時,ViewRootImpl會尋找合適它的InputStage來處理。對於點選事件來說,ViewPostImeInputStage可以處理它,ViewPostImeInputStage中,有一個processPointerEvent方法,如下,它會呼叫mView的dispatchPointerEvent方法,注意,這裡的mView其實就是DecorView。

在View的實現中,dispatchPointerEvent的邏輯如下,這樣一來,點選事件就傳遞給了DecorView的dispatchTouchEvent方法。

DecorView的dispatchTouchEvent的實現如下,需要強調的是,DecorView是PhoneWindow的內部類,還記得前面提到的Window.Callback嗎?沒錯,在下面的程式碼中,這個cb物件其實就是Activity,就這樣點選事件就傳遞給了Activity了。

例子

寫一個簡單的例子,驗證下。選擇一個View,重寫其onTouchEvent方法,然後通過dumpStack方法來列印出當前執行緒的呼叫棧資訊。

選擇Google nexus 6執行一下,log如下所示:

通過上述log,大家不難看出MotionEvent的來源以及傳遞順序,本文止。

另外,就在今天,我的微信公眾號 “Android開發藝術探索” 竟然有打賞功能了,很新鮮,我打算體驗一把,自己給自己打賞一下,大家就不要給我打賞了。

相關文章