Handler一般用於執行緒間通訊,如常用的子執行緒使用handler讓主執行緒更新UI。那麼這是怎麼實現的呢?
我們先把這個大問題分解成多個小問題:
post();postDelayed();sendMessage();sendEmptyMessage();
等方法有什麼不同?postDelayed()
為什麼可以讓執行緒延遲執行?接下來帶著這些疑惑去尋找答案。
post();postDelayed();sendMessage();sendEmptyMessage();
等方法有什麼不同?它們最終都是呼叫同一個方法:sendMessageAtTime()
,只是引數不同,Handler幫我們進行了一下封裝。
先看post();postDelayed();
這兩個方法,檢視原始碼可以發現,這兩個方法都是呼叫sendMessageDelayed(Message, long)
。只是post()的時間這個引數是0。
程式碼如下:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
關鍵是將Runable任務封裝成Message的這個getPostMessage()
。
這裡並不是簡單地將Runable封裝成Message,這裡還有一個Message回收池機制的實現。將在下文展開介紹。
再看sendMessage();sendEmptyMessage();
這兩個方法:
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessage(int what){
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
可以看到這四個方法最終都是呼叫sendMessageDelayed()
:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
上面sendMessageDelayed()
的實現都很簡單,但需要注意的是這裡使用了SystemClock.uptimeMillis()
,它返回的是裝置的開機時間(不包括息屏睡眠時間),這個時間和Handler的訊息可以延遲觸發有關。將在後面詳細介紹。
因為MessageQueue是通過Looper獲取到的。
而Message需要通過MessageQueue來等待執行。
在建立Handler時如果檢測到Looper為空,將會丟擲NullPointerException錯誤
如果不是通過建構函式傳入的Looper,比如Handler#Callback,構造方法會通過Looper.myLooper()
獲取到當前執行緒的Looper。
Looper.myLooper()
可以獲取到當前執行緒的Looper是因為ThreadLocal的特性。
程式碼如下:
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler最長使用的大概是子執行緒通知UI執行緒更新UI吧。
我們通過post();sendMessage();
等方法提交一個Message時,這個Message會被放入MessageQueue。在建立Handler時會得到一個Looper,Looper會迴圈從MessageQueue取出Message處理,而每個Looper屬於一個執行緒,如果該Looper是UI執行緒的,那Message就是在UI執行緒處理。
知其然亦應知其所以然。
我們從Handler#post()
這個方法開始研究。
在上面已經講過,post()
最終呼叫的是sendMessageAtTime()
,這個方法首先是獲取了與該Handler繫結Looper的MessageQueue物件,然後通過一些引數設定,最後執行MessageQueue#enqueueMessage()
方法。
看方法名就知道這個方法主要工作是對Message排隊處理:
boolean enqueueMessage(Message msg, long when) {
//...
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//...
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
//...
}
return true;
}
我去掉了一些和排隊無關的程式碼,上面這段程式碼並不難,就是以連結串列的形式將Message進行排列。
首先判斷這個Message是否正在被使用。
其實這個也和Message回收池有些關係。
我們new的物件和回收池中取出的Message預設狀態是0,當Message進入MessageQueue等待處理時就是被使用狀態。從MessageQueue取出,被處理完成回收的Message其狀態又會被重置為0 。
回到Message排隊問題,然後判斷三個條件:
Handler#sendMessageAtFrontOfQueue()
才會出現當其中一個條件滿足時,將當前Message插入連結串列頭部。
既然Message時怎麼放入MessageQueue這塊已經弄清楚了,那接著看一下Looper。看看是怎麼取出Messages,又是怎麼處理的
如果使用過Looper就應該知道,這是一個迴圈。
檢視這個類的註釋,可以看到它提供了一個簡單的用法。
Looper.prepare();
Looper.loop();
主要有兩個方法:
Looper實現在這裡不進行深入,只講一下和MessageQueue有關的。
先看loop()
:
//Looper
public static void loop() {
final Looper me = myLooper();
//...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return false;
}
//。。。
msg.target.dispatchMessage(msg);
//。。。
msg.recycleUnchecked();
return true;
}
//Handler
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到loop()
裡面用for寫了一個死迴圈,它執行了loopOnce()
,它是真正取出Message並執行的方法。
在dispatchMessage()
方法內,首先判斷callback是否為空,它是Message的Runable,就是我們使用post();postDelayed();
提交到Runable。
然後判斷Handler#Callback
,是否為空,這個是在Handler構造方法傳入的Callback,這裡也解釋了當我們實現了callback時可以跨執行緒通訊的原因。
next()
返回null會結束for迴圈。我們Android主執行緒沒有訊息為什麼還可以繼續執行?我們建立Looper物件都是通過其靜態方法來建立的,而Looper的構造方法有一個引數quitAllowed,這個引數為True時MessageQueue不會因為訊息為空而退出。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
postDelayed()
為什麼可以讓執行緒延遲執行?讓我們回到上面Looper#loopOnce()
這個方法,我在上面沒有介紹怎麼從MessageQueue取出Message就是留給這個問題的。
線上關鍵程式碼:
//MessageQueue
Message next() {
for (;;) {
synchronized (this) {
//獲取當前的開機時間
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
//判斷當前開機時間是否小於msg的開機時間
//如果為false表示這條訊息應該被拿出去處理了
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
}
//。。。
}
}
}
這塊程式碼還是很多的,我去掉了和該問題無關的程式碼。
這裡先獲取了當前開機時間,然後和Message的when對比,如果當前開機時間比when大,表示這條訊息到了處理時間,直接return。
我們使用postDelayed(Runnable,0)
時,執行到sendMessageDelayed()
後會加上SystemClock.uptimeMillis()
最後變成when值,也就是SystemClock.uptimeMillis()+0
,
因此MessageQueue取出訊息時,該Message會被立即執行,而延遲xxx時間是一樣的道理。
讓我們回到上面展示的Looper#loopOnce()
這個方法,可以看到當訊息被取出來處理後,呼叫了msg.recycleUnchecked();
回收當前Message:
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details. flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
這個方法會重置Message引數,然後判斷當前回收池有沒有達到上限,上限是50個,沒有達到會把這個Message插入連結串列等待再次使用。
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
Message.obtain()
會檢查回收池,如果回收池不為空,從連結串列頭部取出一個物件並返回。
在Google官方檔案也能看到,Google建議我們通過Message.obtain()
獲取一個新的Message物件,而不是直接new。
handler的這個機制是由幾個類一起共同作業共同實現的。它們分別是: