1. 程式人生 > >原始碼分析Handler機制

原始碼分析Handler機制

看下面例子:
image.png
在子執行緒傳送一個訊息,然後在主執行緒街道這個訊息處理,這個訊息是如何從子執行緒切換到主執行緒的呢?首先跟蹤一下handler.sendMessage(new Message())如下:
image.png
image.png
從上面兩圖看輾轉來到sendMessageAtTime方法,看到A處,此處mQueue是何方神聖呢???看名字只是知道個大概是個訊息佇列, 而B處看方法名都知道是將msg入隊列了:
mQueue在原始碼中兩處被賦值,都是在構造方法裡:
image.png
image.png
本文這個例子建立Handler物件的時候呼叫的是其無參的構造方法,如下:
image.png
正是上上圖C處所在的建構函式,從C、D處可以看到Handler物件的MessageQueue是looper物件的成員mQueue賦予的,而looper物件又是如何建立的呢?
image.png


嗯,這裡我簡單的解釋一下ThreadLocall,表示每一個執行緒都有自己的一份備份,相互獨立。而主執行緒是什麼時候設定這個ThreadLocal的呢??其實在我之前的文章Activity生命週期回撥是如何被回撥的?中原始碼分析的時候已經有涉及到這一點:

image.png

如上圖E1處:
image.png
F處又跳轉呼叫了prepare方法:
image.png
這裡注意一下咯:Looper建構函式是私有的,在類外部是沒辦法直接new設定給一個執行緒的,不過也不用擔心,呼叫他的prepare這個靜態方法就可以了,所以這裡記 住哦,Looper的prepare方法很重要,可以給一個執行緒“掛載”一個Looper物件,好啦回到F處,接下來看下標記G處唄:
image.png

接下來看到上圖中的E2處呼叫了Looper.loop方法:
image.png
image.png

從上面兩張圖可以看到,Looper.loop方法就是進入一個死迴圈,在迴圈體內不斷地從佇列取出訊息,然後進行分發處理見標記E3處,嗯,上圖你可以看到有一個E4,這裡計算了訊息分發耗時,其實這裡有一個應用,可以利用這裡點計算UI執行緒是否卡頓,像這個問題的文章已經是爛大街了,隨便百度一下都有,嗯,有時間我可能也會寫一篇總結一下,回到上面提到的標記E3處:
msg.target.dispatchMessage(msg);我們知道這個msg是Message物件,而這個target呢??它其實是一個Handler物件,問題是這個target又是什麼時候被賦值的??好吧,你暫且先記下這個疑問待會再說咯:
image.png

好的,到目前為止你已經知道訊息是如何派發哪裡被處理的了,繼續往回翻,回到標記C處:

image.png
所以C處只是取出當前執行緒“掛載”的Looper物件而已,接下去看D,“mLooper.mQueue”這分明是說mQueue是mLooper的成員呀,進去Looper內部研究研究成員結構:
image.png
所以到這裡你已經瞭解以下這些事實:

  • Looper物件可以通過靜態方法prepare“掛載”一個執行緒上
  • Handler物件內部用到的Looper、MessageQueue都是來自當前執行緒“掛載”的Looper(順便提醒一下,這個事實就說明了一個執行緒要使用Handler之前要先讓這個執行緒“掛載”上一個Looper物件,哈哈,那又怎麼讓執行緒“掛載”上一個Looper呢?上文不是說了嗎——呼叫prepare靜態函式)
  • Looper的loop方法會啟動一個死迴圈不斷的從佇列中讀取訊息,然後將訊息送到Handler物件的handleMessage方法中處理

嗯,現在可以回到文章開頭的標記B處,這裡可以看到當呼叫Handler物件來發送訊息時,會呼叫enqueueMessage方法,將訊息入佇列:
image.png
見上圖,標記 I 處是真正的將訊息入佇列,不過在入佇列之前多做了一項操作見標記H處——將handler物件設定到msg訊息物件的target成員中,到此上文那個叫你暫時存檔的疑問也幫你解決了。

按國際慣例來個總結咯:

  • 一個執行緒在使用Handler之前要先線上程上掛載一個Looper,android主執行緒在ActivityThread的main方法已經做了這個掛載步驟,所以平時我們在主執行緒之間建立Handler物件式直接可以使用的不會出錯,但是在子執行緒建立Handler物件就必須先幫執行緒掛在一個Looper物件。

  • Looper的loop方法會開啟一個死迴圈不斷讀取訊息佇列的訊息,然後傳給handler物件的handleMessage方法處理

  • handler物件在傳送訊息的時候,會將訊息入訊息佇列,壓入佇列之前會將自身引用設定在Message訊息物件的target成員方便loop死迴圈讀取訊息之後分發訊息。