同步屏障的應用
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true
//通過postSyncBarrier()設置同步屏障,
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//handler發送消息,有了同步屏障mTraversalRunnable就會被優先執行。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
ViewRootImpl.scheduleTraversals()里發送了Handler消息,最終會執行TraversalRunnable的run(),在這個run()中會執行doTraversal(),最終會觸發View的繪制流程:measure(),layout(),draw()。為了讓繪制流程盡快被執行,用到了同步屏障技術。
源碼分析
開啟同步屏障是通過MessageQueue.postSyncBarrier():
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
//構造一個Message,沒有對msg.target賦值,target是Handler
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;
}
}
//將該message插入表頭
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
這里構造了一個Message,并且沒有指定msg.target,最后將這個Message插入表頭。這個Message就構成了一個內存屏障。
再看它是如何構成內存屏障的。根據Handler的使用及調用流程源碼分析可知,取消息調用的是MessageQueue.next():
Message next() {
for (;;) {
synchronized (this) {
Message prevMsg = null;
Message msg = mMessages;
//根據msg.target是否為null判斷內存屏障是否存在。
if (msg != null && msg.target == null) {
do {
//找出異步消息
prevMsg = msg;
msg = msg.next;
//根據isAsynchronous判斷是否是異步消息,是異步消息則跳出循環
} while (msg != null && !msg.isAsynchronous());
}
//將消息返回
return msg;
}
}
在next()中如果設置了同步屏障,那么就會通過do..while()循環優秀去找消息列表中的異步消息,找到后返回。
所以所有的異步消息都處理完后,才會處理同步消息。同步屏障就是添加了一個標識,這個標識是一個沒有target的Message。如果有這個標識,就先去處理異步消息。
再看異步消息如何發送,發送消息會調到Handler.enqueueMessage():
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//mAsynchronous只能通過Handler構造設置
if (mAsynchronous) {
//標記為異步消息
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
根據mAsynchronous判斷是否需要標記為異步消息,這個mAsynchronous變量可以在Handler的構造中設置,一旦設置了以后,該Handler發送的所有消息都是異步消息,不能修改。
如果需要同步異步消息都發送,可以通過構造普通Handler,然后發送消息時設置msg.setAsynchronous(true)將消息標記為異步消息。