Android HandlerThread、Looper、MessageQueue 源碼分析

Android HandlerThread、Looper、MessageQueue 源碼分析

簡介

Android 開發中,大家應該對 HandlerThread 有一定了解。顧名思義,HandlerThreadThread 的一個子類。與普通的 Thread 不同,Thread 通常一次只能執行一個后臺任務,如果需要執行多個任務,就必須創建多個線程,這樣容易導致資源管理復雜,甚至可能出現內存泄漏等問題。而 HandlerThread 有一個顯著的特點,它能夠串行地執行多個任務。每個任務通過消息的形式排隊執行,線程池中的任務按順序依次處理,無需手動管理線程的生命周期或調度過程。我們只需通過 Handler 將任務發送到 HandlerThread 中,它會自動按順序執行,極大簡化了開發過程。使用 HandlerThread 的最大優勢是:它是單線程的,因此不需要擔心線程安全問題。實際上,Android 源碼中也有許多地方使用了 HandlerThread,它在設計思想上值得我們學習和借鑒。接下來,我們將通過源碼進一步了解它的實現原理。

源碼分析

HandlerThread內部持有一個Looper,Looper中持有一個消息隊列MessageQueue

Looper

在了解HandlerThread前,我們有必要先認識一下Looper,Looper 是一個負責管理消息隊列(MessageQueue)并循環處理其中消息的類。簡單來說,Looper 通過不斷地從消息隊列中取出消息并處理它們,來實現線程的消息處理機制。每個線程都有自己的消息隊列和 Looper,通過 Looper,線程能夠不斷地處理任務直到任務隊列為空。接下來我們會涉及到它以下幾個核心接口。

  • prepare() 是一個靜態方法,用來構建一個Looper對象,quitAllowed表示這個Looper是否支持退出,默認是支持,像Android 主線程的Looper是不支持退出的
    public static void prepare() {prepare(true);}
  • loopOnce 靜態方法,顧名思義,循環一次,它的作用是從當前消息隊列MessageQueue中取一條符合條件的消息進行分發操作
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // 取一條消息if (msg == null) {//正常沒消息,隊列會堵塞,不會返回null,所以這里是null肯定是已經要結束了,這里返回false 退出looperreturn false;}//分發消息代碼邏輯省略}
  • loop() 靜態方法,啟動循環,在循環中不斷調用loopOnce來處理消息
//關鍵代碼public static void loop() {final Looper me = myLooper();
//...for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {//返回false 意味著循環結束return;}}}
  • quit()quitSafely() 退出looper,前者是立即退出,后者是處理完當前隊列中所有消息后退出,最終是調用消息隊列MessageQueue對應的退出方法
    public void quit() {mQueue.quit(false);
}public void quitSafely() {mQueue.quit(true);
}

HandlerThread

  • 既然它是一個線程,那可以先從run方法入手:
@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}

可以看出,線程跑起來后,先初始化了一個Looper,然后啟動死循環,HandlerThread在這里充當的作用是在子線程中開啟死循環接受和分發消息
這里有個地方比較有意思,在mLooper = Looper.myLooper();后面,它調用了notifyAll(),它起到了什么作用呢?

  • getLooper(),我們應該知道要把任務提交給HandlerThread執行,需要借助Handler,但是Handler的構造參數是需要傳入一個Looper對象,所以,這里對外公開了獲取Looper的接口
public Looper getLooper() {if (!isAlive()) {return null;}boolean wasInterrupted = false;// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {wasInterrupted = true;}}}/** We may need to restore the thread's interrupted flag, because it may* have been cleared above since we eat InterruptedExceptions*/if (wasInterrupted) {Thread.currentThread().interrupt();}return mLooper;}

這里是直接返回了在run中初始化的mLooper,但是呢,在非當前線程獲取mLooper對象,就會引發線程安全問題,可能mLooper還沒被初始化就調用了getLooper(),這樣就有可能返回一個空的數據了,所以官方在這里做了while循環,并且使用了wait()堵塞,等待上面run初始化完成后再notifyAll()這里

  • getThreadHandler() 就是公開給外面向當前HandlerThread插入消息的接口,內部維護著一個Handler對象
    @NonNullpublic Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());}return mHandler;}
  • quit()quitSafely() 同樣,HandlerThread也有退出的方法,其實現也是調用looper對應的函數退出

MessageQueue

由于關于消息分發邏輯在其他地方講過,這里只要分析退出隊列的邏輯。
它實現了退出和安全退出的方法,這兩個操作有什么區別呢,請看源碼

  • next() looper每調用一次loopOnce,內部就會調用messageQueue獲取一條消息
