Handler 機制算是 Android 基本功,面試常客。但如今面試,多數已經不會直接讓你講講 Handler 的機制,Looper 是如何循環的,MessageQueue 是如何管理 Message 等,而是基於場景去提問,看看你對 Handler 機制的掌握是否紮實。java
本文就來聊聊 Handler 中的 IdleHandler,這個咱們比較少用的功能。它能幹什麼?怎麼使用?有什麼合適的使用場景?哪些不是合適的使用場景?在 Android Framework 中有哪些地方用到了它?面試
在說 IdleHandler 以前,先簡單瞭解一下 Handler 機制。數組
Handler 是標準的事件驅動模型,存在一個消息隊列 MessageQueue,它是一個基於消息觸發時間的優先級隊列,還有一個基於此消息隊列的事件循環 Looper,Looper 經過循環,不斷的從 MessageQueue 中取出待處理的 Message,再交由對應的事件處理器 Handler/callback 來處理。安全
其中 MessageQueue 被 Looper 管理,Looper 在構造時同步會建立 MessageQueue,並利用 ThreadLocal 這種 TLS,將其與當前線程綁定。而 App 的主線程在啓動時,已經構造並準備好主線程的 Looper 對象,開發者只須要直接使用便可。oop
Handler 類中封裝了大部分「Handler 機制」對外的操做接口,能夠經過它的 send/post 相關的方法,向消息隊列 MessageQueue 中插入一條 Message。在 Looper 循環中,又會不斷的從 MessageQueue 取出下一條待處理的 Message 進行處理。post
IdleHandler 使用相關的邏輯,就在 MessageQueue 取消息的 next()
方法中。學習
IdleHandler 說白了,就是 Handler 機制提供的一種,能夠在 Looper 事件循環的過程當中,當出現空閒的時候,容許咱們執行任務的一種機制。this
IdleHandler 被定義在 MessageQueue 中,它是一個接口。spa
// MessageQueue.java
public static interface IdleHandler {
boolean queueIdle();
}
複製代碼
能夠看到,定義時須要實現其 queueIdle()
方法。同時返回值爲 true 表示是一個持久的 IdleHandler 會重複使用,返回 false 表示是一個一次性的 IdleHandler。線程
既然 IdleHandler 被定義在 MessageQueue 中,使用它也須要藉助 MessageQueue。在 MessageQueue 中定義了對應的 add 和 remove 方法。
// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
// ...
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
複製代碼
能夠看到 add 或 remove 其實操做的都是 mIdleHandlers
,它的類型是一個 ArrayList。
既然 IdleHandler 主要是在 MessageQueue 出現空閒的時候被執行,那麼什麼時候出現空閒?
MessageQueue 是一個基於消息觸發時間的優先級隊列,因此隊列出現空閒存在兩種場景。
這兩個場景,都會嘗試執行 IdleHandler。
處理 IdleHandler 的場景,就在 Message.next()
這個獲取消息隊列下一個待執行消息的方法中,咱們跟一下具體的邏輯。
Message next() {
// ...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ...
if (msg != null) {
if (now < msg.when) {
// 計算休眠的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Other code
// 找到消息處理後返回
return msg;
}
} else {
// 沒有更多的消息
nextPollTimeoutMillis = -1;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
複製代碼
咱們先解釋一下 next()
中關於 IdleHandler 執行的主邏輯:
pendingIdleHandlerCount < 0
時,根據 mIdleHandlers.size()
賦值給 pendingIdleHandlerCount
,它是後期循環的基礎;mIdleHandlers
中的 IdleHandler 拷貝到 mPendingIdleHandlers
數組中,這個數組是臨時的,以後進入 for 循環;queueIdle()
記錄返回值存到 keep
中;keep
爲 false 時,從 mIdleHandler
中移除當前循環的 IdleHandler,反之則保留;能夠看到 IdleHandler 機制中,最核心的就是在 next()
中,當隊列空閒的時候,循環 mIdleHandler 中記錄的 IdleHandler 對象,若是其 queueIdle()
返回值爲 false
時,將其從 mIdleHander
中移除。
須要注意的是,對 mIdleHandler
這個 List 的全部操做,都經過 synchronized 來保證線程安全,這一點無需擔憂。
當隊列空閒時,會循環執行一遍 mIdleHandlers
數組並執行 IdleHandler.queueIdle()
方法。而若是數組中有一些 IdleHander 的 queueIdle()
返回了 true
,則會保留在 mIdleHanders
數組中,下次依然會再執行一遍。
注意如今代碼邏輯還在 MessageQueue.next()
的循環中,在這個場景下 IdleHandler 機制是如何保證不會進入死循環的?
有些文章會說 IdleHandler 不會死循環,是由於下次循環調用了 nativePollOnce()
藉助 epoll 機制進入休眠狀態,下次有新消息入隊的時候會從新喚醒,但這是不對的。
注意看前面 next()
中的代碼,在方法的末尾會重置 pendingIdleHandlerCount 和 nextPollTimeoutMillis。
Message next() {
// ...
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
// ...
// 循環執行 mIdleHandlers
// ...
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
複製代碼
nextPollTimeoutMillis 決定了下次進入 nativePollOnce()
超時的時間,它傳遞 0 的時候等於不會進入休眠,因此說 natievPollOnce()
進入休眠因此不會死循環是不對的。
這很好理解,畢竟 IdleHandler.queueIdle()
運行在主線程,它執行的時間是不可控的,那麼 MessageQueue 中的消息狀況可能會變化,因此須要再處理一遍。
實際不會死循環的關鍵是在於 pendingIdleHandlerCount,咱們看看下面的代碼。
Message next() {
// ...
// Step 1
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// ...
// Step 2
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// Step 3
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// ...
}
// Step 4
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
複製代碼
咱們梳理一下:
pendingIdleHandlerCount
的初始值爲 -1;pendingIdleHandlerCount<0
時,纔會經過 mIdleHandlers.size()
賦值。也就是說只有第一次循環纔會改變 pendingIdleHandlerCount
的值;pendingIdleHandlerCount<=0
時,則循環 continus;pendingIdleHandlerCount
爲 0;在第二次循環時,pendingIdleHandlerCount
等於 0,在 Step 2 不會改變它的值,那麼在 Step 3 中會直接 continus 繼續下一次循環,此時沒有機會修改 nextPollTimeoutMillis
。
那麼 nextPollTimeoutMillis
有兩種可能:-1 或者下次喚醒的等待間隔時間,在執行到 nativePollOnce()
時就會進入休眠,等待再次被喚醒。
下次喚醒時,mMessage
必然會有一個待執行的 Message,則 MessageQueue.next()
返回到 Looper.loop()
的循環中,分發處理這個 Message,以後又是一輪新的 next()
中去循環。
到這裏基本上就講清楚 IdleHandler 如何使用以及一些細節,接下來咱們來看看,在系統中,有哪些地方會用到 IdleHandler 機制。
在 AS 中搜索一下 IdleHandler。
簡單解釋一下:
ActivityThread.handleResumeActivity()
中調用。有興趣能夠本身追一下源碼,這些都是使用的場景,具體用 IdleHander 幹什麼,仍是要看業務。
到這裏咱們就講清楚 IdleHandler 幹什麼?怎麼用?有什麼問題?以及使用中一些原理的講解。
下面準備一些基本的問題,供你們理解。
Q:IdleHandler 有什麼用?
Q:MessageQueue 提供了 add/remove IdleHandler 的方法,是否須要成對使用?
Q:當 mIdleHanders 一直不爲空時,爲何不會進入死循環?
Q:是否能夠將一些不重要的啓動服務,搬移到 IdleHandler 中去處理?
Q:IdleHandler 的 queueIdle() 運行在那個線程?
到這裏就把 IdleHandler 的使用和原理說清除了。
IdleHandler 是 Handler 提供的一種在消息隊列空閒時,執行任務的時機。但它執行的時機依賴消息隊列的狀況,那麼若是 MessageQueue 一直有待執行的消息時,IdleHandler 就一直得不到執行,也就是它的執行時機是不可控的,不適合執行一些對時機要求比較高的任務。
本文就到這裏,對你有幫助嗎?有任何問題歡迎留言。以爲有幫助別忘了轉發、點好看,謝謝!
推薦閱讀:
公衆號後臺回覆成長『成長』,將會獲得我準備的學習資料。