Android Handler機制與底層原理詳解

Android 的 Handler 機制是跨線程通信異步消息處理的核心框架,它構成了 Android 應用響應性和事件驅動模型的基礎(如 UI 更新、后臺任務協調)。其核心思想是 “消息隊列 + 循環處理”

核心組件及其關系

  1. Handler (處理器):

    • 角色: 消息的發送者處理者
    • 功能:
      • 發送消息:MessageRunnable 對象放入與其關聯的 MessageQueue 中。方法包括 sendMessage(), post(), sendMessageDelayed(), postDelayed() 等。
      • 處理消息: 實現 handleMessage(Message msg) 方法,定義當消息從隊列中被取出時如何執行相應的操作。對于 post(Runnable r),則是在關聯線程中執行 r.run()
  2. Message (消息):

    • 角色: 通信的載體
    • 內容: 包含需要傳遞的數據 (what, arg1, arg2, obj, data Bundle) 和目標 Handler (target)。
    • 優化: 通常通過 Message.obtain()Handler.obtainMessage() 從對象池中獲取,避免頻繁創建對象引起 GC。
  3. MessageQueue (消息隊列):

    • 角色: 一個按執行時間排序(主要是 when 字段)的優先級隊列
    • 功能:
      • 存儲消息: 接收 Handler 發送來的 Message,并根據 when (絕對時間戳) 插入到隊列的合適位置。
      • 取出消息: 由關聯的 Looper 循環調用 next() 方法取出下一個待處理的消息。如果隊列為空或下一個消息的執行時間未到,next() 會阻塞。
    • 關鍵特性: 單線程 操作(每個 Looper 線程有且只有一個 MessageQueue)。
  4. Looper (循環器):

    • 角色: 消息循環的引擎。負責驅動整個消息處理流程。
    • 功能:
      • 準備循環: 通過 Looper.prepare() 為當前線程創建一個 Looper(及其關聯的 MessageQueue)。
      • 啟動循環: 調用 Looper.loop() 啟動一個無限循環。在這個循環中:
        1. 調用 MessageQueue.next() 獲取下一條消息(可能阻塞)。
        2. 如果取到 null(通常表示調用了 quit),退出循環。
        3. 否則,調用 msg.target.dispatchMessage(msg),將消息分發給發送它的 Handler 處理。
      • 結束循環: 調用 Looper.quit()(立即退出)或 Looper.quitSafely()(處理完已有消息后退出)。
    • 關鍵特性:
      • 單線程 操作(每個 Looper 線程有且只有一個 Looper)。
      • 主線程 (ActivityThread) 的 Looper 在應用啟動時由系統自動創建并啟動 (Looper.prepareMainLooper(), Looper.loop())。
      • 其他線程需要手動調用 prepare()loop() 來創建和使用 Looper(如 HandlerThread)。
  5. ThreadLocal (線程局部存儲):

    • 角色: 確保每個線程訪問到它自己獨有的 LooperMessageQueue 實例。
    • 原理: Looper 內部使用 ThreadLocal 存儲當前線程的 Looper 對象。myLooper()getMainLooper() 都依賴于它。

