同步屏障
大家經(jīng)過上面的學習應(yīng)該知道,線程的消息都是放到同一個MessageQueue里面,取消息的時候是互斥取消息钦购,而且只能從頭部取消息牺六,而添加消息是按照消息的執(zhí)行的先后順序進行的排序丝蹭,那么問題來了秤掌,同一個時間范圍內(nèi)的消息汉操,如果它是需要立刻執(zhí)行的再来,那我們怎么辦,按照常規(guī)的辦法客情,我們需要等到隊列輪詢到我自己的時候才能執(zhí)行哦其弊,那豈不是黃花菜都涼了。所以膀斋,我們需要給緊急需要執(zhí)行的消息一個綠色通道梭伐,這個綠色通道就是同步屏障
的概念。
一仰担、 target 為何物
Message 類:
@UnsupportedAppUsage
/*package*/ Handler target;
從這里可以知道糊识,Message 是持有 Handler 的, 所謂的 target 即為 Handler 對象摔蓝。
我們可以通過 Handle 發(fā)送消息的時候(如調(diào)用Handler#sendMessage()
等 )赂苗,最終都是會調(diào)用 Handler#enqueueMessage()
讓消息入隊,最終找到target 贮尉。
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);
}
當我們發(fā)送一個消息的時候拌滋,msg.target 就會被賦值為this, 而 this 即為我們的 Handler 對象猜谚。因此败砂,通過這種方式傳進來的消息的 target
肯定也就不為 null赌渣,并且 mAsynchronous
默認為 false否灾,也就是說我們一般發(fā)送的消息都為同步消息
二线罕、 異步消息
設(shè)置異步消息有兩種方式:
一種是直接設(shè)置消息為異步的
Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);
還有一個需要用到 Handler 的一個構(gòu)造方法
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}
使用
Handler mMyHandler = new Handler(true);
Message msg = mHandler.obtainMessage();
mMyHandler.sendMessage(msg);
但需要注意的是,通過上面兩種方式來發(fā)送的消息還不是異步消息谷朝,因為它們最終還是會進入 enqueueMessage()斜姥,仍然會給 target 賦值 鸿竖,導致 target 不為null。
三铸敏、同步屏障
發(fā)送異步消息的關(guān)鍵就是要消息開啟一個同步屏障缚忧。屏障的意思即為阻礙,顧名思義杈笔,同步屏障就是阻礙同步消息搔谴,只讓異步消息通過。
MessageQueue#postSyncBarrier()
@UnsupportedAppUsage
@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) {
final int token = mNextBarrierToken++;
//從消息池中獲取Message
final Message msg = Message.obtain();
msg.markInUse();
//初始化Message對象的時候桩撮,并沒有給target賦值,因此 target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
//如果開啟同步屏障的時間(假設(shè)記為T)T不為0峰弹,且當前的同步消息里有時間小于T店量,則prev也不為null
prev = p;
p = p.next;
}
}
//根據(jù)prev是不是為null,將 msg 按照時間順序插入到 消息隊列(鏈表)的合適位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到鞠呈,Message 對象初始化的時候并沒有給 target 賦值融师,因此,target == null的 來源就找到了蚁吝。上面消息的插入也做了相應(yīng)的注釋旱爆。這樣,一條target == null 的消息就進入了消息隊列窘茁。
如果對消息機制有所了解的話怀伦,應(yīng)該知道消息的最終處理是在消息輪詢器Looper#loop()中,而loop()循環(huán)中會調(diào)用MessageQueue#next()從消息隊列中進行取消息山林。
@UnsupportedAppUsage
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 1.如果nextPollTimeoutMillis=-1房待,一直阻塞不會超時。
// 2.如果nextPollTimeoutMillis=0驼抹,不會阻塞桑孩,立即返回。
// 3.如果nextPollTimeoutMillis>0框冀,最長阻塞nextPollTimeoutMillis毫秒(超時)
// 如果期間有程序喚醒會立即返回流椒。
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) {
// 如果target==null,那么它就是屏障明也,需要循環(huán)遍歷宣虾,一直往后找到第一個異步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//確定是異步消息
}
if (msg != null) {
//如果有消息需要處理惯裕,先判斷時間有沒有到,如果沒到的話設(shè)置一下阻塞時間
//場景如常用的postDelay
if (now < msg.when) {
//計算出離執(zhí)行時間還有多久賦值給nextPollTimeoutMillis安岂,
//表示nativePollOnce方法要等待nextPollTimeoutMillis時長后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 獲取到消息
mBlocked = false;
//鏈表操作轻猖,獲取msg并且刪除該節(jié)點
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 {
// 沒有消息,nextPollTimeoutMillis復位
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
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);
}
////開始順序執(zhí)行所有IdleHandler
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(); //具體執(zhí)行
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//根據(jù)queueIdle()方法返回值決定是否移除該IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
從上面可以看出域那,當消息隊列開啟同步屏障的時候(即標識為 msg.target == null )咙边,消息機制在處理消息的時
候,優(yōu)先處理異步消息次员。這樣败许,同步屏障就起到了一種過濾和優(yōu)先級的作用。
下面用示意圖簡單說明:
如上圖所示淑蔚,在消息隊列中有同步消息和異步消息(黃色部分)以及一道墻----同步屏障(紅色部分)市殷。有了同步屏障的存在,msg_2 和 msg_M 這兩個異步消息可以被優(yōu)先處理刹衫,而后面的 msg_3 等同步消息則不會被處理醋寝。那么這些同步消息什么時候可以被處理呢?那就需要先移除這個同步屏障带迟,即調(diào)用removeSyncBarrier()
四音羞、同步屏障使用場景
似乎在日常的應(yīng)用開發(fā)中,很少會用到同步屏障仓犬。那么嗅绰,同步屏障在系統(tǒng)源碼中有哪些使用場景呢?Android 系統(tǒng)中的 UI 更新相關(guān)的消息即為異步消息搀继,需要優(yōu)先處理窘面。
比如,在 View 更新時叽躯,draw财边、requestLayout、invalidate 等很多地方都調(diào)用了
ViewRootImpl#scheduleTraversals()
如下:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//開啟同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//發(fā)送異步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
postCallback() 最終走到了 Choreographer#postCallbackDelayedInternal()
:
@UnsupportedAppUsage
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@UnsupportedAppUsage
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true); //異步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
這里就開啟了同步屏障点骑,并發(fā)送異步消息制圈,由于 UI 更新相關(guān)的消息是優(yōu)先級最高的,這樣系統(tǒng)就會優(yōu)先處理這些異步消息
最后畔况,當要移除同步屏障的時候需要調(diào)用 ViewRootImpl#unscheduleTraversals()
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
五鲸鹦、IdleHandler
很多人在Android項目中都會遇到希望一些操作延遲一點處理,一般會使用Handler.postDelayed(Runnable r, long delayMillis)來實現(xiàn)跷跪,但是又不知道該延遲多少時間比較合適馋嗜,因為手機性能不同,有的性能較差可能需要延遲較多吵瞻,有的性能較好可以允許較少的延遲時間葛菇。
之前在項目中對啟動過程進行優(yōu)化甘磨,用到了IdleHandler,它可以在主線程空閑時執(zhí)行任務(wù)眯停,而不影響其他任務(wù)的執(zhí)行济舆。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Handler handler = new Handler(Looper.myLooper());
//添加 IdleHandler
handler.getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//一些延遲一點處理的操做可以在此處理,
//當其他Handler消息分發(fā)完畢才會執(zhí)行此處的消息
return false;
}
});
//移除 IdleHandler
handler.getLooper().getQueue().removeIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
}
可以將上述代碼添加到Activity onCreate中莺债,在queueIdle()方法中實現(xiàn)延遲執(zhí)行任務(wù)滋觉,在主線程空閑,也就是activity創(chuàng)建完成之后齐邦,它會執(zhí)行queueIdle()方法中的代碼椎侠。
queueIdle()
返回true
表示可以反復執(zhí)行該方法,即執(zhí)行后還可以再次執(zhí)行措拇;返回false
表示執(zhí)行完該方法后會移除該IdleHandler我纪,即只執(zhí)行一次。
注意:在主線程中使用時queueIdle中不能執(zhí)行太耗時的任務(wù)丐吓。
六浅悉、小結(jié)
同步屏障的設(shè)置可以方便地處理那些優(yōu)先級較高的異步消息。當我們調(diào)用Handler.getLooper().getQueue().postSyncBarrier() 并設(shè)置消息的 setAsynchronous(true) 時券犁,target 即為 null 仇冯,也就開啟了同步屏障。當在消息輪詢器 Looper 在 loop() 中循環(huán)處理消息時族操,如若開啟了同步屏障,會優(yōu)先處理其中的異步消息比被,而阻礙同步消息色难。