Android中消息隊列延時不準確的原因,從系統時鐘的精度到MessageQueue的機制
SystemClock系統時鐘
SystemClock類是一個用于獲取時間信息的核心類,為Android開發者提供了多種與時間相關的功能。
- 「類聲明和結構」:
SystemClock是一個final類,繼承自Object,不能被繼承。
不可變類,提供了與時間相關的多種方法。
- 「主要方法」:
SystemClock.uptimeMillis():
返回從系統啟動開始到現在的非休眠時間的毫秒數。
當系統進入深度睡眠(CPU休眠、屏幕休眠、設備等待外部輸入)時,該時間停止。
SystemClock.elapsedRealtime() 和 SystemClock.elapsedRealtimeNanos():
返回從系統啟動到現在的總時間,包括深度睡眠的時間。
兩個方法返回的時間都是單調的,即使在省電模式下也會繼續計時。
SystemClock.sleep(long):
類似于Thread.sleep(long),忽略了InterruptedException異常。
SystemClock.setCurrentTimeMillis(long):
「注意」:通常用于設置系統時間,需要系統權限。
將系統時間設置為指定的毫秒數,通常只在系統源碼環境下使用,并且需要特定的編譯和簽名過程。
- 「其他注意事項」:
監聽時間變化:
通過監聽ACTION_TIME_TICK、ACTION_TIME_CHANGED和ACTION_TIMEZONE_CHANGED等廣播,可以獲取系統時間是否發生改變。
- 「權限要求」:
修改系統時間(如使用SystemClock.setCurrentTimeMillis(long))需要系統權限,應用需要在Android系統源碼環境下進行編譯和簽名。
Hander延時消息延時會不準確
Hanler中發送消息(或者延時消息) 是通過SystemClock.uptimeMillis()為基準計算。
MessageQueue入隊列時,計算Message.when以SystemClock.uptimeMillis()時間為基準。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
從MessageQueue中消費Message,判斷Message是否到了執行時間,也是以SystemClock.uptimeMillis()為基準。
//Looper.loop()循環
public static void loop() {
for (;;) {
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);
}
}
//MessageQueue類的next方法
//msg.when > now時 才會將Message取出然后執行。
Message next() {
for (;;) {
...
synchronized (this) {
// (1)計算當前時間
final long now = SystemClock.uptimeMillis();
// 返回取到的Message
if (msg != null) {
//msg尚未到達觸發時間,則計算新的阻塞超時時間nextPollTimeoutMillis,下次循環觸發隊列阻塞
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
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;
msg.markInUse();
return msg;
}
}
...
}
}
SystemClock.uptimeMillis()計算系統從開機到現在的時間,單位是毫秒。但是它不包括系統休眠的時間(cpu休眠、屏幕休眠等)。
當手機滅屏處于休眠狀態的時間是不計算進SystemClock.uptimeMillis(),比如發送一個延時20分鐘的Message消息,系統息屏后進入了深度睡眠(假設深度睡眠了1個小時),當進程蘇醒后,這一個小時的時間是不計入(1)中的now。
final long now = SystemClock.uptimeMillis();
從系統時鐘看已經過去1個小時了,但是計算now時因為uptimeMillis不包含休眠時的時間。如果now < msg.when 會判定Messsage還沒有到執行時間,就不會從MessageQueue中取出并執行。