15分鐘徹底掌握Handler

15分鐘徹底掌握Handler

目錄

15分鐘徹底掌握Handler

從 new Handler() 開始

Looper 介紹

 Looper初始化

Looper 負責做什麼事情

Handler 的 sendMessage 方法

 Handler 的 enqueueMessage 方法

Handler 的 post(Runnable) 與 sendMessage 有什麼區別

Looper.loop() 爲什麼不會阻塞主線程

Handler 的 sendMessageDelayed 或者 postDelayed 是如何實現的

總結


我們一起分析 Android Handler 的源碼。

Handler 現在幾乎是 Android 面試的必問知識點了,大多數 Android 工程師都在項目中使用過 Handler。主要場景是子線程完成耗時操作的過程中,通過 Handler 向主線程發送消息 Message,用來刷新 UI 界面。這節課我們來了解 Handler 的發送消息和處理消息的源碼實現。

分析源碼的時候最好是找到一個合適的切入點,Handler 源碼的一個切入點就是它的默認構造器。

從 new Handler() 開始

在無參構造器裏調用了重載的構造方法並分別傳入 null 和 false。並且在構造方法中給兩個全局變量賦值:mLooper 和 mQueue。

這兩者都是通過 Looper 來獲取,具體代碼如下:


 

可以看出,myLooper 通過一個線程本地變量中的存根,然後 mQueue 是 Looper 中的一個全局變量,類型是 MessageQueue 類型。

接下來的分析重點就是這個 Looper 是什麼?以及何時被初始化?

Looper 介紹

不知你有沒有思考過一個問題,啓動一個 Java 程序的入口函數是 main 方法,但是當 main 函數執行完畢之後此程序停止運行,也就是進程會自動終止。但是當我們打開一個 Activity 之後,只要我們不按下返回鍵 Activity 會一直顯示在屏幕上,也就是 Activity 所在進程會一直處於運行狀態。實際上 Looper 內部維護一個無限循環,保證 App 進程持續進行。

 Looper初始化

Activity 啓動過程時,ActivityThread 的 main 方法是一個新的 App 進程的入口,其具體實現如下:


解釋說明:

圖中 1 處就是初始化當前進程的 Looper 對象;
圖中 2 處調用 Looper 的 loop 方法開啓無限循環。
prepareMainLooper 方法如下

 

這裏我沒有省略任何一行代碼,因爲此處的代碼很精簡但是每一行又都有意義。

圖中 1 處在 prepareMainLooper 中調用 prepare 方法創建 Looper 對象,仔細查看發現其實就是 new 出一個 Looper。核心之處在於將 new 出的 Looper 設置到了線程本地變量 sThreadLocal 中。也就是說創建的 Looper 與當前線程發生了綁定。

Looper 的構造方法如下:

 

可以看出,在構造方法中初始化了消息隊列 MessageQueue 對象。

prepare 方法執行完之後,會在圖中 3 處調用 myLooper() 方法,從 sThreadLocal 中取出 Looper 對象並賦值給 sMainLooper 變量。

 

注意:

圖中 2 處在創建 Looper 對象之前,會判斷 sThreaLocal 中是否已經綁定過 Looper 對象,如果是則拋出異常。這行代碼的目的是確保在一個線程中 Looper.prepare() 方法只能被調用 1 次。比如以下代碼:

 

執行上述代碼程序會秒崩,打印日誌如下:

 

注意:

不是說調用 2 次 prepare 纔會拋異常嗎?爲什麼 MainActivity 中只調用了 1 遍就導致程序崩潰? 這是因爲在 MainActivity 所在進程被創建時,Looper 的 prepare 方法已經在 main 方法中調用了 1 遍。這會直接導致一個非常重要的結果:

prepare 方法在一個線程中只能被調用 1 次;
Looper 的構造方法在一個線程中只能被調用 1 次;
最終導致 MessageQueue 在一個線程中只會被初始化 1 次。
也就是說 UI 線程中只會存在 1 個 MessageQueue 對象,後續我們通過 Handler 發送的消息都會被髮送到這個 MessageQueue 中。

Looper 負責做什麼事情

用一句話總結 Looper 做的事情就是:不斷從 MessageQueue 中取出 Message,然後處理 Message 中指定的任務。

在 ActivityThread 的 main 方法中,除了調用 Looper.prepareMainLooper 初始化 Looper 對象之外,還調用了 Looper.loop 方法開啓無限循環,Looper 的主要功能就是在這個循環中完成的。

 

很顯然,loop 方法中執行了一個死循環,這也是一個 Android App 進程能夠持續運行的原因。

圖中 1 處不斷地調用 MessageQueue 的 next 方法取出 Message。如果 message 不爲 null 則調用圖中 2 處進行後續處理。具體就是從 Message 中取出 target 對象,然後調用其 dispatchMessage 方法處理 Message 自身。那這個 target 是誰呢?查看 Message.java 源碼可以看出 target 就是 Handler 對象,如下所示:

 

Handler 的 dispatchMessage 方法如下:

 

