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