淺談Android中的Handler機制
Handler是Android中提供的一種非同步回撥機制,也可以理解為執行緒間的訊息機制。為了避免ANR,我們通常會把一些耗時操作(比如:網路請求、I/O操作、複雜計算等)放到子執行緒中去執行,而當子執行緒需要修改UI時則子執行緒需要通知主執行緒去完成修改UI的操作,則此時就需要我們使用Handler機制來完成子執行緒與主執行緒之間的通訊。
1. Handler的一般使用步驟
在明確了Android中只有主執行緒能修改UI介面、子執行緒執行耗時操作的前提後,下面一起來學習下Handler的使用步驟。
在主執行緒中建立Handler例項,並且重寫handlerMessage方法。
private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: //執行相關修改UI的操作 break; } } };
子執行緒中獲取Handler物件,在需要執行更新UI操作的地方使用handler傳送訊息
Message msg = Message.obtain(); msg.obj = "content"; msg.what = 1; //傳送訊息給Handler handler.sendMessage(msg);
在handlerMessage方法中的Switch裡面,根據case下不同的常量執行相關操作
以上只是Handler的一種使用方式,由於本文的重點是探究Handlerde原理,故其他使用方式這裡不重點介紹。
2. 重要類的職責
在深入瞭解Handler機制原理之前,我們應該明確在Handler機制中幾個重要類的職責。
- Handler:負責傳送處理訊息
- MessageQueue:訊息佇列,負責儲存訊息
- Message: 具體傳送的訊息
- Looper: 負責迴圈取出訊息給Handler處理
- ThreadLocal: 用於執行緒間的資料隔離,在每個執行緒中存放各自對應的Looper
3. Handler機制原理
每個Handler都會關聯一個訊息佇列,訊息佇列又是封裝在Looper物件中,而每個Looper又會關聯一個執行緒。這樣Handler、訊息佇列、執行緒三者就關聯上了。
Handler是一個訊息處理器,將訊息傳送給訊息佇列,然後再由對應的執行緒從訊息佇列中逐個取出,並執行。
預設情況下,訊息佇列只有一個,也就是主執行緒的訊息佇列,該訊息佇列通過Looper.prepareMainLooper()方法建立,最後執行Looper.loop()來迴圈啟動訊息。
以上敘述可能有點空洞,下面我們結合原始碼一起來理解:
3.1 Handler是如何關聯訊息佇列和執行緒的?
我們首先看Handler預設的建構函式:
public Handler(Callback callback, boolean async) {
//程式碼省略
mLooper = Looper.myLooper();//獲取Looper
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;//通過Looper物件獲取訊息佇列
mCallback = callback;
mAsynchronous = async;
}
//獲取Looper物件
public final Looper getLooper() {
return mLooper;
}
從Handler的建構函式中我們可以發現,Handler在初始化的同時會通過Looper.getLooper()獲取一個Looper物件,並與Looper進行關聯,然後通過Looper物件獲取訊息佇列。那我們繼續深入到Looper原始碼中去看Looper.getLooper()是如何實現的。
//初始化當前執行緒Looper
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
//為當前執行緒設定一個Looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
從上面的程式可以看出通過prepareMainLooper(),然後呼叫 prepare(boolean quitAllowed)方法建立了一個Looper物件,並通過sThreadLocal.set(new Looper(quitAllowed))方法將該物件設定給了sThreadLocal。
//通過ThreadLocal獲取Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
通過Looper中的預備工作,sThreadLocal中已經儲存了一個Looper物件,然後myLooper()方法通過sThreadLocal.get()方法獲取到了Looper。那麼訊息佇列就與執行緒關聯上了,所以各個執行緒只能訪問自己的訊息佇列。
綜上所述,我們可以發現訊息佇列通過Looper與執行緒關聯上了,而Looper又與Handler是關聯的,所以Handler就跟執行緒、執行緒的訊息佇列關聯上了。
3.2 如何執行訊息迴圈?
在建立Looper物件後,通過Handler發來的訊息放在訊息佇列中後是如何被處理的呢?這就涉及到了訊息迴圈,訊息迴圈是通過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.");
}
final MessageQueue queue = me.mQueue;//獲取訊息佇列
//程式碼省略
for (;;) {//死迴圈
Message msg = queue.next(); // 獲取訊息
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//程式碼省略
try {
msg.target.dispatchMessage(msg);//分發訊息
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//程式碼省略
msg.recycleUnchecked();//訊息回收
}
}
從原始碼中我們可以看出,loop()方法實質上就是通過一個死迴圈不斷的從訊息佇列中獲取訊息,然後又不斷的處理訊息的過程。
3.3 訊息處理機制
msg.target.dispatchMessage(msg);//分發訊息
我們從loop中的 dispatchMessage()方法入手,看看誰是該方法的呼叫者,深入Message原始碼中看看target的具體型別:
public final class Message implements Parcelable {
//程式碼省略
/*package*/ int flags;
/*package*/ long when;
/*package*/ Bundle data;
/*package*/ Handler target;
/*package*/ Runnable callback;
/*package*/ Message next;
//程式碼省略
}
從原始碼中我們可以看到其實target就是Handler型別。所以Handler是將訊息傳送到訊息佇列暫時儲存下,然後又將訊息傳送給Handler自身去處理。那我們繼續到Handler原始碼中去看看Handler是如何處理訊息的:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
/**
* Subclasses must implement this to receive messages.
* 訊息處理方法為一個空方法,由子類去實現
*/
public void handleMessage(Message msg) {
}
從上面的原始碼中可以看出,dispatchMessage(Message msg)只負責分發Message。從Message原始碼中我麼可以知道callback為Runnable型別,如果callback不為空,則執行 handleCallback方法來處理,而該方法又會呼叫callback.run();如果如果callback為空,則呼叫handleMessage來處理訊息,而該方法又為空,所以我們會在子類中重寫該方法,並將修改UI的程式碼寫在裡面。之所以會出現這兩種情況,是因為Handler傳送訊息有兩種形式:
- 在本文的一般使用步驟中,我使用的是sendMessage(msg)傳送訊息,此時callback就為空。
- 當使用post傳送訊息時,callback就不為空。
以上就是Handler機制的原理,大致可以總結為:在子執行緒中Handler將訊息傳送到MessageQueue中,然後Looper不斷的從MessageQueue中讀取訊息,並呼叫Handler的dispatchMessage傳送訊息,最後再Handler來處理訊息。為了更好的幫助大家一起理解,我畫了一個Handler機制的原理圖:
關於Handler機制補充如下幾點:
- Handler建立訊息時用到了訊息池,在建立訊息時會先從訊息池中去查詢是否有訊息物件,如果有,則直接使用訊息池中的物件,如果沒有,則建立一個新的訊息物件。
- 使用ThreadLocal的目的是保證每一個執行緒只建立唯一一個Looper。之後其他Handler初始化的時候直接獲取第一個Handler建立的Looper
以上就是我對Handler機制原理的理解,希望對你有所幫助。如有不足之處,歡迎指正!