Android訊息機制之Handler原始碼分析
前言
Android訊息機制分析
Handler原始碼分析
ofollow,noindex">同步至個人部落格
正文
一. 概述
Handler
算是我們平時開發中比較常用的一個,Handler
所代表的是Android
中重要的一部分, 即訊息機制; 本文將分析Handler
的執行機制
1.1 使用場景
-
執行緒切換: 這個是使用
Handler
最常見和最頻繁的場景了; 我們都知道,UI
執行緒中不允許執行耗時操作, 那麼當我們需要先去獲取資料(耗時)再進行UI
展示或者互動的時候, 執行緒切換就必不可少了 -
任務延時: 即在未來某個時刻再執行任務, 這個主要是通過
Handler.postDelayed()
和Handler.sendMessageDelayed()
完成的(實際上postDelayed()
也是通過sendMessageDelayed()
完成的)
1.2 為什麼會有Handler
我們都知道Handler
的作用主要是用於切換執行緒, 那麼為什麼會出現Handler
喃; 我們以UI
執行緒中執行耗時操作為例, 對於耗時操作, 特別是在需要實時互動的應用中, 肯定是需要設計為多執行緒的, 那麼Android
設計之初為什麼不使用多執行緒去訪問UI
呢, 最致命的一點就是, 多執行緒存在併發訪問的不確定性, 這就會造成UI
表現的不可預期性, 造成使用者體驗極差; 當然, 對於多執行緒併發的問題還是有很多方式去避免和解決的, 最常見的一個就是加鎖, 那麼為什麼不採用多執行緒訪問,
對UI
加鎖的方式實現呢, 一個是加鎖會使UI
邏輯變得複雜, 另一個是加鎖會降低效率, 阻塞某些執行緒的執行
所以, 基於很多方面的考慮, 特別是對於Android
應用這樣互動性很強的場景而言, 使得需要去實現自己的一套非同步通訊機制, 需要達到的目標是, 執行緒之間的獨立性保持和資料互動的便利性, 這也是Handler
實現的
1.3 概述
在Android
訊息機制中, 有非常重要的幾個部分, 也是我們分析的切入點:
-
MessageQueue: 單鏈表, 實現訊息儲存; 是
Java
層和Native
層的連線紐帶 -
Looper: 無限迴圈查詢是否有新訊息
-
Handler: 訊息接受和分發處理的中轉站
以下是我們使用Handler
最常見的一種寫法, 我們通常會重寫handleMessage()
來實現自己的邏輯; 當在其他執行緒中通過Handler.sendMessage()
來發送訊息時, 我們就會在handleMessage()
中接收到該訊息
private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { // do somthing with msg super.handleMessage(msg); } }; new Thread() { @Override public void run() { mHandler.sendEmptyMessage(0); } }.start();
Handler
進行執行緒切換和訊息傳遞的一個大致流程是, 當我們通過sendMessage()
傳送訊息的時候,MessageQueue
負責儲存該訊息,Looper
會無限迴圈的去檢查MessageQueue
中是否有訊息, 如果有則呼叫Handler
的dispatchMessage()
進行訊息分發, 最終會回到handleMessage
中進行處理
在開始講解之前, 需要先明確幾個結論(在稍後會驗證): 每個執行緒都可以有自己的Looper
, 但是預設沒有建立(除了UI
執行緒外);Looper
是建立Handler
所必須的(否則會拋異常);MessageQueue
和Looper
是一一對應的關係, 也就是說每個執行緒會有自己的MessageQueue
和Looper
;Handler
在哪個執行緒中建立就在哪個執行緒中處理訊息, 其他執行緒傳送的訊息儲存在對應Handler
所對應的MessageQueue
中
二. 建構函式
Handler
提供了七個建構函式, 如下表
Handler() | Handler(Callback callback) | Handler(Looper looper) |
---|---|---|
Handler(Looper looper, Callback callback) | Handler(boolean async) | Handler(Callback callback, boolean async) |
Handler(Looper looper, Callback callback, boolean async) |
這七個建構函式最常用的是預設建構函式(即不帶引數那個), 其他幾個, 一類是帶Callback
的, 是提供的訊息處理函式, 我們使用Handler
一般都會重寫其handleMessage()
方法來處理訊息, 但是也可以不重寫handleMessage()
, 而是在構造Handler
的時候傳入一個Callback
回撥,Callback
是Handler
中的一個介面, 如下; 另一類是帶標誌位async
的, 是設定訊息是否為非同步處理,Handler
中的訊息預設是同步處理的
public interface Callback { public boolean handleMessage(Message msg); }
這裡我們以建構函式Handler(Callback callback, boolean async)
進行分析, 如下; 可以看出, 這裡的mLooper
是通過Looper.myLooper()
獲取的, 如果我們獲取為null
也就是說當前執行緒沒有Looper
的話, 會丟擲異常, 這個異常也是我們大多數初學者會遇到的; 這裡也驗證了上面所說的,Looper
是Handler
構造所必須的, 如果當前執行緒沒有Looper
的話, 會拋異常(至於為什麼說是當前執行緒, 接下來馬上分析~)
public Handler(Callback callback, boolean async) { ... mLooper = Looper.myLooper(); 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.myLooper()
又是如何獲取當前執行緒的Looper
的呢; 如下, 可以看出是通過sThreadLocal
獲取的,sThreadLocal
是一個ThreadLocal
型別的例項
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
ThreadLocal
不是一個執行緒, 而是用於在每個執行緒中儲存資料的,ThreadLocal
可以在不同的執行緒中互不干擾的儲存並提供資料; 那麼它是如何實現在不同執行緒中互不干擾的存取資料的呢, 我們先來看其set()
方法, 可以看出, 其實際上會先去檢查當前執行緒是否持有ThreadLocalMap
, 如果有的話, 直接將value
儲存進去即可, 否則, 初始化當前執行緒的ThreadLocalMap
然後再存放值, 這樣就可以根據不同的執行緒來存取各自執行緒的值了
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
那麼我們要構造Handler
就必須獲取當前執行緒的Looper
, 那麼我們又是什麼時候構造和儲存的Looper
喃; 前面我們說了, 每個執行緒都可以有自己的Looper
, 但是除了UI
執行緒以外都預設沒有建立; 我們先來看UI
執行緒中是什麼時候建立的Looper
,UI
執行緒就是ActivityThread
, 在其main()
函式中, 我們發現瞭如下與Looper
相關的語句, 這實際上是開啟了主執行緒的訊息處理(因為要處理各種事件, 所以一開始就開啟了)
public static void main(String[] args) { ... Looper.prepareMainLooper(); ... Looper.loop(); }
我們繼續看Looper.prepareMainLooper()
, 其中實際上是呼叫了prepare()
, 如下; 這裡我們看到了熟悉的ThreadLocal.set()
, 而且儲存的是一個新的Looper
, 那麼這裡就將執行緒和各自的Looper
聯絡起來了, 我們在Handler
建構函式中獲取的也就是和各個執行緒相關的Looper
了
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)); }
到這裡我們知道了,perpare
實際上是在準備Looper
, 那麼Looper.loop()
又是在做啥呢, 其實Looper.loop()
是開啟訊息處理迴圈, 前面我們說了Looper
不斷的去檢測MessageQueue
中是否有訊息, 實際上就是在loop()
中開啟的
這裡我們暫時不講loop()
, 而是放到後面講訊息處理的時候講; 我們平時使用Handler
的時候, 大多是在UI
執行緒中建立的, 因為上面我們已經看到了,UI
執行緒中預設是建立了Looper
的, 所以不需要我們再去手動建立, 但是我們如何在子執行緒中構造和使用Handler
呢; 其實在Looper
的文件中已經給出了示例, 如下; 其實, 和上面UI
執行緒中Looper
的處理過程差不多, 只是由於UI
執行緒的特殊性, 所以單獨給它準備了一個方法~(實際上是做一些其他特殊判斷和處理)
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // process incoming messages here } }; Looper.loop(); } }
三. 訊息傳送
當我們需要通過Handler
傳送訊息時, 可以使用兩個系列, 一個是postXXX
系列, 一個sendXXX
系列
postXXX:post
系列主要是傳送一個自定義的Runnable
事件, 然後去執行(延遲執行)自定義事件
post(Runnable r) |
---|
postAtTime(Runnable r, long uptimeMillis) |
postAtTime(Runnable r, Object token, long uptimeMillis) |
postDelayed(Runnable r, long delayMillis) |
postDelayed(Runnable r, Object token, long delayMillis) |
postAtFrontOfQueue(Runnable r) |
sendXXX:send
系列主要是傳送封裝的一個msg
, 然後根據msg
帶的tag
或者資料在handleMessage()
中進行處理
sendMessage(Message msg) |
---|
sendEmptyMessage(int what) |
sendEmptyMessageDelayed(int what, long delayMillis) |
sendEmptyMessageAtTime(int what, long uptimeMillis) |
sendMessageDelayed(Message msg, long delayMillis) |
sendMessageAtTime(Message msg, long uptimeMillis) |
sendMessageAtFrontOfQueue(Message msg) |
但是, 實際上post
系列最終還是將Runnable
事件封裝為了一個Message
物件交給了send
系列處理, 而不管是postXXX
還是sendXXX
, 最終轉到的都是enqueueMessage()
進行處理; 另外, 不管是post
的Runnable
還是send
的int what
等其他型別, 其實最終都會將每一條訊息封裝為一個Message
物件進行傳遞
四. 訊息儲存
前面我們說過,MessageQueue
會對傳送的訊息進行儲存, 這一步就是在enqueueMessage()
中處理的; 如下; 需要注意的是這裡傳入的MessageQueue
是在建立Handler
時建立, 也就是說和Handler
相關, 因為涉及到多執行緒的處理, 所以需要清楚MessageQueue
的來源, 不然訊息處理的時候就混了~
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
這裡我們簡單看一下MessageQueue
中Message
的組織方式, 如下,MessageQueue.enqueueMessage()
, 可以看出是很明顯的單鏈表的儲存方式, 之所以用單鏈表, 是因為單鏈表在增加和刪除節點上有優勢
boolean enqueueMessage(Message msg, long 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; // invariant: p == prev.next prev.next = msg; ... }
五. 訊息處理
還記得前面我們還有一個問題沒有解決嗎, 就是Looper.loop()
過程,Looper.loop()
過程實際上就是開啟迴圈處理訊息的過程~
該函式如下; 首先獲取訊息處理執行緒的Looper
, 前面我們已經看到了,Looper.loop()
是在訊息處理執行緒中呼叫的, 所以這裡獲取到的, 也就是訊息處理執行緒, 即建立Handler
的執行緒; 之後獲取對應執行緒的訊息佇列, 即MessageQueue
, 需要注意這裡的MessageQueue
和Looper
關聯的, 而Looper
的獲取是通過Thread.currentThread()
來判斷和獲取的, 那麼這裡就能獲取到和對應Handler
相關聯的MessageQueue
, 當我們在不同執行緒中呼叫Handler.sendMsg
的時候, 插入的也是和該Handler
關聯的MessageQueue
, 這樣, 一整條完整的線就串成了:不同的執行緒往需要傳送訊息的執行緒的MessageQueue
中插入Message
, 而和執行緒關聯的Looper
又從該MessageQueue
中不斷獲取Message
處理, 這樣就完成了執行緒的切換 !!
接下來就是用一個死迴圈(for
)開始訊息處理了; 邏輯也比較簡單, 其實就是不斷的從MessageQueue
中獲取訊息(MessageQueue.next()
), 然後回撥Handler.dispatchMessage(msg)
分發, 需要注意的是, 這裡的msg.target
實際上是儲存的對應Handler
的引用
public static void loop() { final Looper me = myLooper(); ... final MessageQueue queue = me.mQueue; ... for (;;) { Message msg = queue.next(); // might block // 如果訊息為null, 不處理咯 if (msg == null) { // No message indicates that the message queue is quitting. return; } ... msg.target.dispatchMessage(msg); } }
我們看一下dispatchMessage
的處理邏輯, 如下; 邏輯也很簡單, 就是訊息處理的優先順序: 如果Message
自身設定了Callback
, 就直接執行Message
的Callback
(這種情況對應前面說的post(Runnable)
的情況); 如果構造Handler
的時候設定了Callback
那麼交給該Callback
處理, 否則回撥handleMessage()
處理
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
這裡還要注意的一點是從MessageQueue
中獲取訊息時, 即MessageQueue.next()
, 這是一個阻塞方法, 也就是說當MessageQueue
中沒有訊息的時候, 會阻塞在這裡, 直到MessageQueue
中再次儲存訊息
六. 總結
到這裡Handler
的整個Java
層面的執行機制我們就講解完啦~ , 到這裡我們對整個Android
的訊息傳遞和處理機制也有了比較詳細的瞭解了; 為加深記憶, 可以對著原始碼自己再過一遍過程~