從源碼角度分析Handler
最近在找工作,面試官經常會問你懂handler機制嗎?
老掉牙的問題了,這誰還不會啊:
Handler包含三部分,分別是Handler、Looper、MessageQueue。其中MessageQueue是一個消息隊列,內部維護了一個鏈表,保存消息。Looper是無限循環獲取消息的,當MessageQueue中為空時,Looper被阻塞,當MessageQueue不為空時,Looper調用next()方法獲取消息,並把消息從隊列中移除,然後回調Handler的處理消息的方法。而Handler中發送消息的方法sendMessage之類的方法,最終是吧消息加入到隊列中去,如此形成一個閉環,如下圖(圖片來源於網路)
其實基本的面試,講完handler的消息機製為基本流程基本就OK了,但是有一天面試官問我,你知道它是怎麼實現的嗎?
那handler消息機制還能怎麼說呢?後來想了想,面試官應該是想考我究竟有沒有看過源碼。那我們就來看看源碼,handler消息機制到底如何實現的呢?我依舊不打算詳細看源碼,看看主要的流程怎麼實現足以。
首先我們看看Message這個類,這個類也沒啥,就是對消息體的一個封裝,但是這裡面有個Handler 對象target,我們先記住它。
然後再看看類Looper,這個類首先要看的方法是prepare(),這個方法裡面sThreadLocal,所以我們知道Looper是通過ThreadLocal來存儲線程之間的數據。ThreadLocal會為每個線程的數據保存一個副本,當一個線程修改數據的時候不會影響到其他線程的數據副本。這裡的sThreadLocal保存了一個Looper構造函數實例化的Looper對象,我們看看Looper的構造函數裡面幹了什麼?
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
它實例化了一個消息隊列mQueue 。prepare()就這麼多內容,然後我們看loop()方法:
public static void loop() {
final Looper me = myLooper();
if(me == null) {
throw newRuntimeException("No Looper; Looper.prepare() wasnt called on this thread.");
}
final MessageQueue queue = me.mQueue;
for(;;) {
Message msg = queue.next();// might block
if(msg ==null) {
return;
}
try{
msg.target.dispatchMessage(msg);
}finally{
if(traceTag !=0) {
Trace.traceEnd(traceTag);
}
}
}
}
loop方法中首先調用myLooper()方法獲得了Looper對象
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
從這個方法中可以看出這個Looer就是在prepare()方法中存在ThreadLocal中的。
然後又從這個Looper對象中獲取剛才實例化的MessageQueue對象。
再然後我們看到下面從這個MessageQueue對象中無限循環的取消息,當沒有消息的時候就返回阻塞,而當有消息時調用了msg.target.dispatchMessage(msg)這個方法,疑問是這個target是什麼?從下面的Handler類中可以找到
然後我們看看Handler類,這個類裡面有個handlerMessage,注釋上說這個方法必須要重寫。
然後在看看發送消息的方法:sendMessage(Message msg) -----> sendMessageDelayed(Message msg, long delayMillis) -----> sendMessageAtTime(Message msg, long uptimeMillis) -----> enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) -----> queue.enqueueMessage(msg, uptimeMillis)
所以說Handler的發送消息的方法最終是給隊列裡面加入消息,那麼這個queue是從哪來的呢?在Handler的構造方法中看的很清楚,是從Looper里取到的,而這個looper就是剛才保存在sThreadLocal中的looper。在Handler的enqueueMessage方法中調用MessageQueue的enqueueMessage方法之前,給Message中的target賦了值,也就是把當前的Handler賦值給了target,所以looper在取到消息之後其實是回調了Handler自己的處理消息的方法dispatchMessage,而這個方法里一般情況下,調用了我們重寫的handleMessage方法。
Handler消息機制的流程從源碼角度分析完啦。簡單點講就是Message類中有個成員變數target會在Handler發送消息時會把當前Handler賦值給它。而Handler發送消息的本質是給mQueue中添加消息,mQueue是從Looper中獲取到的。我們可以通過Looper的靜態方法myLooper獲取當前線程中的Looper,這個Looper被保存在ThreadLocal中,ThreadLocal是可以根據線程保存數據的,並且相互之間不受影響。Looper的prepare()方法向ThreadLocal中保存了當前線程的Looper實例化對象,在Looper的構造函數中初始化了mQueue,loop方法又獲得這個queue,然後不斷循環的取消息,取到消息調用msg的target即handler中的處理消息的方法dispatchMessage,而這個方法最終調用我們重寫的handleMessage方法,按照我們的意願處理消息。
講完了Handle原理,面試官會問你那你知道HandlerThread?
HandlerThread是一個繼承了Thread的類,在run方法中初始化了一個Looper,這樣就可以使用handler對這個線程發消息啦。
面試官:你知道IntentService嗎?講一下原理。
IntentService是繼承了Service的服務,一般用於在後台做耗時操作,並且耗時任務完成之後會自動關閉服務。
IntentService中在Oncreate方法中創建了一個HandlerThread,並把這個Thread中的looper構建了一個Handler對象,於是這個Handler就跟HandlerThread中的Looper產生了關聯。在start方法中,使用這個handler把攜帶任務的Intent當做消息發送出去,於是HandlerThread中的Looper取到消息之後便會回調Handler處理消息的方法。在ServiceHandler內部類的handleMessage方法中可以到會先調用onHandleIntent方法處理消息,然後調用stopSelf結束自己。而onHandleIntent這個方法就是需要我們自己重寫的方法,在這裡面進行耗時操作。
當有多個任務的時候,也就是多次啟動service,此時只會執行一次oncreate方法,但是會執行多次start方法,於是消息隊列中會有多少個消息,所有的消息會按照順序處理,結束之後才會調用stopSelf方法結束自己。
並且IntentService不推薦使用bind的方式啟動,這樣會不執行onStart方法,導致沒有把任務發送給消息隊列,便不會回調任何處理消息的函數,成了一個普通的service。
推薦閱讀: