【Android】從原始碼角度看Handler機制
在Android開發規範中,規定了主執行緒的任務的響應時間不能超過5s,否則會出現ANR,即程式無響應。為了避免這個問題的出現,常用的一個解決方案就是開闢新執行緒,在開闢出來的子執行緒中去處理耗時的業務,然後回到UI執行緒(主執行緒)來重新整理UI,這個過程中“回到UI執行緒重新整理UI”這一步的實現,常用到handler。因此在我的理解中,Handler、主執行緒、子執行緒之間的關係可以比喻成人操作無人機,主執行緒就是操作無人機的人,子執行緒就是無人機,而handler就是人和無人機之間的遙控器,人放出無人機去拍攝各種場景(處理耗時操作),這個過程中人(主執行緒)可以不受影響的做自己的事情,當無人機(子執行緒)處理完任務,會把資料通過遙控器(Handler)的畫面傳遞給人(主執行緒),人收到畫面後對畫面資料進行讀取和使用(更新主執行緒)。例子僅做輔助理解用,如果有理解偏差不到位,還望指出!感謝!
實際上網上有關多執行緒、併發、Handler的資料有很多,多數講解的內容也十分深刻詳細,筆者自知能力有限,本文就不再像各位大佬那樣介紹對Handler的理解了,但是想從原始碼的角度,來分析一下Handler的工作原理,也作為自己學習的內化成果檢測。
首先需要介紹幾個概念:
- MessageQueue: 訊息佇列,其實這裡說佇列有點不合適,因為實際上其內部儲存並非佇列的形式,而是用了一個單鏈表的資料結構來儲存一系列訊息。
- Looper: 訊息輪詢器,它能夠不斷的訪問我們的訊息佇列,從而提取其中的訊息以供處理。實現的核心在於其loop()方法。
- Handler: 既能接收訊息,又能處理訊息,主執行緒和子執行緒的橋樑。
- Message: 訊息,是資料的載體。
下面我們正向的去了解一下Handler的執行原理:
我們前面說過,Handler相當於一個主執行緒與子執行緒之間的橋樑,這個橋樑之所以能在兩端之間建立聯絡,是因為looper的存在:
我們的 Android 應用在啟動時,會執行到 ActivityThread 類的 main 方法,就和我們以前寫的 java 控制檯程式一樣,其實 ActivityThread 的 main 方法就是一個應用啟動的入口。在這個入口裡,會做很多初始化的操作。其中就有 Looper 相關的設定,程式碼如下
public static void main(String[] args) {
//............. 無關程式碼...............
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
進入prepareMainLooper()的實現:
public static void prepareMainLooper() {
prepare(false);//注意這裡
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
進來就先執行了一個prepare方法,進去看看:
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));
}
這裡我們可以看到,有一個sThreadLocal變數,如果這個變數非空,則執行prepare會報出異常:Only one Looper may be created per thread,如果這個變數為空,我們會為這個變數賦值一個新的Looper物件。
這裡的Looper(quiteAllowed)顯然是呼叫了Looper的構造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
至此我們可以發現,整個過程在app的啟動之初,主執行緒開始呼叫Looper.prepareMainLooper()方法, 為sThreadLocal設定了一個唯一的Looper,並且這個Looper是持有當前執行緒的引用的,也就是說,這個Looper與我們呼叫prepareMainLooper的執行緒是一個繫結的關係,而這個執行緒,就是主執行緒。
因此,在app啟動之初,就將主執行緒所對應的Looper物件放進了sThreadLocal中。
說到這裡還需要說一下這個ThreadLocal,ThreadLocal並不是執行緒,它的作用是可以在每個執行緒中儲存資料。它其實類似於一個HashMap,每個執行緒對應了一系列資料。我們通過set和get方法讀取某個執行緒所對應的一些資料的值。這個後文會有例子。
回過頭繼續看剛剛的main入口:
public static void main(String[] args) {
//............. 無關程式碼...............
Looper.prepareMainLooper();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
在為當前執行緒建立好looper並放進sThreadLocal後,就開始執行Looper.loop()方法。這個方法具體是幹什麼的呢?
public static void loop() {
//獲得一個 Looper 物件
final Looper me = myLooper();
// 拿到 looper 對應的 mQueue 物件
final MessageQueue queue = me.mQueue;
//死迴圈監聽(如果沒有訊息變化,他不會工作的) 不斷輪訓 queue 中的 Message
for (;;) {
// 通過 queue 的 next 方法拿到一個 Message
Message msg = queue.next(); // might block
//空判斷
if (msg == null)return;
//訊息分發
msg.target.dispatchMessage(msg);
//回收操作
msg.recycleUnchecked();
}
}
這裡就是訊息輪詢的關鍵了,在上文中給出的Looper的構造方法中,我們注意到它建立了一個MessageQueue物件mQueue,未來所有的任務都會在這個mQueue佇列中存放,主執行緒通過loop()去遍歷這個佇列,並依次執行其中的任務。
這裡介紹完looper的初始化過程以及系統通過looper實現的訊息輪詢,下面說一說Handler與這些東西到底有什麼關係,先看Handler的構造方法:
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//別忘了myLooper的實現
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
這樣一看,似乎可以明白了——我們每次建立一個Handler,就會呼叫myLooper去從sThreadLocal中拿到當前建立Handler的執行緒的Looper物件,並且根據這個looper物件得到對應的訊息佇列。也就是說,如果我們在主執行緒中建立一個handler,那麼他就會拿到主執行緒的Looper,然後得到主執行緒不斷輪詢的那個訊息佇列。
需要注意的是:執行緒預設是不具有Looper的,如果需要使用Handler就必須為執行緒建立Looper。我們經常提到的主執行緒,也叫UI執行緒,它就是ActivityThread,ActivityThread被建立時就會初始化Looper,這也是在主執行緒中預設可以使用Handler的原因。
當我們使用Handler去傳送訊息的時候,最終都會走進一個函式:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//注意這句 檢視定義可以發現 Message的target是一個型別為Handler的成員變數
//handler遵循誰傳送訊息 誰處理訊息的原則 所以這裡把target設定成self
// 使用預設的 handler 構造方法時,mAsynchronous 為 false。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//這句以後,messageQueue就會多一條message
//looper在呼叫loop()不斷輪訓messageQueue的時候就會發現它,並且處理它
}
看到這裡相信能夠明白了,我們每次使用handler去傳送訊息後,都會在當前執行緒的訊息佇列中新增一條訊息,當前執行緒輪詢到這條訊息並執行它。
執行它的過程,剛剛我們在loop()方法的原始碼中可以看到,有這樣一個函式:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);//我們沒有給上面兩個callback賦值,所以走進這個分支
}
}
//看這個方法的實現:
public void handleMessage(Message msg) {
//這是一個空方法
}
handleMessage方法的方法體是空的,這也是為什麼我們在使用的時候常常需要複寫一個handleMessage的原因。
至此應該有許多讀者已經看暈了,說實話筆者能力有限…這一塊正向的去解釋確實有點凌亂。因此在這裡再總結一下:
在android中執行緒是預設沒有Looper的,在程式啟動之初,會為主執行緒建立一個Looper物件,並且根據Looper初始化了當前執行緒的訊息佇列,然後開始主動輪詢這個佇列中的訊息。當我們在某個執行緒中建立Handler時,就會獲取當前執行緒的Looper(注意,這個執行緒是建立Handler的執行緒,而不是handler傳送訊息的執行緒),根據looper拿到這個執行緒的訊息佇列,然後未來所有通過Handler傳送的訊息,都會被加入到這個執行緒的佇列中。我們常在主執行緒中建立了Handler後,用一個新的執行緒去呼叫handler.sendmessage()方法,而handler又是遵循自己傳送自己處理的機制,會自己接收到訊息並走入handleMessage方法,我們再通過複寫handleMessage來重新整理UI。
以上是對Handler機制以及其執行過程正向的分析,功力有限可能難以做到深入淺出。
有理解的不對的還望指正!感謝閱讀!