我們在 Handler線程通信機制:實戰、原理、性能優化! 中已經知道了Handler的消息同步機制,在MessageQueue的next方法中,有一段邏輯是處理同步屏障的,我們在本章中將分析同步屏障是什麼?原理?以及它在Android中的使用。
同步屏障機制的原理
在Handler的介紹中,我們已經瞭解了同步屏障,但當時這塊不是重點介紹的內容,我們來複習一下它的實現原理。
MessageQueue的next方法:
Message next() {
……
for (;;) {
……
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) {//同步屏障消息
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
……
}
在MessageQueue的next方法中,獲取當前執行的消息時,首先對消息進行了判斷,如果當前消息是同步屏障消息,則屏蔽後續所有同步消息的執行,異步消息不受影響。
同步屏障消息對象的target屬性是null,也就是沒有持有Handler對象的引用。
這裏我們回想一下,如果消息通過Handler的sendMessage方法添加到消息隊列時,會判斷target是否爲null,如果是null則會拋出錯誤,所以同步屏障消息的添加不是通過Handler的常用方法添加的。
同步屏障的添加和移除
既然同步屏障不是通過Handler的sendMessage等方法添加的,那麼它又是通過哪個方法進行設置的呢?
我們在MessageQueue中發現是postSyncBarrier方法添加的同步屏障消息。
同步屏障的添加
MessageQueue的postSyncBarrier方法:
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
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) {
//獲取token值
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;
}
}
邏輯解析:
- postSyncBarrier方法調用了有參的postSyncBarrier方法,傳遞了一個執行時間的參數。
- 在同步代碼塊中,執行添加邏輯。
- 首先通過mNextBarrierToken屬性獲取一個int類型的token,它也是該方法的返回值,用於移除屏障時使用。
- 獲取了一個新的Message對象,注意這裏沒有設置target屬性,正常的消息target屬性不能爲空。
- 根據when屬性,將同步屏障消息添加到消息隊列的合適位置。
- 最後返回token。
注意:添加同步屏障,不需要喚醒線程。
同步屏障的移除
同步屏障添加後,並不能自動移除,需要調用系統接口手動移除。
MessageQueue的removeSyncBarrier方法:
public void removeSyncBarrier(int token) {//參數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) {//沒有找到則拋出error
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) {//表示同步屏障還未執行,所以不需要喚醒消息循環(喚醒線程)
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);
}
}
}
邏輯解析:
- MessageQueue的removeSyncBarrier方法可以移除同步屏障,參數是添加消息屏障的返回值token,用於移除指定的消息屏障。
- removeSyncBarrier方法首先判斷當前消息mMessages,是否是同步屏障消息,如果當前消息不是同步屏障消息,則遍歷消息鏈表進行查找。
- 如果沒有找到同步屏障消息,則拋出Error,否則繼續執行。
- 如果同步屏障消息尚未執行,則刪除該消息即可,不用執行喚醒消息循環。
- 否則,當前消息既是同步屏障消息,刪除該消息後,需要根據條件,進一步判斷是否需要喚醒消息循環。
- 最後,如果需要喚醒消息循環,並且當前消息循環未執行退出操作,則調用nativeWake方法執行喚醒操作。
同步消息屏障在Android系統中的使用場景
在Android系統中,當應用進行視圖的刷新渲染時,用到了同步消息屏障,這裏使用的目的是:優先保障視圖的刷新邏輯的執行,避免UI線程執行其他同步任務導致的卡頓等問題。
ViewRootImpl的scheduleTraversals方法:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
ViewRootImpl的scheduleTraversals方法,調用了MessageQueue的postSyncBarrier方法來添加一個同步消息屏障,然後執行視圖渲染相關邏輯。
ViewRootImpl的unscheduleTraversals方法
當系統完成UI視圖的渲染刷新後,會調用ViewRootImpl的unscheduleTraversals方法移除同步屏障。
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
總結
本文章我們學習了Android消息循環的同步屏障機制原理,以及Android系統中的使用,我們來簡單總結下:
- 在MessageQueue的next方法中,獲取當前執行的消息時,首先對消息進行了判斷,如果當前消息是同步屏障消息,則屏蔽後續所有同步消息的執行,異步消息不受影響。
- 同步屏障消息對象的target屬性是null,也就是沒有持有Handler對象的引用。
- 同步屏障的消息可以通過MessageQueue的postSyncBarrier方法進行添加,添加時,不需要進行消息循環的喚醒操作。
- MessageQueue的postSyncBarrier方法的返回值是一個int類型的token,用於移除屏障時使用。
- 同步屏障添加後,並不能自動移除,需要調用系統接口手動移除。
- MessageQueue的removeSyncBarrier方法可以移除同步屏障,參數是添加消息屏障的返回值token。
- 如果同步屏障消息尚未執行,則刪除該消息即可,不用執行喚醒消息循環。
- 當前消息既是同步屏障消息,刪除該消息後,需要根據條件,進一步判斷是否需要喚醒消息循環,如果需要喚醒消息循環,並且當前消息循環未執行退出操作,則調用nativeWake方法執行喚醒操作。
- 在Android系統中,當應用進行視圖的刷新渲染時,ViewRootImpl的scheduleTraversals方法中,用到了同步消息屏障。這裏使用的目的是:優先保障視圖的刷新邏輯的執行,避免UI線程執行其他同步任務導致的卡頓等問題。
- 當系統完成UI視圖的渲染刷新後,會調用ViewRootImpl的unscheduleTraversals方法移除同步屏障。