可以看出,在 dispatchMessage 方法中會調用一個空方法 handleMessage,而這個方法也正是我們創建 Handler 時需要覆蓋的方法。那麼 Handler 是何時將其設置爲一個 Message 的 target 的呢?

Handler 的 sendMessage 方法

Handler 有幾個重載的 sendMessage 方法,但是基本都大同小異。我用最普通的 sendMessage 方法來分析,代碼具體如下:


可以看出,經過幾層調用之後,sendMessage 最終會調用 enqueueMessage 方法將 Message 插入到消息隊列 MessageQueue 中。而這個消息隊列就是我們剛纔分析的在 ActivityThread 的 main 方法中通過 Looper 創建的 MessageQueue。

 Handler 的 enqueueMessage 方法


可以看出:

在圖中 1 處 enqueueMessage 方法中,將 Handler 自身設置爲 Message的target 對象。因此後續 Message 會調用此 Handler 的 dispatchMessage 來處理;
圖中 2 處會判斷如果 Message 中的 target 沒有被設置,則直接拋出異常;
圖中 3 處會按照 Message 的時間 when 來有序得插入 MessageQueue 中,可以看出 MessageQueue 實際上是一個有序隊列,只不過是按照 Message 的執行時間來排序。
至此 Handler 的發送消息和消息處理流程已經介紹完畢,接下來看幾個面試中經常被問到的與 Handler 相關的題目。

Handler 的 post(Runnable) 與 sendMessage 有什麼區別

看一下 post(Runnable) 的源碼實現如下:

 

實際上 post(Runnable) 會將 Runnable 賦值到 Message 的 callback 變量中,那麼這個 Runnable 是在什麼地方被執行的呢?Looper 從 MessageQueue 中取出 Message 之後,會調用 dispatchMessage 方法進行處理,再看下其實現:

 

可以看出,dispatchMessage 分兩種情況:

如果 Message 的 Callback 不爲 null,一般爲通過 post(Runnabl) 方式,會直接執行 Runnable 的 run 方法。因此這裏的 Runnable 實際上就是一個回調接口,跟線程 Thread 沒有任何關係。
如果 Message 的 Callback 爲 null,這種一般爲 sendMessage 的方式,則會調用 Handler 的 hanlerMessage 方法進行處理。


Looper.loop() 爲什麼不會阻塞主線程

剛纔我們瞭解了,Looper 中的 loop 方法實際上是一個死循環。但是我們的 UI 線程卻並沒有被阻塞,反而還能夠進行各種手勢操作,這是爲什麼呢?在 MessageQueue 的 next 方法中,有如下一段代碼:


nativePollOnce 方法是一個 native 方法,當調用此 native 方法時,主線程會釋放 CPU 資源進入休眠狀態,直到下條消息到達或者有事務發生,通過往 pipe 管道寫端寫入數據來喚醒主線程工作,這裏採用的 epoll 機制。關於 nativePollOnce 的詳細分析可以參考:nativePollOnce函數分析。

Handler 的 sendMessageDelayed 或者 postDelayed 是如何實現的

之前我已經介紹過,在向 MessageQueue 隊列中插入 Message 時,會根據 Message 的執行時間排序。而消息的延時處理的核心實現是在獲取 Message 的階段,接下來看下 MessageQueue 的 next 方法。

 

圖中藍框處表示從 MessageQueue 中取出一個 Message,但是當前的系統時間小於 Message.when,因此會計算一個 timeout,目的是實現在 timeout 時間段後再將 UI 線程喚醒,因此後續處理 Message 的代碼只會在 timeout 時間之後纔會被 CPU 執行。

注意:在上述代碼中也能看出,如果當前系統時間大於或等於 Message.when,那麼會返回 Message 給 Looper.loop()。但是這個邏輯只能保證在 when 之前消息不被處理,不能夠保證一定在 when 時被處理。

總結

應用啓動是從 ActivityThread 的 main 開始的,先是執行了 Looper.prepare(),該方法先是 new 了一個 Looper 對象,在私有的構造方法中又創建了 MessageQueue 作爲此 Looper 對象的成員變量,Looper 對象通過 ThreadLocal 綁定 MainThread 中;
當我們創建 Handler 子類對象時,在構造方法中通過 ThreadLocal 獲取綁定的 Looper 對象,並獲取此 Looper 對象的成員變量 MessageQueue 作爲該 Handler 對象的成員變量;
在子線程中調用上一步創建的 Handler 子類對象的 sendMesage(msg) 方法時,在該方法中將 msg 的 target 屬性設置爲自己本身,同時調用成員變量 MessageQueue 對象的 enqueueMessag() 方法將 msg 放入 MessageQueue 中;
主線程創建好之後,會執行 Looper.loop() 方法,該方法中獲取與線程綁定的 Looper 對象,繼而獲取該 Looper 對象的成員變量 MessageQueue 對象,並開啓一個會阻塞(不佔用資源)的死循環,只要 MessageQueue 中有 msg,就會獲取該 msg,並執行 msg.target.dispatchMessage(msg) 方法(msg.target 即上一步引用的 handler 對象),此方法中調用了我們第二步創建 handler 子類對象時覆寫的 handleMessage() 方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章