詳解Android消息機制完整的執(zhí)行流程
從Handler.post()說起
Handler.post()是用來發(fā)送消息的,我們看下Handler源碼的處理:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
首先會調(diào)用到getPostMessage()方法將Runnable封裝成一條Message,然后緊接著調(diào)用sendMessageDelayed()方法:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
這里我們介紹下sendMessageDelayed()的第二個參數(shù)delayMillis,這個表示消息延時執(zhí)行的時間,而post()方法本身代表著非延遲執(zhí)行,所以這里delayMillis的值為0.
而如果是我們另一個常用的函數(shù)postDelay(),這里的delayMillis的值就是傳入的延遲執(zhí)行的時間。
繼續(xù)往下走,會調(diào)用到Handler.sendMessageAtTime()方法:
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
//...
return enqueueMessage(queue, msg, uptimeMillis);
}
獲取到Looper對應的消息隊列MessageQueue,繼續(xù)往下走,作為參數(shù)傳給enqueueMessage()方法,這個方法主要是對上面封裝的Message進行填充:
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被負責分發(fā)的target賦值成當前Handler對象,然后根據(jù)是否為異步Handler來決定是否給Message添加異步標識。
MessageQueue.enqueueMessage()添加消息至隊列中
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添加到消息隊列中,下來我們主要講解這個方法的三個核心點,對應上面的注釋標識:
1.如果當前消息隊列本來為null、消息執(zhí)行的時間戳為0、消息執(zhí)行的時間小于消息隊列隊頭消息的執(zhí)行時間,只要滿足上面三個條件之一,直接將該條Message添加到消息隊列隊頭;
這里說下消息執(zhí)行的時間戳什么時候會為0,就是調(diào)用Handler.sendMessageAtFrontOfQueue()這個方法,就會觸發(fā)將當前發(fā)送的Message添加到消息隊列隊頭。
2.如果上面的三個條件都不滿足,就遍歷消息隊列,比較將要發(fā)送的消息和消息隊列的消息執(zhí)行時間戳when,選擇適當?shù)奈恢貌迦耄?/p>
3.判斷是否需要喚醒當前主線程,開始從消息隊列獲取消息進行執(zhí)行;
Looper.loop()分發(fā)消息
這個方法會開啟一個for(;;)循環(huán),不斷的從消息隊列中獲取消息分發(fā)執(zhí)行,沒有消息時會阻塞主線程進行休眠,讓出CPU執(zhí)行權(quán)。
for(;;)循環(huán)會不斷的調(diào)用Looper.loopOnce(),開始真正的消息獲取和分發(fā)執(zhí)行:
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;
}
上面是經(jīng)過簡化的代碼,首先調(diào)用MessageQueue.next()從消息隊列中獲取消息,然后調(diào)用關(guān)鍵方法msg.target.dispatchMessage(msg)開始消息的分發(fā)執(zhí)行,這個方法之前的文章有進行介紹,這里就不再過多介紹了。
接下來我們看下MessageQueue.next()如何獲取消息的。
MessageQueue.next()獲取消息
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.執(zhí)行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);
}
}
}
//...
}
}
- 如果當前消息隊列中沒有消息或者還沒到下一條消息的執(zhí)行時間,就調(diào)用
nativePollOnce()方法休眠主線程,讓出CPU執(zhí)行權(quán); - 如果
Message的target為null,就代表是一個消息屏障消息,之后就只能從消息隊列獲取異步消息了,如果不存在,就嘗試執(zhí)行Idle消息; - 如果不存在消息屏障,則就從消息隊列中正常嘗試獲取
Message,如果不存在,就嘗試執(zhí)行Idle消息; - 執(zhí)行
Idle消息,只有在主線程空閑(當前消息隊列中沒有消息或者還沒到下一條消息的執(zhí)行時間)的情況下才會去嘗試執(zhí)行Idle消息,這種類型的消息非常有用,具體的可以參考我之前寫的文章:IdleHandler基本使用及應用案例分析
總結(jié)
本篇文章主要是詳細分析了Android消息機制的整個執(zhí)行流程(不包括native層),最核心的就是Handler、Looper、MessageQueue、Message四個類及構(gòu)成的關(guān)聯(lián)。
到此這篇關(guān)于詳解Android消息機制完整的執(zhí)行流程的文章就介紹到這了,更多相關(guān)Android消息機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android 手機SD卡讀寫操作(以txt文本為例)實現(xiàn)步驟
要完成SD卡讀寫操作首先對manifest注冊SD卡讀寫權(quán)限其次是創(chuàng)建一個對SD卡中文件讀寫的類寫一個用于檢測讀寫功能的的布局然后就是UI的類了,感興趣的朋友可以參考下,希望可以幫助到你2013-02-02
Android控件BottomSheet實現(xiàn)底邊彈出選擇列表
這篇文章主要介紹了Android控件BottomSheet實現(xiàn)底邊彈出選擇列表,比較常用的選擇條件或跳轉(zhuǎn)方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-08-08
Android開發(fā)教程之ContentProvider數(shù)據(jù)存儲
這篇文章主要介紹了Android開發(fā)教程之ContentProvider數(shù)據(jù)存儲的相關(guān)資料,需要的朋友可以參考下2016-12-12
Android App中的GridView網(wǎng)格布局使用指南
GridView布局所實現(xiàn)的就是類似于九宮格的矩陣界面效果,下面整理了Android App中的GridView網(wǎng)格布局使用指南,包括分割線的添加與自定義GridView的實現(xiàn)等技巧,需要的朋友可以參考下2016-06-06
詳解Android studio實現(xiàn)語音轉(zhuǎn)文字功能
這篇文章主要介紹了如何通過Android studio調(diào)用科大訊飛的語音轉(zhuǎn)文字功能,文中的示例代碼講解詳細,感興趣的小伙伴可以了解一下2022-03-03

