概述
Android的消息機制對我們開發者來說應該是很熟悉的,其中最常見的用法就是利用Handler切換到主線程然後更新UI,消息機制的用法當然不僅僅局限于這個場景,但總的來說,消息機制解決了線程間和線程内的消息通信的問題。Android消息機制是指以Handler為上層接口,MessageQueue和Looper為底層支撐的工作過程。下面簡單介紹一下這三個類:
①Handler是我們經常接觸的,我們常用它來切換線程;
②MessageQueue,顧名思義,它是一個消息隊列(内部實作是單連結清單),它的工作職責是把消息插入消息隊列以及從消息隊列擷取一條消息;
③Looper,消息循環,它會不斷地循環查詢MessageQueue是否有新的消息,然後去處理這個消息。
實際上,這三個類是一起協調運作的,它們是一個整體。是以本文将詳細介紹Java層的Handler、MessageQueue和Looper的工作原理。由于篇幅限制,Native層的消息機制會在下一篇文章進行解析。(注:本文源碼均取自Android P)
Java層的消息機制
我們首先來看Java層的消息機制,因為這是我們開發時經常接觸的,先了解Java層的消息機制然後再往底層去探究。我們分别從MessageQueue、Looper、Handler來解析它們的工作原理。
一、消息隊列MessageQueue的工作原理
MessageQueue雖然被稱作消息隊列,但實際上它的内部實作是用單連結清單來實作的。它主要涉及兩個操作:插入和讀取。插入表示将一條消息插入消息隊列等待處理,而讀取則是從消息隊列中讀取出一條消息傳回。
1、MessageQueue#enqueueMessage(Message,long)
該方法用于把一個
Message
添加到消息隊列中,詳細請看代碼:
boolean enqueueMessage(Message msg, long when) {
//這裡的msg.target 實際上就是Handler執行個體
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//判斷該Message是否已經被加入了隊列
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//加鎖,該代碼段同一時間内隻允許一條線程程序操作
synchronized (this) {
//如果該Handler被标記為了退出,那麼入隊失敗
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(); //把目前Message标為為使用狀體
msg.when = when;
Message p = mMessages; //mMessage實際上是消息連結清單的頭部
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果目前消息連結清單為空或者消息觸發時間為0,那麼把該消息設定為連結清單的頭部
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.
// 同時滿足以下3個條件則需要馬上喚醒消息隊列:
// 1、mBlocked為true
// 2、連結清單頭部的消息是一個barrier(表現為message.target為null)
// 3、該message是一個異步消息
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
//周遊單連結清單,找出消息需要插入的位置,該位置的when需要比消息的when大,
//即消息在連結清單中的順序是以時間排序的,when越大,則排在連結清單越後面。
//這裡的when,可以了解為消息觸發的時間,比如:
//handler.sendMessageDelayed()規定了消息的觸發時間
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // 插入單連結清單中恰當的位置
prev.next = msg;
}
// needWake标記位為真,則喚醒消息隊列,下面的是native方法,先從Native層開始喚醒。
// mPtr 表示的是Native層消息連結清單的頭部
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
詳細的分析以注釋的形式寫在了代碼内,代碼的邏輯還是很清晰的,當通過
Handler
發送一個消息的時候,就會通過上述方法把該消息插入了消息隊列(連結清單)中。首先先對該消息進行有效性判斷,不符合條件的添加失敗。然後開始周遊
mMessages
,即消息連結清單;根據觸發時間來找到消息需要插入的位置。當消息被插入單連結清單後,就要根據需要來确定是否需要喚醒該消息隊列。所謂的喚醒的意思是指:由于有滿足要求的新消息加入了隊列,原本處于阻塞狀态的消息隊列就能夠取出該新消息而傳回。
我們把關注點放在
needWake = mBlocked && p.target == null && msg.isAsynchronous()
喚醒條件的判斷上。
mBlocked
表示消息隊列正在被阻塞;
p.target == null
表示隊列頭部的消息是一個消息屏障,最後一個條件表示目前要插入的消息是一個異步消息。消息屏障是什麼東西呢?我們先把這個放在一邊,待會再回過頭來解釋。從上面三個條件可以知道,同時滿足這三個條件後,會就調用
nativeWake(mPtr)
方法,去喚醒Native Looper。
2、MessageQueue#next()
該方法用于在消息隊列中擷取一個消息,如果沒有消息就會一直阻塞在這裡,該方法運作于建立Handler所在的線程。
Message next() {
//如果消息隊列已經退出 直接傳回
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//調用Native方法,先處理Native層的新消息,根據nextPollTimeoutMillis參數确定逾時時間
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
//在消息連結清單中尋找下一條消息,如果找到就傳回處理
final long now = SystemClock.uptimeMillis();//擷取目前時間戳,從開機到現在的時間
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//如果該msg是一個消息屏障,那麼直接去找下一個異步消息來執行
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一條消息還沒到觸發時間,設定一個時間間隔,待會再觸發
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 擷取該消息
mBlocked = false;
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;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
// 如果調用了quit()方法,傳回null,中止消息循環
if (mQuitting) {
dispose();
return null;
}
// 第一次for循環周遊的時候,如果隊列空閑,那麼可以去處理Idle Handler
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 運作空閑handlers。可以了解為如果消息隊列為空,或者還沒到觸發時間,那麼可以去執行别的功能
// 隻有在第一次周遊的時候才會運作該代碼塊。
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
從代碼邏輯來看,next()方法内部是一個死循環,它不斷地周遊消息連結清單,直到找到一個可執行的消息後傳回。同時,在每一次的循環内部,會先調用
nativePollOnce(ptr, nextPollTimeoutMillis)
的一個native方法,該方法會在native層進行消息循環的處理。接着,在單連結清單中尋找下一條消息,并計算逾時時間,然後進行下一次循環,直到逾時時間到,此時該消息就準備好了,可以傳回給Looper進行處理。而在第一次for循環周遊的時候,如果目前消息隊列沒有可執行的消息而處于空閑狀态的時候,MessageQueue會調用
IdleHandler
來進行額外的處理。
3、消息屏障的作用
我們檢視MessageQueue的源碼,可以看到它有以下方法:
public int postSyncBarrier(long when) { }
public void removeSyncBarrier(int token){ }
閱讀注釋可以知道,上述兩個方法可以添加一個消息屏障到隊列中或從隊列中移除消息屏障。而消息屏障的作用在于:如果消息隊列遭遇到一個消息屏障,那麼在此之後的所有同步消息都會被延遲執行,直到消息屏障被移除後,才會繼續處理這些同步消息。而異步消息則不受到任何影響。消息屏障是由target為null的Message來表示。
4、IdleHandler的作用
在
next()
方法内,在第一次周遊for循環的時候,如果目前消息清單是空閑狀态,那麼就會調用IdleHandler來進行空閑情況下的處理。我們來看看它到底是什麼:
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
這是一個接口,聲明了一個
queueIdle()
方法,表示如果消息隊列在等待新的消息到來的時候,可以調用該方法。我們來看一個例子:
class ActivityThread{
final class GcIdler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
doGcIfNeeded();
return false;
}
}
}
這是ActivityThread為MessageQueue所添加的一個IdleHandler,顯然,這個handler表示了在空閑狀态下進行垃圾回收的功能。
是以,這啟發了我們可以通過
IdleHandler
來對MessageQueue的空閑狀态進行進一步的處理。比如這樣:
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
小結:上面介紹了MessageQueue的幾個方法,核心是enqueueMessage()和next(),前者表示将一個消息放在消息連結清單的正确位置,而後者是一個阻塞式循環直到可以傳回一個消息給Looper。一種常見情況是:子線程通過enqueueMessage()把消息添加到了消息隊列,而主線程在周遊的過程中會找到剛才插入的消息,進而把該消息傳回給Looper。
二、Looper的工作原理
下面來介紹Looper的工作原理。*Looper與線程相關,一條線程對應一個Looper,而一個Looper又對應一個MessageQueue。*Looper充當了一個消息循環角色,它不斷地進行循環,調用MessageQueue.next()來擷取一個消息進行處理。
如果需要為子線程建立一個Handler,那麼就要先調用
Looper#prepare()
為該線程初始化一個Looper,否則會抛出異常。那麼UI線程我們在業務層并沒有顯式地調用該方法,但是我們可以直接建立UI線程的Handler,這是為什麼呢?這是因為在應用啟動的時候,在
ActivityThread#main()
方法裡面,已經幫我們調用了
Looper.prepareMainLooper()
方法,這樣我們就可以在主線程直接使用Handler了。我們來看看
Looper#prepare()
:
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed); //執行個體化一個MessageQueue,一個Looper對應一個MessageQueue
mThread = Thread.currentThread();
}
這裡出現了
ThreadLocal
,這個類筆者在之前的文章有專門解析過,這裡就不再贅述了。簡單地說,它是一個線程獨立的變量,不同的線程所通路得到的值僅與該線程有關,與别的線程無關。這裡把Looper設定為了線程獨立的變量,也就是說調用了
Looper#prepare()
方法的線程,會建立一個Looper執行個體與目前線程綁定起來。經過以上步驟,該線程的Looper就建立完畢了,接着如果要啟動消息循環機制,就要接着調用
Looper#loop()
方法,該方法真正地開啟了消息循環,它會不斷地從MessageQueue取出消息進行處理。
public static void loop() {
final Looper me = myLooper();
//在沒有調用prepare()的線程而使用Handler會直接抛出異常
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
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.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//省略部分代碼....
for (;;) {
Message msg = queue.next(); // 這裡可能會阻塞,等待消息
if (msg == null) {
// 如果MessageQueue傳回了null,表示已經沒有消息要處理,退出消息循環
// 隻有調用了quit()方法,MessageQueue才會傳回null
return;
}
try {
msg.target.dispatchMessage(msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked(); //回收該Message
}
}
loop()方法的邏輯不難了解,它的for循環内部,會不斷地調用
queue.next()
方法,進而擷取一個Message,然後調用
msg.target.dispatchMessage(msg)
方法來處理消息。實際上msg.target就是發送消息的Handler,也就是說,這消息從Handler發送到消息隊列後,經過消息循環系統的循環後,最後又交給了Handler#dispatchMessage(msg)來處理,隻是經過消息循環機制後,dispatchMessage(msg)方法會在建立Handler所在的線程執行,這樣也就實作了代碼邏輯切換到指定的線程去執行這一效果了。
接着,我們來看看主線程的消息循環是怎樣的,在
ActivityThread#main(args)
代碼内:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
//省略部分代碼...
Looper.prepareMainLooper();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
我們可以看到,這裡順序執行了
prepare()
和
loop()
,開啟了主線程的消息循環系統,這樣子線程發送過來的消息,會經過主線程的消息循環然後切換到UI線程内去處理這些消息。
三、Handler的工作原理
上面講述了MessageQueue和Looper的工作原理,它們是Handler得以運作的底層支撐,有了上述的知識基礎,下面來了解Handler就容易很多了。接着我們來看Handler的工作原理。我們利用Handler發送消息有很多方法可以調用,一種常見的形式是:
mHandler.sendEmptyMessageDelayed(msg,delayMillis)
,然後我們在
Handler#handleMessage(msg)
來處理這個消息,此時線程已經被切換到了建立Handler的線程了。
1、首先,我們來看Handler的構造方法,看它的初始化過程做了哪些工作:
public Handler() {
this(null, false); //async預設是false,即采取同步消息的形式
}
public Handler(Callback callback, boolean async) {
// 下面檢查是否存在記憶體洩漏的問題,為了防止記憶體洩漏,我們的Handler
// 需要寫出靜态内部類的形式
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
// 判斷目前線程是否已經準備好了Looper
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會判斷是否存在記憶體洩漏的問題,以及目前線程是否以及準備好了Looper(即Looper.prepare()是否已經被調用)。然後設定mCallback和mAsynchronous參數。從構造方法可以看出,預設的情況下我們使用無參構造方法所建立的Handler的mAsynchronous都是false,即該Handler發送的消息都是同步形式的,會被消息屏障過濾掉。當然,我們也可以利用帶async參數的構造方法建立一個發送異步消息的Handler。
2、利用Handler發送消息
一般地,我們發送消息的時候有很多方法可以調用,比如post()和sendMessage()等,但實際上post()方法内部調用的還是sendMessage()方法。是以我們直接來看sendMessage的相關方法即可,比如說
mHandler.sendEmptyMessageDelayed()
方法:
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain(); //擷取一個Message執行個體,這裡被設計成對象池的形式,便于複用
msg.what = what; //what參數用于辨別某一個消息
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
// 消息觸發時間 = 系統時間 + 延遲時間
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this; //這裡的this 指向了調用該方法的對象,即我們的Handler
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
代碼的邏輯很容易了解,生成一個消息後,就會調用
MessageQueue#enqueueMessage(msg,when)
方法,把該消息插入了消息隊列。是以說,Handler的sendMessage()系列方法,隻是把消息插入了消息隊列,後續是交給Looper的消息循環來進行處理的。
3、利用Handler處理消息
前面提到,Looper的loop()方法會不斷調用MessageQueue#next()方法來獲得一個Message,然後調用
msg.target.dispatchMessage(msg)
方法,最終把消息又交給了Handler處理,此時已經被切換到了目标線程。我們來看
Handler#dispatchMessage(msg)
的源碼:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //如果msg有callback,那麼優先調用
} else {
if (mCallback != null) {
//如果Handler有callback,那麼調用Handler的callback來處理
if (mCallback.handleMessage(msg)) {
return;
}
}
//否則,最後調用Handler的重寫方法來處理這個消息
handleMessage(msg);
}
}
處理邏輯很清晰,首先會判斷msg是否帶有一個callback的成員變量,這裡的msg.callback實際上就是一個Runnable,這是通過handler.post(runnable)來添加的。然後接着判斷Handler的mCallback是否不為空,這裡的mCallback實際上建立Handler的另一種方式,即我們可以不要派生Handler子類,而是利用
Handler handler = new Handler(callback)
來建立一個Handler。在以上條件都不滿足的情況下,最後交給
Handler#handleMessage(msg)
來處理,這也是我們派生子類需要重寫的方法。
總結
上面介紹了Handler、MessageQueue和Looper三者的工作原理,下面給出一個示意圖來友善大家了解整個消息機制的工作原理。
首先是子線程的Handler執行個體發送一個消息,這個消息就會被插入到消息隊列中等待被處理。在主線程的Looper周遊的過程中,會發現消息隊列新增了消息,就會把這條消息取出來交給Handler處理,處理完畢後Looper繼續周遊過程查找下一條消息。