什麼是Handler的同步屏障
Handler中的Message可以分爲兩類:同步消息、異步消息。消息類型可以通過以下函數得知
//Message.java
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
一般情況下這兩種消息的處理方式沒什麼區別,在設置了同步屏障時纔會出現差異。
Handler設置同步屏障之後可以攔截Looper對同步消息的獲取和分發。加入同步屏障之後,Looper只會獲取和處理異步消息,如果沒有異步消息會進入阻塞狀態。
怎樣設置同步屏障
同步屏障可以通過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;
}
}
方法分析
可以看到,該函數僅僅是創建了一個 Message 對象並加入到了消息鏈表中。乍一看好像沒什麼特別的,但是這裏面有一個很大的不同點是該 Message 沒有 target。
我們通常都是通過Handler發送消息的,Handler中發送消息的函數有postXXX、sendEmptyMessageXXX以及sendMessageXXX等函數,而這些函數最終都會調用enqueueMessage函數
//Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//...
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到 enqueueMessage 爲 msg 設置了 target 字段。
所以,從代碼層面上來講,同步屏障就是一個 Message,一個 target 字段爲空的 Message。
同步屏障的工作原理
同步屏障只在Looper死循環獲取待處理消息時纔會起作用,也就是說同步屏障在MessageQueue.next函數中發揮着作用。
next 函數相關代碼
Message next() {
//...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
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 while循環遍歷消息鏈表
// 跳出循環時,msg指向離表頭最近的一個異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//...
} 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();
//返回異步消息
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
}
//...
}
}
可以看到,當設置了同步屏障之後,next函數將會忽略所有的同步消息,返回異步消息。換句話說就是,設置了同步屏障之後,Handler只會處理異步消息。再換句話說,同步屏障爲Handler消息機制增加了一種簡單的優先級機制,異步消息的優先級要高於同步消息。
如何發送異步消息
通常我們使用 Handler 發消息時,這些消息都是同步消息,如果我們想發送異步消息,那麼在創建 Handler 時使用以下構造函數中的其中一種( async傳true )
public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);
然後通過該Handler發送的所有消息都會變成異步消息
同步屏障的應用
Android4.1之後增加了Choreographer機制,用於同 Vsync 機制配合,統一動畫、輸入和繪製時機。
ViewRootImpl的requestLayout開啓繪製流程:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//檢查是否在主線程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout佈局。
//重要函數
scheduleTraversals();
}
}
Android 應用框架中爲了更快的響應UI刷新事件在 ViewRootImpl.scheduleTraversals 中使用了同步屏障
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//設置同步障礙,確保mTraversalRunnable優先被執行
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//內部通過Handler發送了一個異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
mTraversalRunnable 調用了 performTraversals 執行measure、layout、draw
爲了讓mTraversalRunnable儘快被執行,在發消息之前調用MessageQueue.postSyncBarrier設置了同步屏障。