
我們經常提及 Android 中極為重要的執行緒間通信方式即
Handler機制,貌似 Handler 類發揮了很大的作用,
事實上當你了解它的原理之后,會發現 Handler 只是該機制的呼叫入口和回呼而已,最重要的東西是 Looper 和 MessagQueue,以及不斷流轉的 Message,
本次針對該機制常被問及的 18 個問題進行整理和回答,供大家解惑和回顧~
文章目錄
- 1. 簡述下 Handler 的總體原理?
- 2. Looper 存在哪?如何保證執行緒獨有?
- 3. Looper 快取在 ThreadLocal 的作用是?
- 4. 主執行緒的 Main Looper 和普通 Looper 的異同?
- 5. Handler 或 Looper 如何切換執行緒?
- 6. Looper 的 loop() 為什么不卡死?
- 7. Looper 等待的時候執行緒到底是什么狀態?
- 8. Looper 等待如何準確喚醒?
- 9. Message 如何獲取?為什么?
- 10. MessageQueue 如何管理 Message?
- 11. 理解 Message 和 MessageQueue 的異同?
- 12. Handler、Mesage 和 Runnable 的關系如何理解?
- 13. IdleHandler 了解過嗎?有什么用?
- 14. 異步 Message 或同步屏障了解過嗎?怎么用?什么原理?
- 15. Looper、MessageQueue、Message 及 Handler 的關系?
- 16. Native 側的 NativeMessageQueue 和 Looper 的作用是?
- 17. Native 側如何使用 Looper?
- 18. 正確理解 Handler 導致的記憶體泄露?
1. 簡述下 Handler 的總體原理?
- Looper#
prepare()初始化執行緒獨有的Looper以及MessageQueue - Looper#
loop()開啟死回圈讀取 MessageQueue 中下一個恰當 Message- 尚無 Message 的話,呼叫 Native 側的
pollOnce()進入無限等待 - Message 執行的
when條件未滿足的話,呼叫 pollOnce() 時傳入超時引數進入有限等待
- 尚無 Message 的話,呼叫 Native 側的
- 向持有 Looper 的
Handler發送 Message 或Runnable后 Message 將被插入到 Looper 持有的 MessageQueue 中合適的位置- MessageQueue 發現有合適的 Message 插入,呼叫 Native 側的
wake()喚醒執行緒,促使 MessageQueue 的讀取進入下一次回圈,因為此刻已有 Message 則出隊和處理 - Native 側有限等待后指執行緒將喚醒并繼續讀取 MessageQueue,因為時長條件將滿足則將其出隊處理
- MessageQueue 發現有合適的 Message 插入,呼叫 Native 側的
- 直接回呼 Message 的
callback屬性即 Runnable,或依據target屬性即 Handler 回呼handleMessage()
2. Looper 存在哪?如何保證執行緒獨有?
- Looper 實體被快取在靜態屬性
sThreadLocal中 ThreadLocal內部通過Map和弱參考的方式快取了每個執行緒獨有的 Looper,所以無論在哪個執行緒呼叫myLooper()都可以從 ThreadLocal 中獲取其對應的 Looper 實體
3. Looper 快取在 ThreadLocal 的作用是?
- 并非不是用來切換執行緒的,只是為了讓每個執行緒方便程獲取自己的 Looper 實體,見 Looper#
myLooper() - 后續可供 Handler 初始化時指定其所屬的 Looper 執行緒
- 或用來判斷是否是主執行緒
4. 主執行緒的 Main Looper 和普通 Looper 的異同?
-
區別:
1. Main Looper 不可
quit主執行緒需要不斷讀取系統訊息和用書輸入,是行程的入口,只可被系統直接終止,進而其 Looper 在創建的時候設定了不可
quit的標志,而其他執行緒的 Looper 則可以也必須手動 quit2. Main Looper 實體還被靜態快取
為了便于每個執行緒獲得主執行緒 Looper 實體,見 Looper#getMainLooper(),Main Looper 實體還作為
sMainLooper屬性快取到了 Looper 類中, -
相同點:
-
都是通過 Looper#prepare() 間接呼叫 Looper 建構式創建的實體
-
都快取到了靜態實體 ThreadLocal 中方便每個執行緒獲取自己的 Looper 實體
5. Handler 或 Looper 如何切換執行緒?
-
Handler 創建的時候指定了其所屬執行緒的 Looper,同時持有了 Looper 獨有的 MessageQueue
-
Looper的loop() 會持續讀取 MessageQueue 中合適的 Message,沒有 Message 的時候進入等待
-
當向 Handler 發送 Message 或 post Runnable 后,Handler 會向持有的 MessageQueue 中插入 Message
-
Message 抵達并滿足條件后會喚醒 MessageQueue 所屬的執行緒,并將 Message 回傳給 Looper
-
Looper 接著回呼 Message 所指向的 Handler Callback 或 Runnable,達到執行緒切換的目的
簡言之,向 Handler 發送 Message 其實是向 Handler 所屬的執行緒獨有 MessageQueue 插入 Message,而執行緒獨有的 Looper 又會持續讀取 MessageQueue 和喚醒,
所以發送完 Message 之后,將切換到其所屬的執行緒并運行,
6. Looper 的 loop() 為什么不卡死?
為了讓主執行緒持續處理用戶的輸入,loop() 是死回圈,并不斷呼叫 MessageQueue#next() 讀取合適的 Message,
但當沒有 Message 的時候,會呼叫 pollOnce() 并通過 Linux 的 epoll 機制進入休眠并釋放資源,同時 eventFd 會監聽 Message 抵達的寫入事件并進行喚醒,
這樣可以達到實時接收輸入,適時釋放資源且不卡死執行緒的目的,
7. Looper 等待的時候執行緒到底是什么狀態?
呼叫 Linux 的 epoll 機制進入等待,事實上 Java 執行緒處于 Runnable 狀態,
8. Looper 等待如何準確喚醒?
讀取合適 Message 的 MessageQueue#next() 會因為 Message 尚無或執行條件尚未滿足進行兩種等的等待:
-
無限等待
尚無 Message(佇列中沒有 Message 或建立了同步屏障但尚無異步 Message)的時候,呼叫 Natvie 側的 `pollOnce()V 會傳入引數 -1,
Linux 執行
epoll_wait()將進入無限等待,其等待合適的 Message 插入后呼叫 Native 側的wake()向喚醒 fd 寫入事件觸發喚醒 -
有限等待
有限等待的場合將下一個 Message 剩余時長作為引數交給 epoll_wait(),epoll 將等待一段時間之后自動回傳,接著回到 MessageQueue 讀取的下一次回圈
9. Message 如何獲取?為什么?
-
通過 Message 的靜態方法
obatin()獲取,因為該方法不是無腦地 new,而是從單鏈表池子里獲取實體,并在recycle()后將其放回池子 -
可以復用 Message 實體,滿足頻繁使用 Message 的場景,更加高效
注意:快取池存在上限 50,沒必要無限制地快取,本身也是一種浪費
10. MessageQueue 如何管理 Message?
- MessageQueue 通過單鏈表管理 Message,不同于行程復用的 Message Pool,其是執行緒獨有的
- 通過 Message 的執行時刻 when 對 Message 進行排隊和出隊
11. 理解 Message 和 MessageQueue 的異同?
-
相同點:
都是通過單鏈表來管理 Message 實體;
Message 通過 obtain() 和 recycle() 向單鏈表獲取插入節點,MessageQueue 通過 enqueueMessage() 和 next() 向單鏈表獲取和插入節點
-
區別:
Message 單鏈表是靜態的,供行程使用的快取池;
MessageQueue 單鏈表非靜態,只供 Looper 執行緒使用;
12. Handler、Mesage 和 Runnable 的關系如何理解?
-
作為使用 Handler 機制的入口,Handler 是發送 Message 或 Runnable 的起點
-
發送的 Runnable 本質上也是 Message,只不過作為
callback屬性被持有 -
Handler 持有 MesageQueue,最終 Message 實體都被插入到佇列中,等待調度
-
Handler 作為
target屬性被持有在 Mesage 中,在 Message 執行條件滿足的時候供 Looper 回呼
事實上,Handler 只是供 App 使用 Handler 機制的 API,實質來說,Message 是更為重要的載體,
13. IdleHandler 了解過嗎?有什么用?
-
適用于期望空閑時候執行,但不影響主執行緒操作的任務
-
系統應用:
- Activity
destroy回呼就放在了IdleHandler中 ActivityThread中GCHandler使用了 IdleHandler,在空閑的時候執行 GC 操作
- Activity
-
App 應用:
- 發送一個回傳 true 的 IdleHandler,在里面讓某個 View 不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個 View
- 將某部分初始化放在 IdleHandler 里不影響 Activity 的啟動,,,
-
關于 IdleHandler 的其他問題:
add/removeIdleHandler 的方法,是否需要成對使用?
不需要,回呼回傳 false 也可以移除
- 當
mIdleHanders一直不為空時,為什么不會進入死回圈?
執行過 IdleHandler 之后會將計數重置為 0,確保下一次回圈不重復執行
- 是否可以將一些不重要的啟動服務,搬移到 IdleHandler 中去處理?
最好不要,回呼時機不太可控,需要搭配
remove謹慎使用- IdleHandle 的
queueIdle()運行在那個執行緒?
取決于 IdleHandler add 到的 MessageQueue 所處的執行緒
14. 異步 Message 或同步屏障了解過嗎?怎么用?什么原理?
-
異步 Message 是設定了
isAsync屬性的 Message 實體,可以用異步 Handler 發送,也可以呼叫 Message#setAsynchronous()直接設定為異步 Message -
同步屏障指的是在 MessageQueue 的某個位置放一個沒有 target 屬性的 Message,確保此后的非異步 Message 無法執行,只能執行異步 Message
-
原理:
當 MessageQueue 輪循 Message 時候發現建立了同步屏障的時候,會去跳過其他 Message,讀取下個 async 的 Message 并執行,屏障移除之前同步 Message 都會被阻塞
-
應用:
比如螢屏重繪
Choreographer就使用到了同步屏障,確保螢屏重繪事件不會因為佇列負荷影響螢屏及時重繪, -
注意:
同步屏障的添加或移除 API 并未對外公開,App 需要使用的話需要依賴反射機制
15. Looper、MessageQueue、Message 及 Handler 的關系?
- Looper 負責輪循 MessageQueue,保持執行緒不結束
- MessagQueue 負責管理待處理 Message 的入隊和出隊
- Message 是承載任務的載體,由 MessageQueue 進行調度
- Handler 則是對外公開的 API,負責發送 Message 和處理任務的回呼
16. Native 側的 NativeMessageQueue 和 Looper 的作用是?
-
NativeMessageQueue 負責連接 Java 側的 MessageQueue,進行后續的
wait和wake,后續將創建 wake 的FD,并通過 epoll 機制等待或喚醒,但并不參與管理 Java 的 Message -
Native 側也需要使用 Looper 機制,等待和喚醒的需求是同樣的,所以將這部分實作都封裝到了 Looper.cpp 中,供 Java 和 Native 一起使用
17. Native 側如何使用 Looper?
-
Looper Native 部分承擔了 Java 側 Looper 的等待和喚醒,除此之外其還提供了 Message、
MessageHandler或WeakMessageHandler、LooperCallback或SimpleLooperCallback等 API -
這些部分可供 Looper 被 Native 側直接呼叫,比如
InputFlinger廣泛使用 -
主要方法是呼叫 Looper 建構式或 prepare 創建 Looper,然后通過 poll 開始輪詢,接著
sendMessage或addEventFd,等待 Looper 的喚醒,使用程序和 Java 的呼叫思路類似
18. 正確理解 Handler 導致的記憶體泄露?
- 持有 Activity 實體的內名內部類或內部類的生命周期應當和 Activity 保持一致
- 如果 Activity 本該銷毀了,但異步任務仍然活躍或通過 Handler 發送的 Message 尚未處理完畢,將使得內部類實體的生命周期被錯誤地延長
- 造成本該回收的 Activity 實體被別的
Thread或Main Looper占據而無法及時回收(活躍的 Thread 或 靜態屬性 sMainLooper 是 GC Root 物件) - 記得持有 Activity 盡量采用靜態內部類 + 弱參考的寫法,另外在 Activity 銷毀的時候及時地終止 Thread 或清空 Message(Message 清空后會執行 recycle(),內部將重置 target 等屬性,handler 就不再可達了)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/305723.html
標籤:其他
下一篇:m4s
