關於Handler你所需要知道的一切
前言
Handler是個老生常談的問題,我相信幾乎所有的Android開發者都會使用Handler,那關於Handler還有什麼好講的嗎?Handler如果僅僅是使用的話,確實沒什麼好講的,但是Handler卻是一個幾乎所有面試官都會問的問題,不同的要求問的深度也不一樣,今天我就帶大家學習一下關於Handler你所必須要掌握的知識。
Handler訊息機制
首先有四個物件我們必須要了解一下 Handler 、 Looper 、 ThreadLocal 還有 MessageQueue 。
Handler
首先我們要明白Handler訊息機制是用來幹嘛的?Handler是把其他執行緒切換到 Handler所在的執行緒 ,注意,這裡不是UI執行緒。雖然我們的大多數使用Handler的場景,都是我們在子執行緒做了一下耗時的操作(IO或者資料庫),當子執行緒執行完以後我們可能需要更新UI,這個時候我們用Handler來處理(sendMessage()或者post())就把子執行緒切換到了UI執行緒了。假如說,我們現在有這麼一個需求,執行緒A發個資訊給執行緒B(執行緒A、執行緒B都不是主執行緒),這個時候我們用Handler依然可以做,只需要線上程B中 建立 好Handler就可以了(關於如何在子執行緒建立Handler我會在下面詳細說明)。所以,Handler並不是把其他執行緒切換到主執行緒(UI執行緒),而是切換到它所在的執行緒,這一點一定要弄清楚。
Looper
Handler訊息機制裡面最核心的類,訊息輪詢。Handler要想切換執行緒(或者說傳送訊息),必須要先獲得當前執行緒的Looper,然後呼叫Looper.loop()方法把MessageQueue裡面的message輪詢出來"交"給Handler來處理,至於如何“交”給Handler的,下面我會通過原始碼帶大家分析。
ThreadLocal
ThreadLocal是Looper內部的一個,它雖然前面有個“Thread”,但其實它並不是執行緒,它相當於一個執行緒內部的儲存類。剛才在講Looper的時候我們說到,“Handler要想切換執行緒(或者說傳送訊息),必須要先獲得當前執行緒的Looper”,那如何獲得當前執行緒的Looper呢?正是通過ThreadLocal,它可以在不同執行緒之間互不干擾地儲存資料。ThreadLocal裡面有一個內部靜態類物件ThreadLocalMap,通過其set()和get()方法對資料物件進行儲存和讀取。
MessageQueue
MessageQueue——訊息佇列,雖然它叫訊息佇列,但實際上的結構並不是一個佇列,而是一種單鏈表的資料結構,只是使用列隊的形式對資料進場做新增或者移除。MessageQueue是在Looper被建立的時候由Looper生成的。 同一執行緒下 ,Handler傳送的所有message都會被加入到MessageQueue裡面,當Message被loop()以後,就會從訊息佇列中移除。Message我沒有單獨拿出來,因為確實比較簡單,就是訊息本身,它可以攜帶兩個int型引數,如果要傳比較複雜的引數就用obj屬性,what屬性用來區分是哪個Handler傳送的資訊。
小結一下
Handler是把其他執行緒切換到它所在的執行緒,使用Handler發訊息之前要先建立Looper物件,建立和讀取Looper都需要使用ThreadLocal,它可以在不同執行緒之間互不干擾地儲存資料。在建立Looper物件的同時也把MessageQueue建立好了,Handler所發的message會被新增到MessageQueue物件裡面,Looper.loop()方法以後會把MessageQueue上的Message輪詢出來。連線這幾個物件的還有一條很重要的線,那就是執行緒,記住,以上的過程都要保證Handler、Looper、MessageQueue在 同一執行緒 ,這樣,才能保證Handler的訊息機制被正確使用。
子執行緒建立Handler
注意:我們這裡是為了讓大家更好地學習Handler,所以要在子執行緒建立Handler,現實使用場景,很少會在子執行緒建立Handler
在主執行緒(UI執行緒)建立Handler,我相信所有人都會
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mainHandler = new Handler(); mainHandler.post(new Runnable() { @Override public void run() { Log.e("qige_test", "thread_name=" + Thread.currentThread().getName()); } }); }
我們先按照主執行緒的方式在子執行緒建立一個Handler試一下,看看會有什麼樣的結果
new Thread(){ @Override public void run() { childHandler=new Handler(); childHandler.post(new Runnable() { @Override public void run() { Log.e("qige_test","thread_name="+Thread.currentThread().getName()); } }); } }.start();

