常見的使用Handler執行緒間通訊:
//主執行緒:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
...
}
};
//子執行緒:
Message message = new Message();
message.arg1 = 1;
Bundle bundle = new Bundle();
bundle.putString("test", "test");
message.setData(bundle);
handler.sendMessage(message);
複製程式碼
這類操作一般用於在子執行緒更新UI。在主執行緒建立一個handler,重寫handlermessage方法,然後在子執行緒裡傳送訊息,主執行緒裡就會接受到訊息。這就是簡單的執行緒間通訊。
如果在子執行緒建立handler物件則會報錯。根據Log提示,子執行緒建立handler需要呼叫Looper.prepare() (在main函式中已經呼叫了Looper.prepareMainLooper(),該方法內會調起Looper.prepare()),Looper.loop()方法 。但是即使子執行緒呼叫Looper.prepare()建立Looper物件,這個Looper也是子執行緒的,不可以用於更新UI操作。
那到底Handler、Looper這幾個類之間是如何工作的呢?我們從源頭看起,以下是Looper類的prepare()方法:
public final class Looper {
...
final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //threadLocal是執行緒內部的資料儲存類,該類儲存了執行緒的所有資料資訊。
public static void prepare() {
prepare(true);
}
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)); //建立一個Looper(Looper的構造器裡也建立了一個MessageQueue,),將Looper與執行緒關聯起來
}
public static @Nullable Looper myLooper() { //下面會看到的,設定Handler類裡的Looper時會呼叫該方法
return sThreadLocal.get(); //獲得Looper物件
}
...
}
複製程式碼
當handler傳輸message時,不論是呼叫sendMessage(Message msg)還是sendMessageDelayed(),最後都會指向sendMessageAtTime()方法:
public class Handler {
...
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);
}
...
}
複製程式碼
mQueue即訊息佇列,用於將收到的訊息以佇列形式排列,提供出隊和入隊方法,該變數是Looper的成員變數,在Handler建立時賦值給handler
public class Handler {
...
final Looper mLooper;
final MessageQueue mQueue;
mLooper = Looper.myLooper(); //建立Handler前呼叫Looper.prepare()時定義並設定了Looper,這裡呼叫Looper.myLooper()來獲得該Looper
mQueue = mLooper.mQueue;
...
}
複製程式碼
上面呼叫的enqueueMessage(queue, msg, uptimeMillis)方法作用是訊息入隊
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //把handler本身賦值給要入隊的訊息,用來待會兒出隊使用
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製程式碼
可以看到最後是由訊息佇列queue呼叫自身MessageQueue類的入隊方法enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don`t have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
複製程式碼
mMessages是MessageQueue類的一個成員變數,用以記錄排在最前面的訊息,msg是我們傳入的message,msg.next是Message類的成員變數,可以理解成下一條訊息。入列方法重點看這幾句:
Message p = mMessages;
msg.next = p; //把mMessages賦值給新入隊的這條訊息的next
mMessages = msg; //把新入隊的訊息賦值給mMessages
複製程式碼
就像排隊一樣,msg是來插隊的,排第一的mMessages自願排到msg的後面,並讓msg站到自己原來的位置上,這樣就完成的msg的入隊操作,整個訊息入隊操作是按照時間來排序的。至於出隊操作,就在一開始所提到的ActivityTread中的main方法裡呼叫的Looper.loop()方法裡:
public static void loop() {
...
for (;;) {
...
Message msg = queue.next(); //獲取下一條訊息
...
msg.target.dispatchMessage(msg); //傳遞訊息
...
msg.recycleUnchecked(); //清空狀態,迴圈往復
}
}
...
}
複製程式碼
提煉出來就是在loop方法裡一直死迴圈,從MessageQueue訊息佇列裡使用next()方法獲得下一條訊息,next方法簡單看就是:
Message msg = mMessages;
mMessages = msg.next;
msg.next = null;
return msg;
複製程式碼
這就是簡單的解釋訊息出列,把排第一的訊息作為方法的返回值,然後讓排第二的排到第一去。獲得訊息後使用msg.target(上面入隊時賦值的handler)來傳遞訊息:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //如果有callback引數則呼叫處理回撥的方法
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg); //將訊息作為引數傳遞出去
}
}
複製程式碼
至此,handler傳遞訊息的整個流程走完。另外還有一個我們經常用到handler的方法post:
public final boolean post(Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) { //將runnable變成message自身的callback變數
Message m = Message.obtain();
m.callback = r;
return m;
}
複製程式碼
可以看到,post的runnable引數經過getPostMessage()方法最後被賦值給要傳遞下去的訊息的callback這個變數,等到訊息出列時,如果訊息帶有callback引數則呼叫處理回撥的方法handleCallback(msg)
private static void handleCallback(Message message) {
message.callback.run();
}
複製程式碼
可以看到,不論是從sendMessage裡發出的訊息,還是在post傳遞的runnable裡執行的程式碼,最後都是殊途同歸,都是在UI執行緒執行的。最後總結一下吧,執行緒間通訊原理大概就是:
- Looper.prepare()建立Looper和MessageQueue,並與所線上程關聯
- Looper.loop()通過一個for死迴圈不斷對MessageQueue進行輪詢
- 建立handler時,會把Looper和MessageQueue賦值給handler,將三者關聯起來。當handler呼叫sendMessage傳遞訊息,訊息會被髮送到Looper的訊息佇列MessageQueue裡
- 一旦loop()方法接收到訊息,則將訊息通過該訊息攜帶的handler(msg.target)的handleMessage方法處理