上海 網(wǎng)站平臺開發(fā)互聯(lián)網(wǎng)營銷師考試題及答案
Android HandlerThread、Looper、MessageQueue 源碼分析
簡介
在 Android
開發(fā)中,大家應(yīng)該對 HandlerThread
有一定了解。顧名思義,HandlerThread
是 Thread
的一個子類。與普通的 Thread
不同,Thread 通常一次只能執(zhí)行一個后臺任務(wù),如果需要執(zhí)行多個任務(wù),就必須創(chuàng)建多個線程,這樣容易導(dǎo)致資源管理復(fù)雜,甚至可能出現(xiàn)內(nèi)存泄漏等問題。而 HandlerThread
有一個顯著的特點,它能夠串行地執(zhí)行多個任務(wù)。每個任務(wù)通過消息的形式排隊執(zhí)行,線程池中的任務(wù)按順序依次處理,無需手動管理線程的生命周期或調(diào)度過程。我們只需通過 Handler 將任務(wù)發(fā)送到 HandlerThread
中,它會自動按順序執(zhí)行,極大簡化了開發(fā)過程。使用 HandlerThread
的最大優(yōu)勢是:它是單線程的,因此不需要擔(dān)心線程安全問題。實際上,Android 源碼中也有許多地方使用了 HandlerThread
,它在設(shè)計思想上值得我們學(xué)習(xí)和借鑒。接下來,我們將通過源碼進一步了解它的實現(xiàn)原理。
源碼分析
HandlerThread
內(nèi)部持有一個Looper
,Looper
中持有一個消息隊列MessageQueue
Looper
在了解HandlerThread
前,我們有必要先認識一下Looper
,Looper 是一個負責(zé)管理消息隊列(MessageQueue
)并循環(huán)處理其中消息的類。簡單來說,Looper 通過不斷地從消息隊列中取出消息并處理它們,來實現(xiàn)線程的消息處理機制。每個線程都有自己的消息隊列和 Looper,通過 Looper,線程能夠不斷地處理任務(wù)直到任務(wù)隊列為空。接下來我們會涉及到它以下幾個核心接口。
prepare()
是一個靜態(tài)方法,用來構(gòu)建一個Looper
對象,quitAllowed
表示這個Looper是否支持退出,默認是支持,像Android 主線程的Looper
是不支持退出的
public static void prepare() {prepare(true);}
loopOnce
靜態(tài)方法,顧名思義,循環(huán)一次,它的作用是從當(dāng)前消息隊列MessageQueue
中取一條符合條件的消息進行分發(fā)操作
private static boolean loopOnce(final Looper me,final long ident, final int thresholdOverride) {Message msg = me.mQueue.next(); // 取一條消息if (msg == null) {//正常沒消息,隊列會堵塞,不會返回null,所以這里是null肯定是已經(jīng)要結(jié)束了,這里返回false 退出looperreturn false;}//分發(fā)消息代碼邏輯省略}
loop()
靜態(tài)方法,啟動循環(huán),在循環(huán)中不斷調(diào)用loopOnce
來處理消息
//關(guān)鍵代碼public static void loop() {final Looper me = myLooper();
//...for (;;) {if (!loopOnce(me, ident, thresholdOverride)) {//返回false 意味著循環(huán)結(jié)束return;}}}
quit()
和quitSafely()
退出looper,前者是立即退出,后者是處理完當(dāng)前隊列中所有消息后退出,最終是調(diào)用消息隊列MessageQueue
對應(yīng)的退出方法
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
,然后啟動死循環(huán),HandlerThread
在這里充當(dāng)?shù)淖饔檬窃谧泳€程中開啟死循環(huán)接受和分發(fā)消息
這里有個地方比較有意思,在mLooper = Looper.myLooper();
后面,它調(diào)用了notifyAll()
,它起到了什么作用呢?
getLooper()
,我們應(yīng)該知道要把任務(wù)提交給HandlerThread
執(zhí)行,需要借助Handler
,但是Handler
的構(gòu)造參數(shù)是需要傳入一個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
,但是呢,在非當(dāng)前線程獲取mLooper
對象,就會引發(fā)線程安全問題,可能mLooper
還沒被初始化就調(diào)用了getLooper()
,這樣就有可能返回一個空的數(shù)據(jù)了,所以官方在這里做了while
循環(huán),并且使用了wait()
堵塞,等待上面run
初始化完成后再notifyAll()
這里
getThreadHandler()
就是公開給外面向當(dāng)前HandlerThread
插入消息的接口,內(nèi)部維護著一個Handler
對象
@NonNullpublic Handler getThreadHandler() {if (mHandler == null) {mHandler = new Handler(getLooper());}return mHandler;}
quit()
、quitSafely()
同樣,HandlerThread
也有退出的方法,其實現(xiàn)也是調(diào)用looper
對應(yīng)的函數(shù)退出
MessageQueue
由于關(guān)于消息分發(fā)邏輯在其他地方講過,這里只要分析退出隊列的邏輯。
它實現(xiàn)了退出和安全退出的方法,這兩個操作有什么區(qū)別呢,請看源碼
next()
looper每調(diào)用一次loopOnce
,內(nèi)部就會調(diào)用messageQueue獲取一條消息
Message next() {//只放關(guān)鍵代碼for (; ; ) {//堵塞,等待消息或者時機合適或主動喚醒nativePollOnce(ptr, nextPollTimeoutMillis);//...//符合分發(fā)條件的 返回這條消息給looperif (msg != null) {//...return msg;} else {// No more messages.nextPollTimeoutMillis = -1;}//...//退出狀態(tài) 則釋放隊列,結(jié)束循環(huán)if (mQuitting) {dispose();return null;}}
}
quit()
實際邏輯由下面兩個removeAllFutureMessagesLocked和removeAllMessagesLocked函數(shù)執(zhí)行
void quit(boolean safe) {if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");}synchronized (this) {if (mQuitting) {//如果已經(jīng)給隊列設(shè)置了退出信號,下面的邏輯就不用走了return;}mQuitting = true;//標記當(dāng)前隊列處于退出狀態(tài),此時不再接受新的消息if (safe) {//安全退出removeAllFutureMessagesLocked();} else {//立即退出removeAllMessagesLocked();}//把隊列的消息標記完成后,喚醒上面next的堵塞位置nativePollOnce,執(zhí)行剩下的退出邏輯nativeWake(mPtr);}
}
//移除所有消息
private void removeAllMessagesLocked() {//mMessages 在這里是隊列的頭Message p = mMessages;while (p != null) {//從頭開始,把所有消息標記為回收狀態(tài)Message n = p.next;p.recycleUnchecked();p = n;}mMessages = null;//隊頭消息置空,next()執(zhí)行時會判斷這個,為空直接退出隊列
}//移除所有未來消息,退出這一瞬間之前提交的消息保留繼續(xù)執(zhí)行
private void removeAllFutureMessagesLocked() {final long now = SystemClock.uptimeMillis();Message p = mMessages;if (p != null) {if (p.when > now) {//隊頭的時間比當(dāng)前新,說明全是后面新加的,全部回收掉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();//把所有未來消息標記為回收狀態(tài)} while (n != null);}}
}
從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續(xù)被looper取,一直到取完為止,然后結(jié)束隊列退出循環(huán),而普通退出就會把隊列中所有消息移除,然后緊接著結(jié)束隊列退出循環(huán)
quit和quitSafely 應(yīng)用場景有哪些呢
;//把所有未來消息標記為回收狀態(tài)
} while (n != null);
}
}
}
從上面可以看出,安全退出時,隊列會把所有未來消息移除掉,并且不再接受新的消息,隊列中剩下的消息會繼續(xù)被looper取,一直到取完為止,然后結(jié)束隊列退出循環(huán),而普通退出就會把隊列中所有消息移除,然后緊接著結(jié)束隊列退出循環(huán)### quit和quitSafely 應(yīng)用場景有哪些呢
舉一個簡單的例子,在Android 使用Room操作數(shù)據(jù)庫,由于操作數(shù)據(jù)庫需要在子線程,所以,我們可以構(gòu)造一個`HandlerThread`,專門處理操作數(shù)據(jù)庫的任務(wù),如果操作過程非常耗時,然后又要關(guān)閉數(shù)據(jù)庫,