Android開發(fā)中Handler同步屏障機制(sync barrier)詳解
Handler同步屏障機制是Android開發(fā)中一個較為高級且復(fù)雜的特性,主要用于控制消息隊列MessageQueue中消息的處理順序。當(dāng)設(shè)置同步屏障時,會阻止所有普通消息(同步消息)的處理,同時允許立即消息(例如帶回調(diào)的消息或Runnable對象)繼續(xù)執(zhí)行。
「消息分類」:
- 「普通消息(同步消息)」:常見的通過Handler發(fā)送的消息,按照時間戳順序在MessageQueue中排隊。我們平時發(fā)的消息基本都是同步消息,在這里不做討論。
- 「屏障消息(同步屏障)」:一個特殊的Message對象,沒有target屬性,用于在MessageQueue中插入屏障。
- 「異步消息」:可以通過特定方式標記的消息,優(yōu)先級高于同步消息,即使存在同步屏障也能被處理。
屏障消息(同步屏障)
同步屏障是通過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) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
- 第一步,獲取屏障的的唯一標示,標示從0開始,自加1。
- 第二步,從Message消息對象池中獲取一個msg,設(shè)置msg為正在使用狀態(tài),并且重置msg的when和arg1,arg1的值設(shè)置為token值。但是這里并沒有給tareget賦值。所以msag的target是否為空是判斷這個msg是否是屏障消息的標志。
- 第三步,創(chuàng)建變量pre和p,為下一步做準備。其中p被賦值為mMessages,mMessages指向消息隊列中的第一個元素,所以此時p指向消息隊列中的第一個元素。
- 第四步,通過對隊列中的第一個Message的when和屏障的when進行比較,決定屏障消息在整個消息隊列中的位置,因為消息隊列中的消息都是按時間排序的。
- 第五步,prev != null,代表不是消息的頭部,把msg插入到消息隊列中。
- 第六步,prev == null,代表是消息隊列的頭部,把msg插入消息的頭部。
通常通過Handler發(fā)送消息handler.sendMessage(),最終都會調(diào)用Handler.java中的enqueueMessage()方法。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,enqueueMessage()方法里為msg設(shè)置了target字段。而postSyncBarrier()方法也是從Message消息對象池中獲取一個msg插入到消息隊列中,唯一的不同是沒有設(shè)置target字段,從代碼層面上講,屏障消息就是一個target為空的Message。
「工作原理」:Handler的消息處理是在Looper.loop()方法從消息隊列中獲取消息并交給Handler處理,其中是通過MessageQueue是通過next方法來獲取消息的。
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();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
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;
}
if (mQuitting) {
dispose();
return null;
}
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);
}
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);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
msg.target == null時說明此時的msg是屏障消息,此時會進入到循環(huán),遍歷移動msg的位置,直到移動到的msg是異步message退出循環(huán),也就是說循環(huán)的代碼會過濾掉所有的同步消息,直到取出異步消息為止。
當(dāng)設(shè)置了同步屏障之后,next函數(shù)將會忽略所有的同步消息,返回異步消息。設(shè)置了同步屏障之后,Handler只會處理異步消息。同步屏障為Handler消息機制增加了一種簡單的優(yōu)先級機制,異步消息的優(yōu)先級要高于同步消息。
「移除屏障」:屏障不會自動移除,需要手動調(diào)用MessageQueue.removeSyncBarrier(int token)方法移除。token是postSyncBarrier()方法返回的唯一標識符。
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;
// 循環(huán)遍歷,直到遇到屏障消息時推退出循環(huán)
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);
}
}
}
刪除屏障消息的方法很簡單,就是不斷遍歷消息隊列,直到找到屏障消息,退出循環(huán)的條件有兩個p.target == null(說明是屏障消息)和p.arg1 == token(說明p是屏障消息,在屏障消息入隊的時候,設(shè)置過msg.arg1 = token)。找到屏障消息后,把它從消息隊列中刪除并回收。
異步消息
通常我們使用Handler想消息隊列中添加的Message都是同步的,如果我們想要添加一個異步的Message,有以下兩種方式:
- Handler的構(gòu)造方法有個async參數(shù),默認的構(gòu)造方法此參數(shù)是false,只要在構(gòu)造handler對象的時候,把該參數(shù)設(shè)置為true。
public Handler(@Nullable Callback callback, boolean async) {
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());
}
}
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;
mIsShared = false;
}
async設(shè)置為true后,對全局的mAsynchronous設(shè)置為true。然后在enqueueMessage()調(diào)用msg.setAsynchronous(true)將message設(shè)置為異步的。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- 在創(chuàng)建Message對象時調(diào)用Message的setAsynchronous()方法。在一般情況下,異步消息和同步消息沒有什么區(qū)別,但開啟了同步屏障以后就有區(qū)別了。
- 當(dāng)Looper從MessageQueue中取出消息進行處理時,如果遇到屏障消息,會跳過所有后續(xù)的普通消息,直到找到異步消息或屏障被移除。
- 異步消息不受同步屏障的影響,可以直接被處理。
應(yīng)用場景
- 「確保立即任務(wù)優(yōu)先處理」:在需要優(yōu)先執(zhí)行某些緊急任務(wù)時,可以使用同步屏障暫時阻止其他消息的處理。
- 「避免死鎖和資源競爭」:在復(fù)雜的消息交互場景中,使用同步屏障可以防止因消息處理順序不當(dāng)引發(fā)的死鎖或資源競爭。
- 「UI繪制優(yōu)化」:在Android應(yīng)用框架中,為了更快地響應(yīng)UI刷新事件,ViewRootImpl在繪制流程中使用了同步屏障機制,確保異步繪制任務(wù)可以優(yōu)先執(zhí)行。
注意事項
- 「謹慎使用」:不恰當(dāng)?shù)氖褂猛狡琳峡赡軙?dǎo)致消息處理的延遲或阻塞,影響應(yīng)用性能和響應(yīng)能力。
- 「手動移除」:使用完同步屏障后,必須手動移除,否則會造成同步消息無法處理。