[TOC]
## MessageQueue
MessageQueue中最重要的就是兩個方法:
1.enqueueMessage向隊列中插入消息
2.next 從隊列中取出消息
MessageQueue的底層數據結構是單向鏈表,MessageQueue中的成員變量mMessages指向的就是該鏈表的頭部元素。
### enqueueMessage
先分析enqueueMessage:
~~~
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//msg.target就是發送此消息的Handler
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;//將延遲時間封裝到msg內部
Message p = mMessages;//消息隊列的第一個元素
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//如果此隊列中頭部元素是null(空的隊列,一般是第一次),或者此消息不是延時的消息,則此消息需要被立即處理,此時會將這個消息作為新的頭部元素,并將此消息的next指向舊的頭部元素,然后判斷如果Looper獲取消息的線程如果是阻塞狀態則喚醒它,讓它立刻去拿消息處理
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//如果此消息是延時的消息,則將其添加到隊列中,原理就是鏈表的添加新元素,按照when,也就是延遲的時間來插入的,延遲的時間越長,越靠后,這樣就得到一條有序的延時消息鏈表,取出消息的時候,延遲時間越小的,就被先獲取了。插入延時消息不需要喚醒Looper線程。
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {//喚醒線程
nativeWake(mPtr);
}
}
return true;
}
~~~
源碼中主要的地方我給了注釋,可以參考參考。
由此可以看出:
MessageQueue中enqueueMessage方法的目的有兩個:
1.插入消息到消息隊列
2.喚醒Looper中等待的線程(如果是及時消息并且線程是阻塞狀態)
同時我們知道了MessageQueue的底層數據結構是單向鏈表,MessageQueue中的成員變量mMessages指向的就是該鏈表的頭部元素
### next
~~~java
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
//從注釋可以看出,只有looper被放棄的時候(調用了quit方法)才返回null,mPtr是MessageQueue的一個long型成員變量,關聯的是一個在C++層的MessageQueue,阻塞操作就是通過底層的這個MessageQueue來操作的;當隊列被放棄的時候其變為0。
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞方法,主要是通過native層的epoll監聽文件描述符的寫入事件來實現的。
//如果nextPollTimeoutMillis=-1,一直阻塞不會超時。
//如果nextPollTimeoutMillis=0,不會阻塞,立即返回。
//如果nextPollTimeoutMillis>0,最長阻塞nextPollTimeoutMillis毫秒(超時),如果期間有程序喚醒會立即返回。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//msg.target == null表示此消息為消息屏障(通過postSyncBarrier方法發送來的)
//如果發現了一個消息屏障,會循環找出第一個異步消息(如果有異步消息的話),所有同步消息都將忽略(平常發送的一般都是同步消息)
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 如果消息此刻還沒有到時間,設置一下阻塞時間nextPollTimeoutMillis,進入下次循環的時候會調用nativePollOnce(ptr, nextPollTimeoutMillis)進行阻塞;
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//正常取出消息
//設置mBlocked = false代表目前沒有阻塞
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
//沒有消息,會一直阻塞,直到被喚醒
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
~~~
由此可以看出:
1.當首次進入或所有消息隊列已經處理完成,由于此刻隊列中沒有消息(mMessages為null),這時nextPollTimeoutMillis = -1 ,然后會處理一些不緊急的任務(IdleHandler),之后線程會一直阻塞,直到被主動喚醒(插入消息后根據消息類型決定是否需要喚醒)。
2.讀取列表中的消息,如果發現消息屏障,則跳過后面的同步消息。
3.如果拿到的消息還沒有到時間,則重新賦值nextPollTimeoutMillis = 延時的時間,線程會阻塞,直到時間到后自動喚醒
4.如果消息是及時消息或延時消息的時間到了,則會返回此消息給looper處理。
通過enqueueMessage和next兩個方法的分析我們不難得出:
消息的入列和出列是一個生產-消費者模式,Looper.loop()在一個線程中調用next()不斷的取出消息,另外一個線程則通過enqueueMessage向隊列中插入消息,所以在這兩個方法中使用了synchronized (this) {}同步機制,其中this為MessageQueue對象,不管在哪個線程,這個對象都是同一個,因為Handler中的mQueue指向的是Looper中的mQueue,這樣防止了多個線程對同一個隊列的同時操作。
### Handler.sendMessageDelayed()怎么實現延遲的?
前面我們分析了如果拿到的消息還沒有到時間,則會重新設置超時時間并賦值給nextPollTimeoutMillis,然后調用nativePollOnce(ptr, nextPollTimeoutMillis)進行阻塞,這是一個本地方法,會調用底層C++代碼,C++代碼最終會通過Linux的epoll監聽文件描述符的寫入事件來實現延遲的。
### 為什么這個死循環不會造成ANR異常呢?
我們知道Android 的是由事件驅動的,looper.loop() 不斷地接收事件、處理事件,每一個點擊觸摸或者說Activity的生命周期都是運行在 Looper的控制之下,如果它停止了,應用也就停止了。只能是某一個消息或者說對消息的處理阻塞了 Looper.loop(),而不是 Looper.loop() 阻塞它,這也就是我們為什么不能在UI線程中處理耗時操作的原因。
主線程Looper從消息隊列讀取消息,當讀完所有消息時,主線程阻塞。子線程往消息隊列發送消息,喚醒主線程,主線程被喚醒只是為了讀取消息,當消息讀取完畢,再次睡眠。因此loop的循環并不會對CPU性能有過多的消耗。
## 參考資料
[從源碼角度分析java層Handler機制](https://blog.csdn.net/andywuchuanlong/article/details/48160127)
[從源碼角度分析native層消息機制與java層消息機制的關聯](https://blog.csdn.net/andywuchuanlong/article/details/48179165)
[深入理解MessageQueue](https://www.jianshu.com/p/8c829dc15950)
- Android
- 四大組件
- Activity
- Fragment
- Service
- 序列化
- Handler
- Hander介紹
- MessageQueue詳細
- 啟動流程
- 系統啟動流程
- 應用啟動流程
- Activity啟動流程
- View
- view繪制
- view事件傳遞
- choreographer
- LayoutInflater
- UI渲染概念
- Binder
- Binder原理
- Binder最大數據
- Binder小結
- Android組件
- ListView原理
- RecyclerView原理
- SharePreferences
- AsyncTask
- Sqlite
- SQLCipher加密
- 遷移與修復
- Sqlite內核
- Sqlite優化v2
- sqlite索引
- sqlite之wal
- sqlite之鎖機制
- 網絡
- 基礎
- TCP
- HTTP
- HTTP1.1
- HTTP2.0
- HTTPS
- HTTP3.0
- HTTP進化圖
- HTTP小結
- 實踐
- 網絡優化
- Json
- ProtoBuffer
- 斷點續傳
- 性能
- 卡頓
- 卡頓監控
- ANR
- ANR監控
- 內存
- 內存問題與優化
- 圖片內存優化
- 線下內存監控
- 線上內存監控
- 啟動優化
- 死鎖監控
- 崩潰監控
- 包體積優化
- UI渲染優化
- UI常規優化
- I/O監控
- 電量監控
- 第三方框架
- 網絡框架
- Volley
- Okhttp
- 網絡框架n問
- OkHttp原理N問
- 設計模式
- EventBus
- Rxjava
- 圖片
- ImageWoker
- Gilde的優化
- APT
- 依賴注入
- APT
- ARouter
- ButterKnife
- MMKV
- Jetpack
- 協程
- MVI
- Startup
- DataBinder
- 黑科技
- hook
- 運行期Java-hook技術
- 編譯期hook
- ASM
- Transform增量編譯
- 運行期Native-hook技術
- 熱修復
- 插件化
- AAB
- Shadow
- 虛擬機
- 其他
- UI自動化
- JavaParser
- Android Line
- 編譯
- 疑難雜癥
- Android11滑動異常
- 方案
- 工業化
- 模塊化
- 隱私合規
- 動態化
- 項目管理
- 業務啟動優化
- 業務架構設計
- 性能優化case
- 性能優化-排查思路
- 性能優化-現有方案
- 登錄
- 搜索
- C++
- NDK入門
- 跨平臺
- H5
- Flutter
- Flutter 性能優化
- 數據跨平臺