Android學習總結之handler源碼級

一、核心類關系與線程綁定(ThreadLocal 的核心作用)

1.?Looper 與 ThreadLocal 的綁定

每個線程的 Looper 實例通過?ThreadLocal<Looper> sThreadLocal?存儲,確保線程隔離:

public final class Looper {// 線程本地存儲,每個線程獨有一個 Looperstatic final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();// 主線程 Looper(ActivityThread 中創建)static Looper sMainLooper; final MessageQueue mQueue; // 關聯的消息隊列final Thread mThread; // 綁定的線程private Looper(boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed);mThread = Thread.currentThread(); // 記錄當前線程}// 線程首次調用,創建 Looper 并存儲到 ThreadLocalpublic static void prepare() {prepare(false); // quitAllowed 默認 false(主線程不允許退出)}private static void prepare(boolean quitAllowed) {if (sThreadLocal.get() != null) { // 禁止重復創建throw new RuntimeException("Only one Looper may be created per thread");}sThreadLocal.set(new Looper(quitAllowed)); // 存入當前線程的 Looper}// 獲取當前線程的 Looperpublic static @Nullable Looper myLooper() {return sThreadLocal.get();}
}
  • 主線程:Android 框架在?ActivityThread.main()?中自動調用?Looper.prepareMainLooper()?和?Looper.loop(),無需手動處理。
  • 子線程:必須手動調用?Looper.prepare()(創建 Looper 和 MessageQueue)和?Looper.loop()(啟動消息循環),否則 Handler 無可用 Looper 會報錯。
2.?Handler 的構造與 Looper 關聯

Handler 實例必須與一個 Looper 綁定,默認使用當前線程的 Looper(通過?Looper.myLooper()?獲取):

public class Handler {final Looper mLooper; // 關聯的 Looperfinal MessageQueue mQueue; // Looper 的消息隊列final Callback mCallback; // 消息回調public Handler() {this(null, false);}public Handler(@Nullable Callback callback, boolean async) {if (FIND_POTENTIAL_LEAKS) {final Class<? extends Handler> klass = getClass();if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&(klass.getModifiers() & Modifier.STATIC) == 0) {Log.w(TAG, "The following Handler class should be static or leaks might occur: "+ klass.getCanonicalName());}}mLooper = Looper.myLooper(); // 獲取當前線程的 Looper(必須已 prepare)if (mLooper == null) {throw new RuntimeException("Can't create handler inside thread " + Thread.currentThread()+ " that has not called Looper.prepare()");}mQueue = mLooper.mQueue; // 關聯消息隊列mCallback = callback;mAsynchronous = async;}
}
  • 若子線程未調用?Looper.prepare(),創建 Handler 時會拋出?RuntimeException,這就是子線程必須先準備 Looper 的原因。

二、消息發送:從 Handler 到 MessageQueue 的入隊

1.?Message 的創建與重用(消息池機制)

Message 優先從消息池獲取,避免頻繁 GC:

public final class Message implements Parcelable {// 消息池頭節點(靜態,所有線程共享)private static Message sPool;// 消息池大小(最大 50 個)private static int sPoolSize = 0;// 下一個可用消息(形成單鏈表)@UnsupportedAppUsageMessage next;// 從消息池獲取消息public static Message obtain() {synchronized (sPoolSync) { // 線程安全if (sPool != null) {Message m = sPool;sPool = m.next; // 取出頭節點m.next = null; // 斷開引用m.flags = 0; // 重置標志位sPoolSize--;return m;}}return new Message(); // 池空時新建}// 回收消息到池中(處理完后調用)void recycleUnchecked() {if (isInUse()) { // 確保未被使用if (gCheckRecycle) {throw new IllegalStateException("This message cannot be recycled because it "+ "is still in use.");}return;}recycle();}private void recycle() {if (mRecycled) { // 已回收過則不再處理return;}mRecycled = true;if (sPoolSize < MAX_POOL_SIZE) { // 最大 50 個next = sPool;sPool = this;sPoolSize++;}}
}

  • 優勢:減少對象創建開銷,提升性能,尤其適合高頻發送消息的場景。
2.?MessageQueue 的入隊邏輯(enqueueMessage)

消息按?msg.when(執行時間)排序插入,形成一個?非嚴格 FIFO 的有序單鏈表