Message next() {//只放關鍵代碼for (; ; ) {//堵塞,等待消息或者時機合適或主動喚醒nativePollOnce(ptr, nextPollTimeoutMillis);//...//符合分發條件的 返回這條消息給looperif (msg != null) {//...return msg;} else {// No more messages.nextPollTimeoutMillis = -1;}//...//退出狀態 則釋放隊列,結束循環if (mQuitting) {dispose();return null;}}
}
  • quit()實際邏輯由下面兩個removeAllFutureMessagesLocked和removeAllMessagesLocked函數執行
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {//如果已經給隊列設置了退出信號,下面的邏輯就不用走了return;}mQuitting = true;//標記當前隊列處于退出狀態,此時不再接受新的消息if (safe) {//安全退出removeAllFutureMessagesLocked();} else {//立即退出removeAllMessagesLocked();}//把隊列的消息標記完成后,喚醒上面next的堵塞位置nativePollOnce,執行剩下的退出邏輯nativeWake(mPtr);}
}
//移除所有消息
private void removeAllMessagesLocked() {//mMessages 在這里是隊列的頭Message p = mMessages;while (p != null) {//從頭開始,把所有消息標記為回收狀態Message n = p.next;p.recycleUnchecked();p = n;}mMessages = null;//隊頭消息置空,next()執行時會判斷這個,為空直接退出隊列
}//移除所有未來消息,退出這一瞬間之前提交的消息保留繼續執行
private void removeAllFutureMessagesLocked() {final long now = SystemClock.uptimeMillis();Message p = mMessages;if (p != null) {if (p.when > now) {//隊頭的時間比當前新,說明全是后面新加的,全部回收掉removeAllMessagesLocked();} else {//以下為從消息隊列中找到退出那一瞬間時間一樣的分割點,把分割點前的消息與隊列斷開鏈接Message n;for (;;) {n = p.next;if (n == null) {return;}if (n.when > now) {break;}p = n;}p.next = null;//斷開鏈接,這里把隊列從分割點切斷do {p = n;n = p.next;p.recycleUnchecked();//把所有未來消息標記為回收狀態} while (n != null);}}
}

從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續被looper取,一直到取完為止,然后結束隊列退出循環,而普通退出就會把隊列中所有消息移除,然后緊接著結束隊列退出循環

quit和quitSafely 應用場景有哪些呢

;//把所有未來消息標記為回收狀態
} while (n != null);
}
}
}


從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續被looper取,一直到取完為止,然后結束隊列退出循環,而普通退出就會把隊列中所有消息移除,然后緊接著結束隊列退出循環### quit和quitSafely 應用場景有哪些呢
舉一個簡單的例子,在Android 使用Room操作數據庫,由于操作數據庫需要在子線程,所以,我們可以構造一個`HandlerThread`,專門處理操作數據庫的任務,如果操作過程非常耗時,然后又要關閉數據庫,

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/63574.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/63574.shtml
英文地址,請注明出處:http://en.pswp.cn/web/63574.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

配置PostgreSQL用于集成測試的步驟

在進行軟件開發時,集成測試是確保各個組件能夠協同工作的關鍵環節。PostgreSQL作為一種強大的開源數據庫系統,常被用于集成測試中。下面將詳細介紹如何在不同的環境中配置PostgreSQL以支持集成測試。 1. 選擇并安裝PostgreSQL 首先,你需要根…

WebRTC搭建與應用(一)-ICE服務搭建

WebRTC搭建與應用(一) 近期由于項目需要在研究前端WebGL渲染轉為云渲染,借此機會對WebRTC、ICE信令協議等有了初步了解,在此記錄一下,以防遺忘。 第一章 ICE服務搭建 文章目錄 WebRTC搭建與應用(一)前言一、ICE是什么?二、什么…

【學習筆記】深入淺出詳解Pytorch中的View, reshape, unfold,flatten等方法。

文章目錄 一、寫在前面二、Reshape(一)用法(二)代碼展示 三、Unfold(一)torch.unfold 的基本概念(二)torch.unfold 的工作原理(三) 示例代碼(四&a…

深入理解 MySQL 索引

引言 在數據庫管理中,索引(Index)是提高查詢性能的關鍵技術之一。MySQL 是最流行的關系型數據庫管理系統之一,廣泛應用于各種規模的應用程序中。本文將深入探討 MySQL 中的索引概念、類型、工作原理以及最佳實踐,幫助…

利用notepad++刪除特定關鍵字所在的行

1、按組合鍵Ctrl H,查找模式選擇 ‘正則表達式’,不選 ‘.匹配新行’ 2、查找目標輸入 : ^.*關鍵字.*\r\n (不保留空行) ^.*關鍵字.*$ (保留空行)3、替換為:(空) 配置界面參考下圖: ??…

docker安裝和換源

安裝: https://www.runoob.com/docker/ubuntu-docker-install.html sudo apt-get remove docker docker-engine docker.io containerd runcsudo apt-get install \apt-transport-https \ca-certificates \curl \gnupg-agent \software-properties-commoncurl -fsS…

CSSmodule的作用是什么

CSS Modules的作用主要體現在以下幾個方面: 1. 解決全局樣式污染問題 在傳統的CSS管理方式中,樣式定義通常是全局的,這很容易導致全局樣式污染。當多個組件或頁面共享同一個樣式時,可能會出現樣式沖突和覆蓋的情況,從…

創建第一個QML項目

文章目錄 使用 Qt Creator 創建 Qt Quick 項目詳解為什么選擇 Qt Creator?1. 打開 Qt Creator2. 選擇項目模板3. 設置項目名稱與路徑4. 定義項目細節5. 配置構建套件6. 檢查項目配置7. 編譯并運行項目后續操作修改界面添加功能 總結 使用 Qt Creator 創建 Qt Quick …

【k8s集群應用】K8S二進制安裝大致步驟(簡略版)

文章目錄 K8S二進制安裝部署etcd測試etcd集群(可選)恢復etcd數據庫 部署master組件部署node組件K8S kubeadm安裝關鍵命令更新kubeadm安裝的K8S證書有效期方法一方法二查看證書有效期 K8S二進制安裝 部署etcd 使用cfssl工具簽發證書和私鑰下載解壓etcd軟…

瑞吉外賣項目學習筆記(二)Swagger、logback、表單校驗和參數打印功能的實現

瑞吉外賣項目學習筆記(一)準備工作、員工登錄功能實現 文章目錄 3 項目組件優化3.1 實現Swagger文檔輸出3.2 實現logback日志打印3.3 實現表單校驗功能3.4 實現請求參數和響應參數的打印 3 項目組件優化 3.1 實現Swagger文檔輸出 1)在application.yml中增加knife4…

