首先,需要明確一點,Handler 延時消息機制不是延時發送消息,而是延時去處理消息;舉個例子,如下:
handler.postDelayed(() ->{
Log.e("zjt", "delay runnable");
}, 3_000);
上面的 Handler 不是延時3秒後再發送消息,而是將消息插入消息隊列後等3秒後再去處理。
postDelayed 的方法如下:
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
其中的 getPostMessage 就是將 post 的 runnable 包裝成 Message,如下:
private static Message getPostMessage(Runnable r) {
// 使用 Message.obtain() 避免重複創建實例對象,達到節約內存的目的
Message m = Message.obtain();
m.callback = r;
return m;
}
sendMessageDelayed 方法如下:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
// 延時的時間是手機的開機時間(不包括手機休眠時間)+ 需要延時的時間
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageAtTime 如下:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
這裡面的代碼很好理解,就不說了,看看 enqueueMessage:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this; // 設置 msg 的 target 為Handler
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 非同步消息,這個需要配合同步屏障來使用,可以看我之前的文章,這裡不贅述
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 插入到 MessageQueue 中
return queue.enqueueMessage(msg, uptimeMillis);
}
MessageQueue 的 enqueueMessage 的方法如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
// 判斷發送消息的進程是否還活著
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle(); // 回收消息到消息池
return false;
}
msg.markInUse(); // 標記消息正在使用
msg.when = when;
Message p = mMessages; // 獲取表頭消息
boolean needWake;
// 如果隊列中沒有消息 或者 消息為即時消息 或者 表頭消息時間大於當前消息的延時時間
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
// 表示要喚醒 Hander 對應的線程,這個後面解釋
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;
}
// 喚醒Handler對應的線程
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
舉個例子,假設我們消息隊列是空的,然後我發送一個延時10s的延時消息,那麼會直接把消息存入消息隊列。
從消息隊列中獲取消息是 通過 Looper.loop() 來調用 MessageQueue 的 next()方法,next()的主要代碼如下:
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 表示要休眠多長時間,功能類似於wait(time)
// -1表示一直休眠,
// 等於0時,不堵塞
// 當有新的消息來時,如果handler對應的線程是阻塞的,那麼會喚醒
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) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
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 {
// Got a message.
mBlocked = false;
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 {
// No more messages.
nextPollTimeoutMillis = -1;
}
.......
// 判斷是否有 idle 任務,即主線程空閑時需要執行的任務,這個下面說
if (pendingIdleHandlerCount <= 0) {
// 這裡表示所有到時間的消息都執行完了,剩下的如果有消息一定是延時且時間還沒到的消息;
// 剛上面的 enqueueMessage 就是根據這個變數來判斷是否要喚醒handler對應的線程
mBlocked = true;
continue;
}
......
}
}
其實,從這裡就可以看出來,Handler 的延時消息是如何實現的了。
比方說 發送一個延時10s的消息,那麼在 next()方法是,會阻塞 (10s + 發送消息時的系統開機時間 – 執行next()方法是系統的開機時間),到達阻塞時間時會喚醒。或者這時候有新的消息來了也會 根據 mBlocked = true來喚醒。
IdleHandler是什麼?
在 MessageQueue 類中有一個 static 的介面 IdleHanlder:
public static interface IdleHandler {
boolean queueIdle();
}
當MessageQueue中無可處理的Message時回調; 作用:UI線程處理完所有事務後,回調一些額外的操作,且不會堵塞主進程;
介面中只有一個 queueIdle() 函數,線程進入堵塞時執行的額外操作可以寫這裡, 返回值是true的話,執行完此方法後還會保留這個IdleHandler,否則刪除。
原創文章,作者:投稿專員,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/221071.html