沒錯,如圖所示還沒有建立Looper,那麼如何建立Looper呢?圖中有句話已經給出了答案——Looper.prepare(),同時為了讓我們傳送的訊息被輪詢到,還必須要呼叫Looper.loop(); 所以在完整的在子執行緒建立Handler的程式碼如下:
new Thread(){ @Override public void run() { //建立Looper,同時建立MessageQueue Looper.prepare(); childHandler=new Handler(); childHandler.post(new Runnable() { @Override public void run() { Log.e("qige_test","thread_name="+Thread.currentThread().getName()); } }); //輪詢 Looper.loop(); } }.start();
這樣就可以在子執行緒裡面建立Handler了,看到這裡,你可能會問,為什麼我在主執行緒建立Handler的時候,沒有呼叫Looper.prepare()和Looper.loop()?這個問題我們先放一下,我們先從原始碼角度把Handler訊息機制分析一下。
從原始碼角度分析
先看一下建立Looper和MessageQueue的方法 Looper.prepare()
public static void prepare() { prepare(true); } private static void prepare(boolean quitAllowed) { //ThreadLocal來了 if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); }
這裡就出現了我剛才說的ThreadLocal物件。Android規定每個執行緒有且僅有一個Looper,所以為了防止不同的執行緒之間的Looper相互影響,使用ThreadLocal物件來儲存Looper。
再看一眼new Looper()的原始碼
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
MessageQueue也出來了,建立Looper的同時建立MessageQueue。
下面我們看 Handler.post() 或者是 Handler.sendMessage() 他們本質是一樣的,把訊息加入到訊息隊列當中
public final boolean post(Runnable r) { returnsendMessageDelayed(getPostMessage(r), 0); }
post()或者sendMessagexxx()最終都會呼叫 sendMessageAtTime(Message msg, long uptimeMillis) 方法,我們直接看這個方法的原始碼
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } //關鍵程式碼部分 return enqueueMessage(queue, msg, uptimeMillis); }
都很簡單,最後一步enqueueMessage(queue, msg, uptimeMillis);是把訊息加入佇列,咱們再點開看一下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { //把當前的Handler賦值給msg.target msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
關鍵要看的地方是 msg.target = this 剛才講Message的時候沒有提,Message的target屬性就是一個Handler物件,這裡直接把當前的Handler物件賦值過去了。最後一行: queue.enqueueMessage(msg, uptimeMillis) 是把message加入到訊息佇列裡面,具體的實現我抓不到了,知道這句話是把message加入到MessageQueue裡面就行。
下面分析* Looper.loop() 方法的 關鍵部分原始碼
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } **標註1** final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. ........................ ...................... **標註2** for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } .................................... ................................... try { **標註3** msg.target.dispatchMessage(msg); dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0; } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } ........................................ ........................................ **標註4** msg.recycleUnchecked(); } }
看原始碼的時候注意看一下標註,一個個地來講;
- 標註1:保證了Handler、Looper和MessageQueue在同一執行緒
- 標註2:沒有看錯,就是一個無線死迴圈,Looper會不停地對MessageQueue進行輪詢。這時候,你可能會問,這種 無限死迴圈 會不會很浪費資源?其實並不會,因為當沒有message被新增到佇列的時候,程式會進入阻塞狀態,對資源的消耗是很小的。
- 標註3:還記得剛才講地msg.target吧,這裡 msg.target.dispatchMessage(msg);就是handler.dispatchMessage(msg),直接去看Handler的dispatchMessage(msg)方法
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
這裡面msg.callback是個Runnable物件,也就是當我們使用handler.post(Runnable r)的方法的話,這時候就直接去呼叫Runnable物件裡面的東西了,如果使用的是handler.sendMessagexxx(),就是去呼叫我們重寫的handleMessage(msg)方法了。
如果呼叫post()方法傳送訊息
mainHandler.post(new Runnable() { @Override public void run() { //這裡會被呼叫 Log.e("qige_test","thread_name="+Thread.currentThread().getName()); } });
如果呼叫sendMessagexxx()
mainHandler.sendEmptyMessage(0); mainHandler=new Handler(){ @Override public void handleMessage(Message msg) { //這裡會被呼叫 Log.e("qige_test","what="+msg.what); } };
我們再回到上一級原始碼
- 標註4, msg.recycleUnchecked();其實這裡就是msg完成了它的使命,被釋放了以後又放回快取池裡面去了。
以上基本就是一個完整的Handler訊息機制的過程,我再帶大家好好回顧一下:
1.Looper.prepare();//這一步建立了Looper和MessageQueue
2.handler.sendMessagexxxx(); // 把message新增到MessageQueue上
3.Looper.loop();//輪詢訊息
4.handler.dispatchMessage(msg);//處理訊息
關於在主執行緒建立Handler的疑問
好了,到了該解決歷史遺留問題的時候了,為什麼我們在主執行緒建立handler不需要呼叫 Looper.prepare() 和 Looper.loop() 呢?
這是因為,主執行緒已經給我們建立好了,在哪裡建立好的呢?
在Java裡面,有一個程式的主入口,就是靜態main方法
public class Test { //程式入口 public static void main(String... args){ } }
在Android裡面呢,同樣有這麼一個main方法入口,它在ActivityThread類中。在我們App啟動過程中,會呼叫到ActivityTread的main()方法(由於篇幅問題,該過程沒有辦法詳細講,後期會推文章單獨說一下),下面我們直接看ActivityTread的main()方法的原始碼
為了方便理解,還是隻看關鍵部分程式碼
public static void main(String[] args) { .... //建立Looper和MessageQueue物件,用於處理主執行緒的訊息 Looper.prepareMainLooper(); //建立ActivityThread物件 ActivityThread thread = new ActivityThread(); //建立Binder通道 (建立新執行緒) thread.attach(false); //訊息輪詢 Looper.loop(); }
看到這裡,我想你應該明白了為啥在主執行緒裡建立Handler不需要呼叫Looper.prepare()和Looper.loop()方法了吧,因為在App啟動的時候,ActivityThread已經給我們建立好了。不過,需要注意的是,我剛才在講Looper.loop()原始碼的時候說過這裡面會有一個 無限死迴圈 ,那麼程式執行到這裡豈不是要永遠卡在這了呢,那我們的Activity、Service都是咋執行的呢? 看關鍵的原始碼這一句 thread.attach(false) ,註釋上其實解釋的很清楚,通過Binder建立了新的執行緒,在這個新的執行緒裡面運行了Activity、Service等元件,而之所以Android為什麼採用這種方式,讓我們留在以後再說,你只需要知道,Looper.loop()不會造成卡頓,主執行緒已經給我們建立好了Looper和MessageQueue物件就可以了。
為什麼寫這篇文章
開篇我就說過,Handler幾乎所有人都會用,但僅僅會用是不夠,要知其然更知其所以然。很多面試官願意問Handler相關的問題,好好閱讀這篇文章,它會讓你在面試的時候事半功倍。