boolean enqueueMessage(Message msg, long when) {msg.target = this; // target 指向發送消息的 Handlermsg.workSourceUid = ThreadLocalWorkSource.getUid();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;Message p = mMessages; // 當前頭節點boolean needWake;if (p == null || when == 0 || when < p.when) { // 新消息時間更早,插入頭部msg.next = p;mMessages = msg;needWake = mBlocked; // 當前隊列是否阻塞,決定是否喚醒} else { // 找到合適位置插入(按 when 升序)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;prev.next = msg;}// 若隊列阻塞且需要喚醒(如 Looper.loop() 在等待),通過 native 方法喚醒if (needWake) {nativeWake(mPtr);}}return true;
}

  • 關鍵點
    • synchronized (this)?確保多線程安全,不同線程的 Handler 發送消息到同一 MessageQueue 時不會沖突。
    • 消息按?when?排序,而非嚴格的 FIFO,支持延遲消息(如?postDelayed)。
    • 異步消息(msg.setAsynchronous(true))可打斷同步消息的等待,優先處理。

三、消息循環:Looper.loop () 的無限循環

1.?loop () 方法核心邏輯
public static void loop() {final Looper me = myLooper(); // 獲取當前線程的 Looperif (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");}final MessageQueue queue = me.mQueue; // 關聯的消息隊列// 確保主線程 Looper 不會被 GC 回收(Binder 機制相關)Binder.clearCallingIdentity();final long ident = Binder.clearCallingIdentity();for (;;) { // 無限循環,直到隊列退出Message msg = queue.next(); // 取出消息(可能阻塞)if (msg == null) { // 隊列退出(msg.next == null)return; // 退出循環}// 處理消息:通過 msg.target(Handler)分發msg.target.dispatchMessage(msg);// 回收消息到池中(非必須,系統自動處理)msg.recycleUnchecked();}
}
2.?MessageQueue.next () 的阻塞與喚醒

next()?是消息循環的核心,通過 native 層實現阻塞等待:

Message next() {final long ptr = mPtr; // 指向 native 層的 MessageQueue 實例int pendingIdleHandlerCount = -1; // 首次調用時初始化為 -1int nextPollTimeoutMillis = 0;for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();}// native 層阻塞等待消息,直到有消息或被喚醒nativePollOnce(ptr, nextPollTimeoutMillis);synchronized (this) {// 檢查是否有消息待處理final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages;if (msg != null && msg.when > now) { // 消息未到執行時間,計算延遲nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);} else { // 有可執行的消息,取出頭節點msg = mMessages;mMessages = msg.next;msg.next = null;if (DEBUG) Log.v(TAG, "Returning message: " + msg);nextPollTimeoutMillis = -1; // 下次立即 poll}if (msg != null) { // 有消息,返回給 Looperreturn msg;}// 無消息,檢查是否退出mQuitting = true;return null;}}
}
  • 阻塞原理:通過?nativePollOnce?進入內核等待,當消息入隊時(enqueueMessage?中調用?nativeWake)被喚醒。
  • 退出條件:調用?Looper.quit()?或?Looper.quitSafely()?時,mQuitting?設為 true,next()?返回 null,loop()?終止。

四、消息處理:Handler.dispatchMessage 的分發邏輯

消息最終由發送它的 Handler 處理,分發流程如下:

