Handler原始碼解讀

你的使用者名稱發表於2019-02-26


大家面試的時候是否經常被問到有沒有看過handler原始碼,如果回答看過了接下來就各種深入提問,直到問得你雲裡霧裡面試官才開心。今天作者帶領大家一起深入地過一遍handler原始碼,將面試常用問題各個擊破,從此手捧offer,和麵試失敗say no!


看原始碼首先當然是從使用處看起,我們首先來看一下平時專案中handler的使用,我們首先定義了一個handler物件,並且寫好了接收到訊息以後需要執行的操作,然後在子執行緒中傳送一個訊息。


問題一  handler傳送的訊息是怎麼到達handlerMessage回撥的呢?

Handler原始碼解讀

Handler原始碼解讀

首先我們就從源頭開始看起,handler.senMessage()方法一路點下去,發現最終呼叫的是MessageQueue類的enqueueMessage方法,也就是說最終是把message新增到了訊息佇列MessageQueue中,如下圖所示

Handler原始碼解讀

OK,訊息放進去了,但是我們發現這裡面並沒有呼叫handler的handleMessage方法,所以我們換個角度想,放進去了總要取出來吧,不然放進去就沒有意義了。於是我們就在MessageQueue中尋找返回值為Message的方法,果不其然找到了一個next方法,在該方法中返回了剛剛儲存好的message,如下圖

Handler原始碼解讀

那麼問題又來了,究竟是誰呼叫了這個next方法把訊息取出來了呢?解決這個問題就要反過來考慮,需要呼叫next方法就需要例項化MessageQueue這個類,不然沒法呼叫,MessageQueue是在Looper中初始化的,所以接下來我們來看看Looper類的原始碼。在Looper原始碼中是在建構函式中初始化MessageQueue的

Handler原始碼解讀

接下來我們再搜一下呼叫next方法的地方

Handler原始碼解讀


我們發現在Looper的loop方法中有一個無限迴圈,其中每次迴圈都呼叫MessageQueue的next方法,如果找到了就會執行msg.target.dispatchMessage(msg),那麼msg.target是什麼東西呢?我們接下來就點開Message類,檢視一下target屬性,如下

Handler原始碼解讀

哇塞,原來是Handler物件,這裡其實就是呼叫的handler的handlerMessage方法,現在邏輯就清晰了。Handler首先將我們傳送的message儲存到MessageQueue,然後Looper獲取到message以後呼叫Handler的handlerMessage方法將message返回。


問題二 Handler是如何實現跨執行緒通訊的?

縱覽全域性,我們發現,其實message相當於一個物品,MessageQueue相當於一個倉庫,被handler放到MesageQueue,然後被Looper取出來還給handler,對於物品來說是不會挑選主人的,類的屬性也一樣,無論哪個執行緒都可以操作修改。所以message在子執行緒被放進去以後在主執行緒被取出來就很正常了。

Handler原始碼解讀


問題三 Looper是在哪裡被呼叫的?

我們平時使用Handler一般是子執行緒和主執行緒通訊,而主執行緒的Looper其實在app初始化的時候就已經初始化並且開始Loop無限迴圈獲取Message了,具體程式碼在ActivityThread的main方法中,其中prepareMainLooper就是初始化Looper,然後還呼叫了loop方法。如下圖

Handler原始碼解讀


問題四 如何保證Looper唯一性,一個執行緒不會出現多個Looper?

我們都知道,如果同一執行緒每次都new一個Looper的話就沒辦法保持唯一性。想要知道這個知識點,必須看一下Looper是怎麼設定和獲取的,我們點進去初始化的原始碼,如下圖

Handler原始碼解讀

Handler原始碼解讀

我們發現,Looper的設定和獲取並不是普通的屬性get和set,而是通過ThreadLocal類來操作,所以我們點進去ThreadLocal看一下,發現裡面Looper的設定和獲取操作都需要傳入執行緒引數,也就是說以執行緒為Key,對應了唯一的Looper,其實Looper唯一也就意味著MessageQueue也是唯一的,因為MessageQueue是在Looper構造方法裡初始化一次的,但是Handler可以有多個,因為我們每初始化一個Handler物件,都會被Message記錄,最後呼叫相應Handler的handleMessage方法,並不會有衝突。其中ThreadLocal程式碼如下

Handler原始碼解讀


問題五 MessageQueue是一個什麼樣的資料結構?

分析這個問題,我們首先要找到其設定資料的地方,如下圖所示,MessageQueue儲存了每條message的值和next指標,也就是說想獲取到下一條訊息必須要看上一條訊息的next指向,這樣就導致了資料的先進先出,和LinkedList的實現類似,顯然是一個單向連結串列的實現。

Handler原始碼解讀


問題六 訊息是怎麼實現延時的?

1.既然訊息是儲存在佇列中,那麼剛儲存的時候就應該儲存好該訊息所在的位置才對,不然在獲取的時候就沒辦法保證延時短的訊息先傳送了,如問題五中的圖,這個的實現其實就是死迴圈對每個message的延時時間進行對比,如果當前訊息的延時短則更改指標指向,將當前訊息插入到佇列中。

2.如果傳送訊息的時間沒到,則不交給Looper。如下圖,在Looper獲取訊息時會對時間進行判斷,如果時間還沒到則不返回message,即相當於本次迴圈並沒有到達時間需要傳送的訊息

Handler原始碼解讀


問題七 Handler機制中生產者和消費者模式體現在哪裡?

前面說到handler中把訊息放到了messageQueue,然後Looper從messageQueue中獲取訊息並使用,這個相當於就是一個生產者和消費者模式,一個負責生產一個負責消費。生產消費者模式中的阻塞在handler中也是體現得淋漓盡致,在獲取訊息時,如果訊息池中訊息數量為空,那麼此時進入阻塞狀態,如下圖

Handler原始碼解讀

問題八 為何Looper中死迴圈不會阻塞UI

首先我們必須明白的是app本身就是一個死迴圈,不然豈不是一開啟就結束了?其次Looper和更新UI是共存的關係,這裡會有一個底層的睡眠機制,在需要的時候再喚起。

最後:本人小萌新,之前都是看得多寫得少,現在也想把自己的所見所得記錄下來給大家分享分享,若有寫錯或者沒有補充完整的地方歡迎各位大神指教,以後有新的心得體會會及時更新本文,謝謝大家閱讀!


相關文章