Android消息機制完整的執行流程,瞭解一下
highlight: vs theme: devui-blue
持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第9天,點擊查看活動詳情
經過前面幾篇文章的鋪墊,介紹了
Hanlder
、Message
等類相關使用,分析了其與Looper
、MessageQueue
的部分源碼,本篇文章主要是集中梳理Android整個消息機制執行的完整流程。
從Handler.post()
説起
Handler.post()
是用來發送消息的,我們看下Handler
源碼的處理:
java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
首先會調用到getPostMessage()
方法將Runnable
封裝成一條Message
,然後緊接着調用sendMessageDelayed()
方法:
java
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
這裏我們介紹下sendMessageDelayed()
的第二個參數delayMillis
,這個表示消息延時執行的時間,而post()
方法本身代表着非延遲執行,所以這裏delayMillis
的值為0.
而如果是我們另一個常用的函數postDelay()
,這裏的delayMillis
的值就是傳入的延遲執行的時間
。
繼續往下走,會調用到Handler.sendMessageAtTime()
方法:
java
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
//...
return enqueueMessage(queue, msg, uptimeMillis);
}
獲取到Looper
對應的消息隊列MessageQueue
,繼續往下走,作為參數傳給enqueueMessage()
方法,這個方法主要是對上面封裝的Message
進行填充:
```java 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);
} ```
比如將Message
被負責分發的target
賦值成當前Handler
對象,然後根據是否為異步Handler
來決定是否給Message
添加異步標識。
MessageQueue.enqueueMessage()
添加消息至隊列中
java
boolean enqueueMessage(Message msg, long when) {
//...
synchronized (this) {
//...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//1.
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;
//2.
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
//3.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
這個方法的使用很明確,就是將Message
添加到消息隊列中,下來我們主要講解這個方法的三個核心點,對應上面的註釋標識:
- 如果當前消息隊列本來為null、消息執行的時間戳為0、消息執行的時間小於消息隊列隊頭消息的執行時間,只要滿足上面三個條件之一,直接將該條
Message
添加到消息隊列隊頭;
這裏説下消息執行的時間戳什麼時候會為0,就是調用
Handler.sendMessageAtFrontOfQueue()
這個方法,就會觸發將當前發送的Message
添加到消息隊列隊頭。
-
如果上面的三個條件都不滿足,就
遍歷消息隊列
,比較將要發送的消息和消息隊列的消息執行時間戳when
,選擇適當的位置插入; -
判斷是否需要喚醒當前主線程,開始從消息隊列獲取消息進行執行;
Looper.loop()
分發消息
這個方法會開啟一個for(;;)循環
,不斷的從消息隊列中獲取消息分發執行,沒有消息時會阻塞主線程進行休眠,讓出CPU執行權。
for(;;)循環
會不斷的調用Looper.loopOnce()
,開始真正的消息獲取和分發執行:
java
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
if (msg == null) {
return false;
}
try {
msg.target.dispatchMessage(msg);
}
msg.recycleUnchecked();
return true;
}
上面是經過簡化的代碼,首先調用MessageQueue.next()
從消息隊列中獲取消息,然後調用關鍵方法msg.target.dispatchMessage(msg)
開始消息的分發執行,這個方法之前的文章有進行介紹,這裏就不再過多介紹了。
接下來我們看下MessageQueue.next()
如何獲取消息的。
MessageQueue.next()
獲取消息
```java Message next() { //... for (;;) { //1.休眠主線程 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //2.獲取異步消息 if (msg != null && msg.target == null) { do { prevMsg = msg; msg = msg.next; } while (msg != null && !msg.isAsynchronous()); } //3.獲取普通消息 if (msg != null) { if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; msg.markInUse(); return msg; } } else { nextPollTimeoutMillis = -1; }
//...
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//4.執行Idle消息
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = idler.queueIdle();
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//...
}
} ```
-
如果
當前消息隊列中沒有消息或者還沒到下一條消息的執行時間
,就調用nativePollOnce()
方法休眠主線程,讓出CPU執行權; -
如果
Message
的target為null,就代表是一個消息屏障消息,之後就只能從消息隊列獲取異步消息了,如果不存在,就嘗試執行Idle
消息; -
如果不存在消息屏障,則就從消息隊列中正常嘗試獲取
Message
,如果不存在,就嘗試執行Idle
消息; -
執行
Idle
消息,只有在主線程空閒(當前消息隊列中沒有消息或者還沒到下一條消息的執行時間
)的情況下才會去嘗試執行Idle
消息,這種類型的消息非常有用,具體的可以參考我之前寫的文章:IdleHandler基本使用及應用案例分析
總結
本篇文章主要是詳細分析了Android消息機制的整個執行流程(不包括native層),最核心的就是Handler
、Looper
、MessageQueue
、Message
四個類及構成的關聯,希望能給你帶來幫助。
- kotlin密封sealed class/interface的迭代之旅
- 2022年12月12日—2022年12月25日Android精品文章一覽
- Sqlite簡易性能優化方案,給你的應用插上“翅膀”
- 築基篇:設置界面的開發利器Preference Library,瞭解一下~
- Android消息機制完整的執行流程,瞭解一下
- 你可能需要了解下的Android開發技巧(二)
- 超有用的Android開發技巧:攔截界面View創建
- Handler創建的幾個必備知識點,瞭解一下
- Android消息機制中Message常用的幾種監控方式
- 數見不鮮的RecyclerView使用技巧,你瞭解嗎(一)?
- 常用到的幾個Kotlin開發技巧,減少對業務層代碼的入侵
- 超好用的官方core-ktx庫,瞭解一下(終)~
- Take a look,從delay()方法看協程的掛起與恢復
- 超好用的官方core-ktx庫,瞭解一下~
- 官方core-ktx庫能對SparseArray系列、Pair開發帶來哪些便利?
- 官方core-ktx庫能對富文本Span開發帶來哪些便利2
- 你需要了解的官方core-ktx庫能對開發帶來哪些便利1
- LeakCanary如何監聽Fragment、Fragment View、ViewModel銷燬時機?
- 非反射動態設置TabLayout指示器的寬度
- 探究EventBus粘性事件實現機制