Android 的 Handler
機制是跨線程通信和異步消息處理的核心框架,它構成了 Android 應用響應性和事件驅動模型的基礎(如 UI 更新、后臺任務協調)。其核心思想是 “消息隊列 + 循環處理”。
核心組件及其關系
-
Handler
(處理器):- 角色: 消息的發送者和處理者。
- 功能:
- 發送消息: 將
Message
或Runnable
對象放入與其關聯的MessageQueue
中。方法包括sendMessage()
,post()
,sendMessageDelayed()
,postDelayed()
等。 - 處理消息: 實現
handleMessage(Message msg)
方法,定義當消息從隊列中被取出時如何執行相應的操作。對于post(Runnable r)
,則是在關聯線程中執行r.run()
。
- 發送消息: 將
-
Message
(消息):- 角色: 通信的載體。
- 內容: 包含需要傳遞的數據 (
what
,arg1
,arg2
,obj
,data
Bundle) 和目標Handler
(target
)。 - 優化: 通常通過
Message.obtain()
或Handler.obtainMessage()
從對象池中獲取,避免頻繁創建對象引起 GC。
-
MessageQueue
(消息隊列):- 角色: 一個按執行時間排序(主要是
when
字段)的優先級隊列。 - 功能:
- 存儲消息: 接收
Handler
發送來的Message
,并根據when
(絕對時間戳) 插入到隊列的合適位置。 - 取出消息: 由關聯的
Looper
循環調用next()
方法取出下一個待處理的消息。如果隊列為空或下一個消息的執行時間未到,next()
會阻塞。
- 存儲消息: 接收
- 關鍵特性:
單線程
操作(每個Looper
線程有且只有一個MessageQueue
)。
- 角色: 一個按執行時間排序(主要是
-
Looper
(循環器):- 角色: 消息循環的引擎。負責驅動整個消息處理流程。
- 功能:
- 準備循環: 通過
Looper.prepare()
為當前線程創建一個Looper
(及其關聯的MessageQueue
)。 - 啟動循環: 調用
Looper.loop()
啟動一個無限循環。在這個循環中:- 調用
MessageQueue.next()
獲取下一條消息(可能阻塞)。 - 如果取到
null
(通常表示調用了quit
),退出循環。 - 否則,調用
msg.target.dispatchMessage(msg)
,將消息分發給發送它的Handler
處理。
- 調用
- 結束循環: 調用
Looper.quit()
(立即退出)或Looper.quitSafely()
(處理完已有消息后退出)。
- 準備循環: 通過
- 關鍵特性:
單線程
操作(每個Looper
線程有且只有一個Looper
)。- 主線程 (
ActivityThread
) 的Looper
在應用啟動時由系統自動創建并啟動 (Looper.prepareMainLooper()
,Looper.loop()
)。 - 其他線程需要手動調用
prepare()
和loop()
來創建和使用Looper
(如HandlerThread
)。
-
ThreadLocal
(線程局部存儲):- 角色: 確保每個線程訪問到它自己獨有的
Looper
和MessageQueue
實例。 - 原理:
Looper
內部使用ThreadLocal
存儲當前線程的Looper
對象。myLooper()
和getMainLooper()
都依賴于它。
- 角色: 確保每個線程訪問到它自己獨有的
工作流程詳解
-
初始化 (通常在主線程或自定義線程):
- 主線程: 系統自動完成
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. 啟動消息循環 (阻塞在此處)} }
- 主線程: 系統自動完成
-
發送消息 (例如從后臺線程發往主線程更新 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();
-
消息循環處理 (
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. 回收消息到對象池}} }
- 在擁有
-
消息分發 (
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()} }
底層原理與關鍵技術點
-
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()
會在超時或喚醒時返回。
- 問題: 當隊列為空或下一個消息的執行時間 (
-
ThreadLocal
保證線程隔離:Looper
類內部有一個static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<>();
。Looper.prepare()
調用sThreadLocal.set(new Looper(quitAllowed))
。Looper.myLooper()
調用sThreadLocal.get()
。- 這確保了每個線程調用
myLooper()
得到的都是自己線程創建的Looper
對象,從而每個線程都有自己獨立的MessageQueue
和消息循環。
-
主線程的特殊性:
ActivityThread
的main()
方法是 Android 應用的入口點。- 在
main()
中,系統會調用Looper.prepareMainLooper()
(內部也是prepare(false)
) 創建主線程Looper
,并將其標記為“主 Looper”(set()
到sMainLooper
并標記mIsMain
)。 - 然后調用
Looper.loop()
啟動主線程的消息循環。所有 UI 事件(觸摸、繪制、生命周期回調)以及通過主線程Handler
發送的消息,都在這個循環中被處理。 這就是為什么在非 UI 線程直接更新 UI 會報錯 - UI 操作必須在擁有 View 樹的線程(通常是主線程)執行。
-
屏障消息 (
SyncBarrier
):- 目的: 允許異步消息優先于同步消息執行。
- 實現:
- 調用
MessageQueue.postSyncBarrier()
會插入一個target
為null
的特殊Message
(屏障)。 - 在
next()
方法中,如果遇到屏障,它會跳過所有后續的同步消息 (msg.isAsynchronous() == false
),只查找異步消息 (msg.isAsynchronous() == true
) 或新的屏障。 - 當需要移除屏障時,調用
MessageQueue.removeSyncBarrier(token)
。
- 調用
- 應用場景: View 系統在請求布局 (
requestLayout()
) 和繪制 (draw()
) 時使用屏障,確保 UI 渲染相關的異步消息(如VSYNC
信號觸發的繪制)能優先執行,避免被耗時的同步消息阻塞造成卡頓。
-
異步消息 (
Message.setAsynchronous(true)
):- 標記為異步的消息,在遇到同步屏障時可以被優先處理。
- 通常由系統內部使用(如
Choreographer
用于VSYNC
回調)。開發者也可以通過Handler
的特定構造函數 (Handler(looper, callback, async)
) 或Message.setAsynchronous(true)
發送異步消息。
-
對象池 (
Message
):- 為了避免頻繁創建和銷毀
Message
對象帶來的 GC 開銷,Message
內部維護了一個靜態對象池 (鏈表結構)。 Message.obtain()
會從池中取出一個復用對象(如果可用),否則才新建。Message.recycle()
或recycleUnchecked()
(在Looper.loop()
的finally
塊中調用) 會將處理完的消息放回池中。Handler
的obtainMessage()
系列方法內部也是調用Message.obtain()
。
- 為了避免頻繁創建和銷毀
-
HandlerThread
(便捷類):- 一個已經封裝好了
Looper
的Thread
子類。使用它創建后臺線程處理消息非常方便:HandlerThread handlerThread = new HandlerThread("MyHandlerThread"); handlerThread.start(); // 內部會調用 prepare() 和 loop() Handler handler = new Handler(handlerThread.getLooper()); handler.post(...); // 任務會在 handlerThread 這個后臺線程執行
- 一個已經封裝好了
重要注意事項
-
內存泄漏:
- 典型場景: 在
Activity
中聲明一個非靜態內部類的Handler
。這個Handler
隱式持有外部Activity
的引用。 - 問題: 如果
Handler
的消息隊列中還有未處理完的消息(尤其是延遲消息),這些消息持有Handler
引用 ->Handler
持有Activity
引用 -> 導致Activity
無法被 GC 回收,即使它已經被銷毀。 - 解決方案:
- 使用
static
內部類 +WeakReference
持有Activity
。 - 在
Activity
的onDestroy()
中調用handler.removeCallbacksAndMessages(null)
清除隊列中所有該Handler
的消息。
- 使用
- 典型場景: 在
-
ANR (Application Not Responding):
- 原因: 主線程的
Looper
在loop()
中處理消息 (dispatchMessage
或Runnable.run()
)。如果某個消息的處理耗時過長(超過 5 秒),會阻塞后續所有消息的處理,包括屏幕繪制和用戶輸入事件,系統就會彈出 ANR 對話框。 - 避免: 嚴禁在主線程執行耗時操作(網絡請求、大文件讀寫、復雜計算等)。耗時操作務必放在工作線程,完成后通過
Handler
通知主線程更新 UI。
- 原因: 主線程的
總結
Android 的 Handler
機制是一個基于生產者-消費者模型構建的、圍繞消息隊列和事件循環的異步通信框架。Handler
負責發送消息,Message
是數據載體,MessageQueue
是存儲和調度消息的優先級隊列(底層依賴 epoll
實現高效阻塞/喚醒),Looper
是驅動消息循環的核心引擎(依賴 ThreadLocal
保證線程隔離)。主線程的消息循環由系統自動創建,是 UI 更新的生命線。理解其原理對于編寫高效、響應流暢的 Android 應用至關重要,同時也要警惕內存泄漏和 ANR 問題。屏障消息和異步消息是系統優化 UI 性能的關鍵機制。HandlerThread
為后臺線程使用 Handler
提供了便捷方式。