Android HandlerThread、Looper、MessageQueue 源碼分析
簡介
在 Android
開發中,大家應該對 HandlerThread
有一定了解。顧名思義,HandlerThread
是 Thread
的一個子類。與普通的 Thread
不同,Thread 通常一次只能執行一個后臺任務,如果需要執行多個任務,就必須創建多個線程,這樣容易導致資源管理復雜,甚至可能出現內存泄漏等問題。而 HandlerThread
有一個顯著的特點,它能夠串行地執行多個任務。每個任務通過消息的形式排隊執行,線程池中的任務按順序依次處理,無需手動管理線程的生命周期或調度過程。我們只需通過 Handler 將任務發送到 HandlerThread
中,它會自動按順序執行,極大簡化了開發過程。使用 HandlerThread
的最大優勢是:它是單線程的,因此不需要擔心線程安全問題。實際上,Android 源碼中也有許多地方使用了 HandlerThread
,它在設計思想上值得我們學習和借鑒。接下來,我們將通過源碼進一步了解它的實現原理。
源碼分析
HandlerThread
內部持有一個Looper
,Looper
中持有一個消息隊列MessageQueue
Looper
在了解HandlerThread
前,我們有必要先認識一下Looper
,Looper 是一個負責管理消息隊列(MessageQueue
)并循環處理其中消息的類。簡單來說,Looper 通過不斷地從消息隊列中取出消息并處理它們,來實現線程的消息處理機制。每個線程都有自己的消息隊列和 Looper,通過 Looper,線程能夠不斷地處理任務直到任務隊列為空。接下來我們會涉及到它以下幾個核心接口。
prepare()
是一個靜態方法,用來構建一個Looper
對象,quitAllowed
表示這個Looper是否支持退出,默認是支持,像Android 主線程的Looper
是不支持退出的
public static void prepare() {prepare(true);}
loopOnce
靜態方法,顧名思義,循環一次,它的作用是從當前消息隊列MessageQueue
中取一條符合條件的消息進行分發操作
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // 取一條消息if (msg == null) {//正常沒消息,隊列會堵塞,不會返回null,所以這里是null肯定是已經要結束了,這里返回false 退出looperreturn false;}//分發消息代碼邏輯省略}
loop()
靜態方法,啟動循環,在循環中不斷調用loopOnce
來處理消息
//關鍵代碼public static void loop() {final Looper me = myLooper();
//...for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {//返回false 意味著循環結束return;}}}
quit()
和quitSafely()
退出looper,前者是立即退出,后者是處理完當前隊列中所有消息后退出,最終是調用消息隊列MessageQueue
對應的退出方法
public void quit() {mQueue.quit(false);
}public void quitSafely() {mQueue.quit(true);
}
HandlerThread
- 既然它是一個線程,那可以先從
run
方法入手:
@Overridepublic void run() {mTid = Process.myTid();Looper.prepare();synchronized (this) {mLooper = Looper.myLooper();notifyAll();}Process.setThreadPriority(mPriority);onLooperPrepared();Looper.loop();mTid = -1;}
可以看出,線程跑起來后,先初始化了一個Looper
,然后啟動死循環,HandlerThread
在這里充當的作用是在子線程中開啟死循環接受和分發消息
這里有個地方比較有意思,在mLooper = Looper.myLooper();
后面,它調用了notifyAll()
,它起到了什么作用呢?
getLooper()
,我們應該知道要把任務提交給HandlerThread
執行,需要借助Handler
,但是Handler
的構造參數是需要傳入一個Looper
對象,所以,這里對外公開了獲取Looper的接口
public Looper getLooper() {if (!isAlive()) {return null;}boolean wasInterrupted = false;// If the thread has been started, wait until the looper has been created.synchronized (this) {while (isAlive() && mLooper == null) {try {wait();} catch (InterruptedException e) {wasInterrupted = true;}}}/** We may need to restore the thread's interrupted flag, because it may* have been cleared above since we eat InterruptedExceptions*/if (wasInterrupted) {Thread.currentThread().interrupt();}return mLooper;}
這里是直接返回了在run
中初始化的mLooper
,但是呢,在非當前線程獲取mLooper
對象,就會引發線程安全問題,可能mLooper
還沒被初始化就調用了getLooper()
,這樣就有可能返回一個空的數據了,所以官方在這里做了while
循環,并且使用了wait()
堵塞,等待上面run
初始化完成后再notifyAll()
這里
getThreadHandler()
就是公開給外面向當前HandlerThread
插入消息的接口,內部維護著一個Handler
對象
@NonNullpublic Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());}return mHandler;}
quit()
、quitSafely()
同樣,HandlerThread
也有退出的方法,其實現也是調用looper
對應的函數退出
MessageQueue
由于關于消息分發邏輯在其他地方講過,這里只要分析退出隊列的邏輯。
它實現了退出和安全退出的方法,這兩個操作有什么區別呢,請看源碼
next()
looper每調用一次loopOnce
,內部就會調用messageQueue獲取一條消息
Message next() {//只放關鍵代碼for (; ; ) {//堵塞,等待消息或者時機合適或主動喚醒nativePollOnce(ptr, nextPollTimeoutMillis);//...//符合分發條件的 返回這條消息給looperif (msg != null) {//...return msg;} else {// No more messages.nextPollTimeoutMillis = -1;}//...//退出狀態 則釋放隊列,結束循環if (mQuitting) {dispose();return null;}}
}
quit()
實際邏輯由下面兩個removeAllFutureMessagesLocked和removeAllMessagesLocked函數執行
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {//如果已經給隊列設置了退出信號,下面的邏輯就不用走了return;}mQuitting = true;//標記當前隊列處于退出狀態,此時不再接受新的消息if (safe) {//安全退出removeAllFutureMessagesLocked();} else {//立即退出removeAllMessagesLocked();}//把隊列的消息標記完成后,喚醒上面next的堵塞位置nativePollOnce,執行剩下的退出邏輯nativeWake(mPtr);}
}
//移除所有消息
private void removeAllMessagesLocked() {//mMessages 在這里是隊列的頭Message p = mMessages;while (p != null) {//從頭開始,把所有消息標記為回收狀態Message n = p.next;p.recycleUnchecked();p = n;}mMessages = null;//隊頭消息置空,next()執行時會判斷這個,為空直接退出隊列
}//移除所有未來消息,退出這一瞬間之前提交的消息保留繼續執行
private void removeAllFutureMessagesLocked() {final long now = SystemClock.uptimeMillis();Message p = mMessages;if (p != null) {if (p.when > now) {//隊頭的時間比當前新,說明全是后面新加的,全部回收掉removeAllMessagesLocked();} else {//以下為從消息隊列中找到退出那一瞬間時間一樣的分割點,把分割點前的消息與隊列斷開鏈接Message n;for (;;) {n = p.next;if (n == null) {return;}if (n.when > now) {break;}p = n;}p.next = null;//斷開鏈接,這里把隊列從分割點切斷do {p = n;n = p.next;p.recycleUnchecked();//把所有未來消息標記為回收狀態} while (n != null);}}
}
從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續被looper取,一直到取完為止,然后結束隊列退出循環,而普通退出就會把隊列中所有消息移除,然后緊接著結束隊列退出循環
quit和quitSafely 應用場景有哪些呢
;//把所有未來消息標記為回收狀態
} while (n != null);
}
}
}
從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續被looper取,一直到取完為止,然后結束隊列退出循環,而普通退出就會把隊列中所有消息移除,然后緊接著結束隊列退出循環### quit和quitSafely 應用場景有哪些呢
舉一個簡單的例子,在Android 使用Room操作數據庫,由于操作數據庫需要在子線程,所以,我們可以構造一個`HandlerThread`,專門處理操作數據庫的任務,如果操作過程非常耗時,然后又要關閉數據庫,