public void dispatchMessage(Message msg) {if (msg.callback != null) { // 優先處理 Message 的 Runnable(post(Runnable))handleCallback(msg);} else if (mCallback != null) { // 其次處理 Handler 的 Callbackif (mCallback.handleMessage(msg)) {return;}}handleMessage(msg); // 最后調用用戶重寫的 handleMessage()
}private static void handleCallback(Message message) {message.callback.run(); // 執行 post(Runnable) 傳入的 Runnable
}
  • 三種處理方式
    1. Message 自帶的 Runnable:通過?post(Runnable)?發送的消息,直接執行?Runnable.run()
    2. Handler 的 Callback:通過構造函數傳入的?Callback,優先級高于?handleMessage
    3. 用戶重寫的 handleMessage:最常用的消息處理邏輯。

五、子線程使用 Handler 的完整流程(源碼級示例)

// 子線程類
class WorkerThread extends Thread {private Handler mHandler;private Looper mLooper;// 獲取 Handler(供外部發送消息)public Handler getHandler() {return mHandler;}@Overridepublic void run() {// 1. 準備 Looper(創建 Looper 和 MessageQueue)Looper.prepare();synchronized (this) {mLooper = Looper.myLooper(); // 保存當前線程的 LoopermHandler = new Handler() { // 創建 Handler 關聯當前 Looper@Overridepublic void handleMessage(Message msg) {// 處理消息(子線程中執行)processMessage(msg);if (msg.what == MSG_QUIT) { // 退出消息mLooper.quit(); // 停止消息循環}}};notify(); // 通知主線程 Handler 已準備好}// 2. 啟動消息循環Looper.loop();}
}// 主線程使用子線程 Handler
public class MainActivity extends AppCompatActivity {private WorkerThread mWorkerThread;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);mWorkerThread = new WorkerThread();mWorkerThread.start();synchronized (mWorkerThread) {try {mWorkerThread.wait(); // 等待子線程準備好 Handler} catch (InterruptedException e) {e.printStackTrace();}}// 向子線程發送消息Message msg = Message.obtain();msg.what = WorkerThread.MSG_WORK;mWorkerThread.getHandler().sendMessage(msg);// 發送退出消息mWorkerThread.getHandler().sendMessage(Message.obtain(null, WorkerThread.MSG_QUIT));}
}
  • 關鍵步驟
    1. 子線程中調用?Looper.prepare()?創建 Looper 和 MessageQueue。
    2. 創建 Handler 時自動關聯當前 Looper(因在?prepare()?之后調用)。
    3. 調用?Looper.loop()?啟動消息循環,處理外部發送的消息。
    4. 通過?Looper.quit()?終止循環,避免子線程阻塞。

六、常見問題源碼級解析

1.?為什么主線程可以直接創建 Handler?
  • 主線程(ActivityThread 所在線程)在啟動時,框架自動調用了:
    public static void main(String[] args) {// ...Looper.prepareMainLooper(); // 準備主線程 LooperActivityThread thread = new ActivityThread();thread.attach(false, startSeq);Looper.loop(); // 啟動主線程消息循環
    }
    

    因此主線程的 Looper 已存在,無需手動調用?prepare()
2.?MessageQueue 真的是 “隊列” 嗎?
  • 從數據結構看,它是一個?單鏈表,而非傳統的 FIFO 隊列。消息按?msg.when?排序插入,保證按時間順序執行,支持延遲消息。
3.?postDelayed 不準時的根本原因?
  • postDelayed?計算延遲的基準時間是?SystemClock.uptimeMillis()(系統啟動后非休眠時間),若設備休眠,休眠時間不計入延遲,導致實際執行時間晚于預期。
  • 此外,消息需等待前面的消息處理完成,若主線程被阻塞(如耗時操作),延遲消息會被阻塞。
4.?多線程發送消息到同一 MessageQueue 為何線程安全?
  • MessageQueue.enqueueMessage?使用?synchronized (this)?加鎖,確保同一時間只有一個線程操作隊列,避免并發問題。

七、Handler 機制的設計精髓

  1. 線程隔離:通過 ThreadLocal 保證每個線程獨有 Looper 和 MessageQueue,避免資源競爭。
  2. 消息池:重用 Message 對象,減少內存分配和 GC 壓力,提升性能。
  3. 有序調度:按時間排序的消息鏈表,支持延遲和異步消息,靈活控制執行順序。
  4. 阻塞與喚醒:通過 native 層實現高效的等待與喚醒,避免 CPU 空轉。

總結

? ? ?Handler 機制的核心是通過?Looper(消息循環)、MessageQueue(有序消息鏈表)、Handler(消息收發器)?的協作,實現線程間的安全通信。源碼中大量使用 ThreadLocal、synchronized、單鏈表等技術,確保線程隔離、數據安全和性能優化。深入理解這些細節,能幫助開發者更好地處理線程通信、避免內存泄漏(如非靜態內部類 Handler 導致的 Activity 泄漏),并在復雜場景中靈活運用 Handler 機制。

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

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

相關文章

群體智能優化算法-算術優化算法(Arithmetic Optimization Algorithm, AOA,含Matlab源代碼)

摘要 算術優化算法&#xff08;Arithmetic Optimization Algorithm, AOA&#xff09;是一種新穎的群體智能優化算法&#xff0c;靈感來源于加、減、乘、除四種基本算術運算。在優化過程中&#xff0c;AOA 通過乘除操作實現全局探索&#xff0c;通過加減操作強化局部開發&#…

廣告推薦算法:COSMO算法與A9算法的對比

COSMO算法與A9算法的概念解析 1. A9算法 定義與背景&#xff1a; A9算法是亞馬遜早期為電商平臺研發的核心搜索算法&#xff0c;主要用于優化商品搜索結果的排序和推薦&#xff0c;其核心邏輯圍繞產品屬性與關鍵詞匹配展開。自2003年推出以來&#xff0c;A9通過分析商品標題…

EasyExcel 數據字典轉換器實戰:注解驅動設計

一、場景痛點與解決方案 1. 問題背景 在 Excel 導入導出場景中&#xff0c;開發者常面臨以下問題&#xff1a; 數據可讀性差&#xff1a;數據庫存儲的字典值&#xff08;如 1、true&#xff09;直接導出時難以理解雙向轉換復雜&#xff1a;導入時需將用戶輸入的標簽反向解析…

五種音頻器件綜合對比——《器件手冊--音頻器件》

目錄 音頻器件 簡述 1. 揚聲器&#xff08;Speakers&#xff09; 2. 麥克風&#xff08;Microphones&#xff09; 3. 放大器&#xff08;Amplifiers&#xff09; 4. 音頻接口&#xff08;Audio Interfaces&#xff09; 5. 音頻處理器&#xff08;Audio Processors&#xff09…

紅寶書第二十九講:詳解編輯器和IDE:VS Code與WebStorm

紅寶書第二十九講&#xff1a;詳解編輯器和IDE&#xff1a;VS Code與WebStorm 資料取自《JavaScript高級程序設計&#xff08;第5版&#xff09;》。 查看總目錄&#xff1a;紅寶書學習大綱 一、核心區別&#xff1a;編輯器與IDE 代碼編輯器&#xff08;如VS Code&#xff09…

虛擬電商-話費充值業務(五)充值成功邏輯和網絡異常重試邏輯

一、網絡異常重試邏輯編寫 如果在對接供應商的過程中出現了網絡異常&#xff0c;我們需要做一個補償機制&#xff0c;在任務類型枚舉類&#xff1a;TaskTypeEnum中有一種業務狀態碼是針對遠程調用失敗的 步驟一&#xff1a;在對接供應商的方法&#xff1a;SupplierServiceImp…

從零構建大語言模型全棧開發指南:第四部分:工程實踐與部署-4.3.3低代碼開發:快速構建行業應用(電商推薦與金融風控案例)

?? 點擊關注不迷路 ?? 點擊關注不迷路 ?? 點擊關注不迷路 文章大綱 從零構建大語言模型全棧開發指南-第四部分:工程實踐與部署4.3.3 低代碼開發:快速構建行業應用(電商推薦與金融風控案例)1. 低代碼與AI結合的核心價值2. 電商推薦系統案例2.1 技術架構與實現2.2 性能…

Table as Thought論文精讀

標題&#xff1a;Table as Thought: Exploring Structured Thoughts in LLM Reasoning 作者&#xff1a;Zhenjie Sun, Naihao Deng, Haofei Yu, Jiaxuan You 單位&#xff1a;University of Illinois Urbana-Champaign, University of Michigan 摘要&#xff1a; llm的推理…

ubuntu18 server版花屏問題

新搞了一臺dellT150的塔式服務器&#xff0c;裝的ubuntu18 server版。 開機后遇到花屏&#xff0c;或者卡在開機界面的問題&#xff0c;和售后技術溝通這個情況是ubuntu自帶的顯卡驅動包兼容問題。需要做如下設置&#xff1a; 解決&#xff1a; 1.開機&#xff0c;連續按下e…

【MySQL】理解MySQL的雙重緩沖機制:Buffer Pool與Redo Log的協同之道

在數據庫系統中&#xff0c;內存與磁盤的讀寫性能差距始終是需要解決的核心問題。當注意到Redo Log和Buffer Pool都采用"先寫內存再刷盤"的設計時&#xff0c;一個自然的問題浮現&#xff1a;既然兩者都需要維護內存數據并定期持久化&#xff0c;為何需要雙重緩沖機制…

PMP考試改革解讀:新題型+5A通關秘籍

2024年&#xff0c;項目管理協會&#xff08;PMI&#xff09;對PMP考試進行了重大調整&#xff0c;從考試形式、題型分布到知識領域均進行了優化升級。本文結合PMI官方公告與一線教研經驗&#xff0c;深度解析改革要點&#xff0c;并提供針對性通關策略&#xff0c;助你高效沖刺…

【Django】教程-10-ajax請求Demo,結合使用

【Django】教程-1-安裝創建項目目錄結構介紹 【Django】教程-2-前端-目錄結構介紹 【Django】教程-3-數據庫相關介紹 【Django】教程-4-一個增刪改查的Demo 【Django】教程-5-ModelForm增刪改查規則校驗【正則鉤子函數】 【Django】教程-6-搜索框-條件查詢前后端 【Django】教程…

RabbitMQ高級特性1

RabbitMQ高級特性1 一.消息確認1.消息確認機制2.手動確認代碼肯定確認否定確認1否定確認2Spring中的代碼 二.持久性1.交換機持久化2.隊列的持久化3.消息的持久化非持久化代碼實現三方面都持久化&#xff0c;數據也會丟失 三.發送方確認1.Confirm確認模式2.return返回模式 四.總…

Java網絡編程NIO

一、NIO是什么? NIO可以說是比BIO更強大的IO&#xff0c;可以設置非阻塞模式&#xff08;通過事件的方式監聽數據的到來&#xff09; BIO是基于socket通信&#xff0c;一個線程對應一個socket連接&#xff0c;讀取數據要一直等待 NIO是基于channel通信&#xff0c;一個線程管…

【動態規劃】二分優化最長上升子序列

最長上升子序列 II 題解 題目傳送門&#xff1a;AcWing 896. 最長上升子序列 II 一、題目描述 給定一個長度為 N 的數列&#xff0c;求數值嚴格單調遞增的子序列的長度最長是多少。 輸入格式&#xff1a; 第一行包含整數 N第二行包含 N 個整數&#xff0c;表示完整序列 輸…

Dify接口api對接,流式接收流式返回(.net)

試了好多種方法除了Console.WriteLine()能打印出來&#xff0c;試了好些方法都不行&#xff0c;不是報錯就是打印只有一行&#xff0c;要么就是接收完才返回...下面代碼實現調用api接收流式數據&#xff0c;并進行流式返回給前端&#xff1a; using Furion.HttpRemote; using …

19-元素顯示模式及浮動(CSS3)

知識目標 掌握標準文檔流的解析規則掌握元素的顯示模式掌握元素浮動屬性語法與使用掌握浮動塌陷解決方法 1. 標準文檔流 2. 元素顯示模式 元素顯示模式就是元素&#xff08;標簽&#xff09;以什么方式進行顯示&#xff0c;比如<div>獨占一行&#xff0c;一行可以放多…

HTML jQuery 項目 PDF 批注插件庫在線版 API 示例教程

本文章介紹 HTML && jQuery Web項目中 PDF 批注插件庫 ElasticPDF 在線版 API 示例教程&#xff0c;API 包含 ① 導出批注后PDF數據&#xff1b;② 導出純批注 json 數據&#xff1b;③ 加載舊批注&#xff1b;④ 切換文檔&#xff1b;⑤ 切換用戶&#xff1b;⑥ 清空批…

CATIA裝配體全自動存儲解決方案開發實戰——基于遞歸算法的產品結構樹批量處理技術

一、功能定位與技術架構 本工具針對CATIA V5裝配體文件管理場景&#xff0c;實現了一套全自動遞歸存儲系統&#xff0c;主要功能包括&#xff1a; ?智能路徑選擇&#xff1a;通過Tkinter目錄對話框實現可視化路徑選擇?產品結構遞歸解析&#xff1a;深度優先遍歷裝配體中的子…

C#:接口(interface)

目錄 接口的核心是什么&#xff1f; 1. 什么是接口&#xff08;Interface&#xff09;&#xff0c;為什么要用它&#xff1f; 2. 如何定義和使用接口&#xff1f; 3.什么是引用接口&#xff1f; 如何“引用接口”&#xff1f; “引用接口”的關鍵點 4. 接口與抽象類的區…