Handler.postDelayed的原理
大部分同學在回答Handler的原理的時候都能回答出Handler將訊息丟到MessageQueue中,然後Looper.loop死迴圈不斷從MessageQueue中拿訊息去執行。
這塊我之前也有寫個文章介紹,如果忘了可以去看看。
但是如果再繼續追問Handler.postDelay又是怎麼做到的就講不出來了。這裡就給大家講一講。
原始碼解析
首先來看看handler裡面是怎麼處理postDelayed的:
public class Handler { ... public final boolean postDelayed(Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } ... public final boolean sendMessageDelayed(Message msg, long delayMillis) { ... return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); } ... public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; ... return enqueueMessage(queue, msg, uptimeMillis); } ... private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; ... return queue.enqueueMessage(msg, uptimeMillis); } ... }
可以發現最後它也是把Runnable封裝成Message然後發給MessageQueue去處理的,所以我們繼續看看MessageQueue.enqueueMessage方法:
boolean enqueueMessage(Message msg, long when) { ... synchronized (this) { ... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // 插入到佇列頭 msg.next = p; mMessages = msg; needWake = mBlocked; } else { // 按時間排序插入佇列 needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; //如果不是插入佇列頭的話不需要喚醒執行緒,讓它繼續等到拿佇列頭的訊息的時候再重新計算睡眠時間 } } msg.next = p; // invariant: p == prev.next prev.next = msg; } if (needWake) { // 喚醒執行緒 nativeWake(mPtr); } } return true; }
這個方法的作用其實很簡單,按時間順序把Message插入MessageQueue,形成一個按時間排序的單鏈表,然後喚醒執行緒。
然後看看喚醒了什麼執行緒?
我們都知道MessageQueue中的訊息是由Looper.loop裡面的一個死迴圈去讀取的。
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block ... } ... }
這個這裡還提示了MessageQueue.next方法也許會阻塞,所以我們看看next方法裡面幹了什麼:
Message next() { ... int nextPollTimeoutMillis = 0; for (;;) { ... //阻塞nextPollTimeoutMillis時間 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null && msg.target == null) { // 跳過佇列前面的無用Message do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { //如果時間沒有到,就計算需要等待的時間 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { //從佇列頭拿出Message mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } ... } ... } }
這裡面有個native方法nativePollOnce,阻塞執行緒一段固定的時間,當然MessageQueue.enqueueMessage裡面的nativeWake方法也能直接喚醒它。
當有Message插入佇列頭的時候,就會喚醒執行緒。然後MessageQueue.next方法就會拿出佇列頭的Message計算是否需要再等待一段時間去執行。
舉個例子
程式碼比較暈沒有關係,我們用一個簡單的例子把流程描述一下就好理解了。
首先假設佇列裡面有兩個訊息,分別在三秒、四秒之後執行,也就是說MessageQueue.next的執行緒會睡眠三秒之後才去訊息佇列拿佇列頭的訊息:

1.png
此時,我們又post了一個一秒之後執行的Message,於是它會被插入到佇列頭,然後MessageQueue.next的執行緒會被喚醒。但是拿到佇列頭的訊息發現時間還沒有到,於是又會再睡眠一秒:

2.png
等了一秒之後MessageQueue.next的執行緒自己甦醒拿出佇列頭的MessageC去分發,然後繼續拿MessageA。但是發現時間又沒有到,於是又會再睡眠兩秒:

3.png
這個時候如果我們插入了一個立馬執行的訊息呢?它也是會插入到佇列頭,然後喚醒MessageQueue.next的執行緒,去佇列頭取訊息執行。執行完之後又會拿MessageA。但是發現時間又沒有到,於是又會再睡眠兩秒。