leetcode刷題-回溯算法04

代碼隨想錄回溯算法part01| 491.遞增子序列、46.全排列、47.全排列II 491.遞增子序列46.全排列47.全排列II 491.遞增子序列 leetcode題目鏈接 代碼隨想錄文檔講解 思路: 與上一題不同,不能用used列表,因為這個題不能排序, 在每一…

基于字節大模型的論文翻譯(含免費源碼)

基于字節大模型的論文翻譯 源代碼: 👏 star ? https://github.com/boots-coder/LLM-application 展示 項目簡介 本項目是一個基于大語言模型(Large Language Model, LLM)的論文閱讀與翻譯輔助工具。它通過用戶界面&#xff08…

mysql的事務控制和數據庫的備份和恢復

事務控制語句 行鎖和死鎖 行鎖 兩個客戶端同時對同一索引行進行操作 客戶端1正常運行 客戶端2想修改,被鎖行 除非將事務提交才能繼續運行 死鎖 客戶端1刪除第5行 客戶端2設置第1行為排他鎖 客戶端1刪除行1被鎖 客戶端2更新行5被鎖 如何避免死鎖 mysql的備份和還…

Tengine:Nginx二次開發-高性能進化

前言:在當今的互聯網時代,Web 服務器的性能和穩定性對于網站的成功至關重要。Nginx 以其高性能和可擴展性而聞名,但有時候,我們需要更多的特性來滿足特定的業務需求。Tengine,作為一個由淘寶網發起的 Nginx 二次開發版…

RK3588, FFmpeg 拉流 RTSP, mpp 硬解碼轉RGB

RK3588 ,基于FFmpeg, 拉取RTSP,使用 mpp 實現硬解碼. ?? 傳送 ?? Ubuntu x64 架構, 交叉編譯aarch64 FFmpeg mppRK3588, FFmpeg 拉流 RTSP, mpp 硬解碼轉RGBRk3588 FFmpeg 拉流 RTSP, 硬解碼轉RGBRK3588 , mpp硬編碼yuv, 保存MP4視頻文件.

Windows 下 Anaconda的安裝與配置 GPU 版

給之前的電腦安一下深度學習環境 判斷是否有NVIDIA GPU Ctrl Shift Esc 打開任務管理器 帶此字眼表示有 NVIDIA GPU 安裝Anaconda anaconda 打開郵箱會看到下載鏈接 這里建議修改為其他盤,要不然下載的包和創建的環境都在C盤,占用空間 三個都打鉤 取…

【openssl】 version `OPENSSL_3.0.3‘ not found 問題

【openssl】 version OPENSSL_3.0.3 not found 問題 使用openssl時候報錯: openssl lib/libcrypto.so.3: version OPENSSL_3.0.3 not found查閱CSDN發現有博主說把別的地方的libcrypto.so.3 復制過去就好了。 嘗試無效 警告!這個操作不對: 不…

flask flask-socketio創建一個網頁聊天應用

應用所需環境: python 3.11.11 其他 只需要通過這個命令即可 pip install flask3.1.0 Flask-SocketIO5.4.1 -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple 最好是用conda創建一個新的虛擬環境來驗證 完整的pip list如下 Package Version ----…

聯邦學習防止數據泄露

文章目錄 聯邦學習防止數據泄露的原理聯邦學習的優勢聯邦學習與集中式學習的成本分析聯邦學習的實際應用案例個人設想參考文獻 聯邦學習 (Federated Learning) 是一種分布式機器學習技術,旨在解決數據隱私保護問題。它允許在分散的數據源上進行模型訓練,…

STM32 水質水位檢測項目(硬件架構)及(軟件架構)

硬件選型 水位測量模塊 TDS采集模塊 外置ADC模塊(ADS1115) 水位測量模塊使用方法 水位測量原理 壓力傳感器:水越深壓力越大 P ρgh Fps Fρgh*s P大氣壓 水位測量傳感器本質上是一個壓力測量傳感器。壓力的值和傳感器產生的電壓值是線…