工作流程詳解

  1. 初始化 (通常在主線程或自定義線程):

    • 主線程: 系統自動完成 Looper.prepareMainLooper()Looper.loop()
    • 自定義線程:
      class WorkerThread extends Thread {public Handler mHandler;public void run() {Looper.prepare(); // 1. 為當前線程創建 Looper 和 MessageQueuemHandler = new Handler() { // 2. 創建 Handler,自動綁定到當前線程的 Looper@Overridepublic void handleMessage(Message msg) {// 處理來自其他線程的消息}};Looper.loop(); // 3. 啟動消息循環 (阻塞在此處)}
      }
      
  2. 發送消息 (例如從后臺線程發往主線程更新 UI):

    // 假設在主線程初始化時保存了主線程 Handler: mainHandler
    new Thread(new Runnable() {@Overridepublic void run() {// ... 后臺工作 ...Message msg = mainHandler.obtainMessage(MSG_UPDATE_UI, data);mainHandler.sendMessage(msg); // 或 mainHandler.post(updateUiRunnable)// 消息被放入主線程的 MessageQueue}
    }).start();
    
  3. 消息循環處理 (Looper.loop()):

    • 在擁有 Looper 的線程中,loop() 方法無限循環:
      public static void loop() {final Looper me = myLooper(); // 獲取當前線程的 Looperfinal MessageQueue queue = me.mQueue; // 獲取關聯的 MessageQueuefor (;;) {Message msg = queue.next(); // 1. 取消息 (可能阻塞)if (msg == null) { // 2. 收到退出信號 (null),退出循環return;}try {msg.target.dispatchMessage(msg); // 3. 分發消息給 Handler 處理} finally {msg.recycleUnchecked(); // 4. 回收消息到對象池}}
      }
      
  4. 消息分發 (Handler.dispatchMessage(Message msg)):

    public void dispatchMessage(Message msg) {if (msg.callback != null) { // 1. 優先處理 Message 自帶的 Runnable (post(Runnable r))handleCallback(msg);} else {if (mCallback != null) { // 2. 其次處理 Handler 構造時傳入的 Callbackif (mCallback.handleMessage(msg)) {return;}}handleMessage(msg); // 3. 最后調用 Handler 子類實現的 handleMessage()}
    }
    

底層原理與關鍵技術點

  1. MessageQueue.next() 的阻塞與喚醒 (Linux epoll):

    • 問題: 當隊列為空或下一個消息的執行時間 (when) 還沒到時,next() 需要高效地阻塞線程,避免無意義的 CPU 空轉。
    • 解決方案: 利用 Linux 的 epoll 系統調用 實現高效的 I/O 多路復用等待。
      • MessageQueue 內部有一個 native 層的對象 (mPtr 指向 NativeMessageQueue),它封裝了一個 epoll 實例 (mEpollFd)。
      • 當需要阻塞時 (next() 發現沒有即時消息),會調用 nativePollOnce(ptr, timeoutMillis)。這個 native 方法最終會進入 epoll_wait() 系統調用,讓線程在指定的文件描述符 (event fd) 上等待。
      • 喚醒機制:
        • 當有新消息加入隊列(特別是插入到隊列頭部時)或設置了新的屏障時,會調用 nativeWake(ptr)
        • nativeWake() 會向 event fd 寫入數據
        • 這個寫操作會喚醒正在 epoll_wait() 上阻塞的線程,使其返回并繼續處理消息隊列。
      • 延遲消息: 如果阻塞是因為等待延遲消息 (when > now),timeoutMillis 會被設置為剩余等待時間。epoll_wait() 會在超時或喚醒時返回。
  2. ThreadLocal 保證線程隔離:

    • Looper 類內部有一個 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
    • Looper.prepare() 調用 sThreadLocal.set(new Looper(quitAllowed))
    • Looper.myLooper() 調用 sThreadLocal.get()
    • 這確保了每個線程調用 myLooper() 得到的都是自己線程創建的 Looper 對象,從而每個線程都有自己獨立的 MessageQueue 和消息循環。
  3. 主線程的特殊性:

    • ActivityThreadmain() 方法是 Android 應用的入口點。
    • main() 中,系統會調用 Looper.prepareMainLooper() (內部也是 prepare(false)) 創建主線程 Looper,并將其標記為“主 Looper”(set()sMainLooper 并標記 mIsMain)。
    • 然后調用 Looper.loop() 啟動主線程的消息循環。所有 UI 事件(觸摸、繪制、生命周期回調)以及通過主線程 Handler 發送的消息,都在這個循環中被處理。 這就是為什么在非 UI 線程直接更新 UI 會報錯 - UI 操作必須在擁有 View 樹的線程(通常是主線程)執行。
  4. 屏障消息 (SyncBarrier):

    • 目的: 允許異步消息優先于同步消息執行。
    • 實現:
      • 調用 MessageQueue.postSyncBarrier() 會插入一個 targetnull 的特殊 Message (屏障)。
      • next() 方法中,如果遇到屏障,它會跳過所有后續的同步消息 (msg.isAsynchronous() == false),只查找異步消息 (msg.isAsynchronous() == true) 或新的屏障。
      • 當需要移除屏障時,調用 MessageQueue.removeSyncBarrier(token)
    • 應用場景: View 系統在請求布局 (requestLayout()) 和繪制 (draw()) 時使用屏障,確保 UI 渲染相關的異步消息(如 VSYNC 信號觸發的繪制)能優先執行,避免被耗時的同步消息阻塞造成卡頓。
  5. 異步消息 (Message.setAsynchronous(true)):

    • 標記為異步的消息,在遇到同步屏障時可以被優先處理。
    • 通常由系統內部使用(如 Choreographer 用于 VSYNC 回調)。開發者也可以通過 Handler 的特定構造函數 (Handler(looper, callback, async)) 或 Message.setAsynchronous(true) 發送異步消息。
  6. 對象池 (Message):

    • 為了避免頻繁創建和銷毀 Message 對象帶來的 GC 開銷,Message 內部維護了一個靜態對象池 (鏈表結構)。
    • Message.obtain() 會從池中取出一個復用對象(如果可用),否則才新建。
    • Message.recycle()recycleUnchecked() (在 Looper.loop()finally 塊中調用) 會將處理完的消息放回池中。
    • HandlerobtainMessage() 系列方法內部也是調用 Message.obtain()
  7. HandlerThread (便捷類):

    • 一個已經封裝好了 LooperThread 子類。使用它創建后臺線程處理消息非常方便:
      HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
      handlerThread.start(); // 內部會調用 prepare() 和 loop()
      Handler handler = new Handler(handlerThread.getLooper());
      handler.post(...); // 任務會在 handlerThread 這個后臺線程執行
      

重要注意事項

  1. 內存泄漏:

    • 典型場景:Activity 中聲明一個非靜態內部類的 Handler。這個 Handler 隱式持有外部 Activity 的引用。
    • 問題: 如果 Handler 的消息隊列中還有未處理完的消息(尤其是延遲消息),這些消息持有 Handler 引用 -> Handler 持有 Activity 引用 -> 導致 Activity 無法被 GC 回收,即使它已經被銷毀。
    • 解決方案:
      • 使用 static 內部類 + WeakReference 持有 Activity
      • ActivityonDestroy() 中調用 handler.removeCallbacksAndMessages(null) 清除隊列中所有該 Handler 的消息。
  2. ANR (Application Not Responding):

    • 原因: 主線程的 Looperloop() 中處理消息 (dispatchMessageRunnable.run())。如果某個消息的處理耗時過長(超過 5 秒),會阻塞后續所有消息的處理,包括屏幕繪制和用戶輸入事件,系統就會彈出 ANR 對話框。
    • 避免: 嚴禁在主線程執行耗時操作(網絡請求、大文件讀寫、復雜計算等)。耗時操作務必放在工作線程,完成后通過 Handler 通知主線程更新 UI。

總結

Android 的 Handler 機制是一個基于生產者-消費者模型構建的、圍繞消息隊列事件循環的異步通信框架。Handler 負責發送消息,Message 是數據載體,MessageQueue 是存儲和調度消息的優先級隊列(底層依賴 epoll 實現高效阻塞/喚醒),Looper 是驅動消息循環的核心引擎(依賴 ThreadLocal 保證線程隔離)。主線程的消息循環由系統自動創建,是 UI 更新的生命線。理解其原理對于編寫高效、響應流暢的 Android 應用至關重要,同時也要警惕內存泄漏和 ANR 問題。屏障消息和異步消息是系統優化 UI 性能的關鍵機制。HandlerThread 為后臺線程使用 Handler 提供了便捷方式。

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

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

相關文章

jQuery JSONP:實現跨域數據交互的利器

jQuery JSONP&#xff1a;實現跨域數據交互的利器 引言 隨著互聯網的發展&#xff0c;跨域數據交互的需求日益增加。在Web開發中&#xff0c;由于同源策略的限制&#xff0c;直接通過XMLHttpRequest請求跨域數據會遇到諸多問題。而JSONP&#xff08;JSON with Padding&#xff…

Redis集群和 zookeeper 實現分布式鎖的優勢和劣勢

在分布式系統中&#xff0c;實現分布式鎖是確保多個節點間互斥訪問共享資源的一種常見需求。Redis 集群 和 zookeeper 都可以用來實現這一功能&#xff0c;但它們有著各自不同的優勢和劣勢。 CAP 理論&#xff1a; 在設計一個分布式系統時&#xff0c;一致性&#xff08;Consis…

如何備份vivo手機中的聯系人?

隨著vivo移動設備在全球設立7個研發中心&#xff0c;vivo正在進入更多的國家。如今&#xff0c;越來越多的人開始使用vivo手機。以vivo X100為例&#xff0c;它配備了主攝像頭和多個輔助攝像頭&#xff0c;提供多樣化的拍攝選項&#xff0c;并搭載了最新的FunTouch OS&#xff…

python腳本編程:使用BeautifulSoup爬蟲庫獲取熱門單機游戲排行榜

BeautifulSoup是一個便捷的解析html頁面元素的python庫&#xff0c;此處用來寫一個簡單的爬蟲批量抓取國內游戲資訊網站的近期熱門單機游戲排行榜。 網頁來源如下所示代碼 from bs4 import BeautifulSoup import requests# get web page web_url "https://www.3dmgame.co…

C#配置全面詳解:從傳統方式到現代配置系統

C#配置全面詳解&#xff1a;從傳統方式到現代配置系統 在軟件開發中&#xff0c;配置是指應用程序運行時可調整的參數集合&#xff0c;如數據庫連接字符串、API 地址、日志級別等。將這些參數從代碼中分離出來&#xff0c;便于在不修改代碼的情況下調整應用行為。C# 提供了多種…

數據中臺架構解析:湖倉一體的實戰設計

目錄 一、數據中臺與湖倉一體架構是什么 1. 數據中臺 2. 湖倉一體架構 3. 湖倉一體在數據中臺里的價值 二、湖倉一體架構的核心部件 1. 數據湖 2. 數據倉庫 3. 數據集成工具 4. 數據分析與處理引擎 三、湖倉一體架構實戰設計 1. 需求分析與規劃 2. 數據湖建設 3. …

SQL Server表分區技術詳解

表分區概述 表分區是將大型數據庫表物理分割為多個較小單元的技術,邏輯上仍表現為單一實體。該技術通過水平分割數據顯著提升查詢性能,尤其針對TB級數據表可降低90%的響應時間。典型應用場景包含訂單歷史表、日志記錄表等具有明顯時間特征的業務數據,以及需要定期歸檔的審計…

WHIP(WebRTC HTTP Ingestion Protocol)詳解

WHIP&#xff08;WebRTC HTTP Ingestion Protocol&#xff09;詳解 WHIP&#xff08;WebRTC HTTP Ingestion Protocol&#xff09;是一種基于 HTTP 的協議&#xff0c;用于將 WebRTC 媒體流推送到媒體服務器&#xff08;如 SRS、Janus、LiveKit&#xff09;。它是為簡化 WebRT…

圖像噪點消除:用 OpenCV 實現多種濾波方法

在圖像處理中&#xff0c;噪點是一個常見的問題。它可能是由于圖像采集設備的缺陷、傳輸過程中的干擾&#xff0c;或者是光照條件不佳引起的。噪點會影響圖像的質量和后續處理的效果&#xff0c;因此消除噪點是圖像預處理的重要步驟之一。本文將介紹如何使用 OpenCV 實現幾種常…

AI的Prompt提示詞:英文寫好還是中文好?

在與AI人大模型交互時,Prompt(提示詞)的質量直接決定了輸出的精準度和有效性。一個常見的問題是:究竟是用英文寫Prompt好,還是用中文寫更好?這并非一個簡單的二元選擇,而是涉及到語言模型的底層邏輯、表達的精確性以及個人使用習慣的綜合考量。 英文Prompt的優勢 模型訓…

react的條件渲染【簡約風5min】

const flag1true; console.log(flag1&&hello); console.log(flag1||hello); const flag20; console.log(flag2&&hello); console.log(flag2||hello); // &&運算符&#xff0c;如果第一個條件為假&#xff0c;則返回第一個條件&#xff0c;否則返回第二…

【RK3568+PG2L50H開發板實驗例程】FPGA部分 | 紫光同創 IP core 的使用及添加

本原創文章由深圳市小眼睛科技有限公司創作&#xff0c;版權歸本公司所有&#xff0c;如需轉載&#xff0c;需授權并注明出處&#xff08;www.meyesemi.com)1.實驗簡介實驗目的&#xff1a;了解 PDS 軟件如何安裝 IP、使用 IP 以及查看 IP 手冊實驗環境&#xff1a;Window11 PD…

thinkphp微信小程序一鍵獲取手機號登陸(解密數據)

微信小程序獲取手機號登錄的步驟相對較為簡單,主要分為幾個部分: 1.用戶授權獲取手機號: 微信小程序通過調用 wx.getPhoneNumber API 獲取用戶授權后,獲取手機號。 2.前端獲取用戶的手機號: 用戶在小程序中點擊獲取手機號時,系統會彈出授權框,用戶同意后,你可以通過 …

數據庫設計精要:完整性和范式理論

文章目錄數據的完整性實體的完整性主鍵域完整性參照完整性外鍵多表設計/多表理論一對一和一對多多對多數據庫的設計范式第一范式&#xff1a;原子性第二范式&#xff1a;唯一性第三范式&#xff1a;不冗余性數據的完整性 實體的完整性 加主鍵&#xff0c;保證一個表中每一條數…

智能推薦社交分享小程序(websocket即時通訊、協同過濾算法、時間衰減因子模型、熱度得分算法)

&#x1f388;系統亮點&#xff1a;websocket即時通訊、協同過濾算法、時間衰減因子模型、熱度得分算法&#xff1b;一.系統開發工具與環境搭建1.系統設計開發工具后端使用Java編程語言的Spring boot框架項目架構&#xff1a;B/S架構運行環境&#xff1a;win10/win11、jdk17小程…

部署NextCloud AIO + Frp + nginx-proxy-manager內網穿透私有云服務

網絡拓撲 假設已有域名為nextcloud.yourhost.com 用戶通過域名https訪問 -> Nginx -> frps -> frpc -> NextCloud 其中Nginx和frps安裝在具有公網IP的服務器上&#xff0c;frpc和NextCloud安裝在內網服務器中。 Nginx配置 通過docker安裝nginx-proxy-manager 外…

【源力覺醒 創作者計劃】文心開源大模型ERNIE-4.5-0.3B-Paddle私有化部署保姆級教程及技術架構探索

一起來輕松玩轉文心大模型吧&#x1f449;一文心大模型免費下載地址: https://ai.gitcode.com/theme/1939325484087291906 前言 2025年6月30日&#xff0c;百度正式開源文心大模型4.5系列&#xff08;ERNIE 4.5&#xff09;&#xff0c;涵蓋10款不同參數規模的模型&#xff0…

大模型面試:如何解決幻覺問題

在大模型面試中回答“如何解決幻覺”問題時&#xff0c;需要展現你對問題本質的理解、技術方案的掌握以及工程實踐的洞察。以下是一個結構化的回答框架和關鍵點&#xff0c;供你參考&#xff1a;回答框架&#xff1a;問題理解 -> 解決方案 -> 總結 1. 明確問題&#xff0…

matlab實現五自由度機械臂阻抗控制下的力跟蹤

五自由度機械臂阻抗控制下的力跟蹤,可以實現對力的跟蹤反饋&#xff0c;基于MATLAB的機器人工具箱 eyebot.m , 767 zuakang_wailiraodong.m , 2568 colormp.mat , 682

excel日志表介紹

在Excel中制作“日志表事物”&#xff08;可理解為記錄事務的日志表格&#xff09;&#xff0c;通常用于系統性追蹤事件、任務、操作或數據變化。以下從表格設計、核心要素、制作步驟、函數應用及場景案例等方面詳細說明&#xff0c;幫助你高效創建和使用事務日志表。 一、日志…