Handler Message種類
Handler的Messgae種類分爲三種:
- 普通消息
- 異步消息
- 屏障消息
其中普通消息又稱爲同步消息,我們平時發的消息基本都是同步消息,在這裏不做討論。
異步消息
通常我們使用Handler想消息隊列中添加的Message都是同步的,如果我們想要添加一個異步的Message,有以下兩種方式:
- 1、Handler的構造方法有個async參數,默認的構造方法此參數是false,只要我們在構造handler對象的時候,把該參數設置爲true就可以了。
public Handler(Callback callback, boolean async) {
......省略代碼
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到,async設置爲true後,對全局的mAsynchronous設置爲true。然後在enqueueMessage()方法裏,調用msg.setAsynchronous(true),將message設置爲異步的。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- 2、在創建Message對象時,直接調用Message的setAsynchronous()方法。
在一般情況下,異步消息和同步消息沒有什麼區別,但是一旦開啓了同步屏障以後就有區別了。
同步屏障
一般來說,MessageQueue裏面的所有Message是按照時間從前往後有序排列的。
同步屏障消息就是在消息隊列中插入一個屏障,在屏障之後的所有普通消息都會被擋着,不能被處理。不過異步消息卻例外,屏障不會擋住異步消息,因此可以認爲,屏障消息就是爲了確保異步消息的優先級,設置了屏障後,只能處理其後的異步消息,同步消息會被擋住,除非撤銷屏障。
同步屏障是通過MessageQueue的postSyncBarrier方法開啓的。
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
// 1
final int token = mNextBarrierToken++;
// 2
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
// 3
// 指向前一個Message
Message prev = null;
// 消息隊列中的第一個Message賦值給p
Message p = mMessages;
if (when != 0) {
// 4 通過p的時間和屏障的時間,確定屏障消息插入的位置
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 5 說明屏障消息不是插入消息隊列的頭部
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
// 6 屏障消息在消息隊列的頭部
msg.next = p;
mMessages = msg;
}
return token;
}
}
- 第一步,獲取屏障的的唯一標示,標示從0開始,自加1。
- 第二步,從Message消息對象池中獲取一個msg,設置msg爲正在使用狀態,並且重置msg的when和arg1,arg1的值設置爲token值。但是這裏並沒有給tareget賦值。所以msag的target是否爲空是判斷這個msg是否是屏障消息的標誌。
- 第三步,創建變量pre和p,爲下一步做準備。其中p被賦值爲mMessages,mMessages指向消息隊列中的第一個元素,所以此時p指向消息隊列中的第一個元素。
- 第四步,通過對隊列中的第一個Message的when和屏障的when進行比較,決定屏障消息在整個消息隊列中的位置,因爲消息隊列中的消息都是按時間排序的。
- 第五步,prev != null,代表不是消息的頭部,把msg插入到消息隊列中。
- 第六步,prev == null,代表是消息隊列的頭部,把msg插入消息的頭部。
我們通常通過Handler發送消息handler.sendMessage(),最終都會調用Handler.java中的enqueueMessage()方法。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,enqueueMessage()方法裏爲msg設置了target字段。
而上面的postSyncBarrier(),也是從Message消息對象池中獲取一個msg,插入到消息隊列中,唯一的不同是沒有設置target字段。所以從代碼層面上講,屏障消息就是一個target爲空的Message。
屏障消息的工作原理
通過postSyncBarrier方法屏障就被插入到消息隊列中了,那麼屏障是如何擋住普通消息只允許異步消息通過的呢?
我們知道Handler的消息處理是在Looper.loop()從消息隊列中獲取消息,並交給Handler處理的,其中是通過MessageQueue是通過next方法來獲取消息的。查看一下next()的源碼,
Message next() {
// .....省略代碼
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
// 指向前一個message
Message prevMsg = null;
// 初始時指向第一個message
Message msg = mMessages;
// 1 msg.target == null說明遇到消息屏障
if (msg != null && msg.target == null) {
// 能進入這個if,說明此時的msg是屏障消息
// 循環遍歷,退出循環的條件是,message到末尾了,或者
// 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 {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
// 將msg從消息鏈表中移除
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
// 返回msg
return msg;
}
從上面的代碼可以看出,msg.target == null時說明此時的msg是屏障消息,此時會進入到循環,遍歷移動msg的位置,知道移動到的msg是異步message則退出循環,也就是說,循環的代碼會過濾掉所有的同步消息,直到取出異步消息爲止。
當設置了同步屏障之後,next函數將會忽略所有的同步消息,返回異步消息。換句話說就是,設置了同步屏障之後,Handler只會處理異步消息。再換句話說,同步屏障爲Handler消息機制增加了一種簡單的優先級機制,異步消息的優先級要高於同步消息。
移除同步屏障
同步屏障的移除是在MessageQueue.java的removeSyncBarrier()方法。
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 循環遍歷,直到遇到屏障消息時推退出循環
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
// 刪除屏障消息p
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
刪除屏障消息的方法很簡單,就是不斷遍歷消息隊列,知道找到屏障消息,退出循環的條件有兩個,一是p.target == null,說明是屏障消息,二是p.arg1 == token,也說明p是屏障消息,因爲在屏障消息入隊的時候,設置過 msg.arg1 = token。找到屏障消息後,把它從消息隊列中刪除並回收。