主頁 > 移動端開發 > Handler 機制的終極 18 問,都了解了嗎?建議收藏~

Handler 機制的終極 18 問,都了解了嗎?建議收藏~

2021-10-06 08:47:43 移動端開發

在這里插入圖片描述

我們經常提及 Android 中極為重要的執行緒間通信方式即 Handler 機制,貌似 Handler 類發揮了很大的作用,

事實上當你了解它的原理之后,會發現 Handler 只是該機制的呼叫入口和回呼而已,最重要的東西是 LooperMessagQueue,以及不斷流轉的 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 的總體原理?

  1. Looper#prepare() 初始化執行緒獨有的 Looper 以及 MessageQueue
  2. Looper#loop() 開啟死回圈讀取 MessageQueue 中下一個恰當 Message
    • 尚無 Message 的話,呼叫 Native 側的 pollOnce() 進入無限等待
    • Message 執行的 when 條件未滿足的話,呼叫 pollOnce() 時傳入超時引數進入有限等待
  3. 向持有 Looper 的 Handler 發送 Message 或 Runnable 后 Message 將被插入到 Looper 持有的 MessageQueue 中合適的位置
    • MessageQueue 發現有合適的 Message 插入,呼叫 Native 側的 wake() 喚醒執行緒,促使 MessageQueue 的讀取進入下一次回圈,因為此刻已有 Message 則出隊和處理
    • Native 側有限等待后指執行緒將喚醒并繼續讀取 MessageQueue,因為時長條件將滿足則將其出隊處理
  4. 直接回呼 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 則可以也必須手動 quit

    2. Main Looper 實體還被靜態快取

    為了便于每個執行緒獲得主執行緒 Looper 實體,見 Looper#getMainLooper(),Main Looper 實體還作為 sMainLooper 屬性快取到了 Looper 類中,

  • 相同點:

  1. 都是通過 Looper#prepare() 間接呼叫 Looper 建構式創建的實體

  2. 都快取到了靜態實體 ThreadLocal 中方便每個執行緒獲取自己的 Looper 實體

5. Handler 或 Looper 如何切換執行緒?

  1. Handler 創建的時候指定了其所屬執行緒的 Looper,同時持有了 Looper 獨有的 MessageQueue

  2. Looper的loop() 會持續讀取 MessageQueue 中合適的 Message,沒有 Message 的時候進入等待

  3. 當向 Handler 發送 Message 或 post Runnable 后,Handler 會向持有的 MessageQueue 中插入 Message

  4. Message 抵達并滿足條件后會喚醒 MessageQueue 所屬的執行緒,并將 Message 回傳給 Looper

  5. 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 了解過嗎?有什么用?

  • 適用于期望空閑時候執行,但不影響主執行緒操作的任務

  • 系統應用:

    1. Activity destroy 回呼就放在了 IdleHandler
    2. ActivityThreadGCHandler 使用了 IdleHandler,在空閑的時候執行 GC 操作
  • App 應用:

    1. 發送一個回傳 true 的 IdleHandler,在里面讓某個 View 不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個 View
    2. 將某部分初始化放在 IdleHandler 里不影響 Activity 的啟動,,,
  • 關于 IdleHandler 的其他問題:

    1. add/remove IdleHandler 的方法,是否需要成對使用?

    不需要,回呼回傳 false 也可以移除

    1. mIdleHanders 一直不為空時,為什么不會進入死回圈?

    執行過 IdleHandler 之后會將計數重置為 0,確保下一次回圈不重復執行

    1. 是否可以將一些不重要的啟動服務,搬移到 IdleHandler 中去處理?

    最好不要,回呼時機不太可控,需要搭配 remove 謹慎使用

    1. 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,進行后續的 waitwake,后續將創建 wake 的FD,并通過 epoll 機制等待或喚醒,但并不參與管理 Java 的 Message

  • Native 側也需要使用 Looper 機制,等待和喚醒的需求是同樣的,所以將這部分實作都封裝到了 Looper.cpp 中,供 Java 和 Native 一起使用

17. Native 側如何使用 Looper?

  • Looper Native 部分承擔了 Java 側 Looper 的等待和喚醒,除此之外其還提供了 Message、MessageHandlerWeakMessageHandlerLooperCallbackSimpleLooperCallback 等 API

  • 這些部分可供 Looper 被 Native 側直接呼叫,比如 InputFlinger 廣泛使用

  • 主要方法是呼叫 Looper 建構式或 prepare 創建 Looper,然后通過 poll 開始輪詢,接著 sendMessageaddEventFd,等待 Looper 的喚醒,使用程序和 Java 的呼叫思路類似

18. 正確理解 Handler 導致的記憶體泄露?

  • 持有 Activity 實體的內名內部類或內部類的生命周期應當和 Activity 保持一致
  • 如果 Activity 本該銷毀了,但異步任務仍然活躍或通過 Handler 發送的 Message 尚未處理完畢,將使得內部類實體的生命周期被錯誤地延長
  • 造成本該回收的 Activity 實體被別的 ThreadMain Looper 占據而無法及時回收(活躍的 Thread 或 靜態屬性 sMainLooper 是 GC Root 物件)
  • 記得持有 Activity 盡量采用靜態內部類 + 弱參考的寫法,另外在 Activity 銷毀的時候及時地終止 Thread 或清空 Message(Message 清空后會執行 recycle(),內部將重置 target 等屬性,handler 就不再可達了)

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/305723.html

標籤:其他

上一篇:第二十一章_你真的了解游戲設計缺陷嗎?

下一篇:m4s

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more