IdleHandler:空閑監聽器(就像我沒事做了,在群里發了個表情,這時候其他人就知道我很閑了)
在每次next獲取消息進行處理時,發現沒有可以處理的消息(隊列空,只有延時消息並且沒到時間,同步阻塞時沒有異步消息)都會通知這些訂閱者。
適合做一些可有可無的東西,因為這個通知太不穩定了(就像別人說過幾天請你吃飯,你要真傻等着你估計能餓死)。
1.怎么使用?
要使用IdleHandler只需要調用MessageQueue#addIdleHandler(IdleHandler handler)方法即
Looper.myQueue().addIdleHandler(new IdleHandler() { @Override public boolean queueIdle() { ... return false; } }
2.源碼分析
首先看下源碼的解釋
/** * Callback interface for discovering when a thread is going to block * waiting for more messages. */ public static interface IdleHandler { /** * Called when the message queue has run out of messages and will now * wait for more. Return true to keep your idle handler active, false * to have it removed. This may be called if there are still messages * pending in the queue, but they are all scheduled to be dispatched * after the current time. */ boolean queueIdle(); }
注釋中很明確地指出當消息隊列空閑時會執行IdleHandler的queueIdle()方法,該方法返回一個boolean值,
如果為false則執行完畢之后移除這條消息(一次性完事),
如果為true則保留,等到下次空閑時會再次執行(重復執行)。
看MessageQueue的源碼可以發現有兩處關於IdleHandler的聲明,分明是:
- 存放IdleHandler的ArrayList(mIdleHandlers),
- 還有一個IdleHandler數組(mPendingIdleHandlers)。
后面的數組,它里面放的IdleHandler實例都是臨時的,也就是每次使用完(調用了queueIdle方法)之后,都會置空(mPendingIdleHandlers[i] = null)
下面看下MessageQueue的next()方法可以發現確實是這樣
Message next() { ...... for (;;) { ...... synchronized (this) { // 此處為正常消息隊列的處理 ...... if (mQuitting) { dispose(); return null; } if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) { // No idle handlers to run. Loop and wait some more. 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; // release the reference to the handler 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; } }
就在MessageQueue的next方法里面。
大概流程是這樣的:
- 如果本次循環拿到的Message為空,或者!這個Message是一個延時的消息而且還沒到指定的觸發時間,那么,就認定當前的隊列為空閑狀態,
- 接着就會遍歷mPendingIdleHandlers數組(這個數組里面的元素每次都會到mIdleHandlers中去拿)來調用每一個IdleHandler實例的queueIdle方法,
- 如果這個方法返回false的話,那么這個實例就會從mIdleHandlers中移除,也就是當下次隊列空閑的時候,不會繼續回調它的queueIdle方法了。
處理完IdleHandler后會將nextPollTimeoutMillis設置為0,也就是不阻塞消息隊列,當然要注意這里執行的代碼同樣不能太耗時,因為它是同步執行的,如果太耗時肯定會影響后面的message執行。
能力大概就是上面講的那樣,那么能力決定用處,用處從本質上講就是趁着消息隊列空閑的時候干點事情,當然具體的用處還是要看具體的處理。
3.IdleHandler返回true和false帶來的不同結果
queueIdle() 方法如果返回 false,那么這個 IdleHandler 只會執行一次,執行完后會從隊列中刪除,如果返回 true,那么執行完后不會被刪除,只要執行 MessageQueue.next 時消息隊列中沒有可執行的消息,即為空閑時間,那么 IdleHandler 隊列中的 IdleHandler 還會繼續被執行。
public void clickTestIdleHandler(View view) { // 添加第一個 IdleHandler Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("Test","IdleHandler1 queueIdle"); return false; } }); // 添加第二個 IdleHandler Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("Test","IdleHandler2 queueIdle"); return false; } }); Handler handler = new Handler(); // 添加第一個Message Message message1 = Message.obtain(handler, new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("Test","message1"); } }); message1.sendToTarget(); // 添加第二個Message Message message2 = Message.obtain(handler, new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } Log.e("Test","message2"); } }); message2.sendToTarget(); }
上面的例子中分別向消息隊列中添加來兩個 IdleHandler 和兩個 Message,其中 IdleHandler 的 queueIdle() 方法返回 false,下面來看一下結果:
16:23:07.726 E/Test: message1 16:23:09.727 E/Test: message2 16:23:11.732 E/Test: IdleHandler1 queueIdle 16:23:13.733 E/Test: IdleHandler2 queueIdle
可以看到 IdleHandler 是在消息隊列的其它消息執行完后才執行的,而且只執行來一次。
再來看一下 queueIdle() 方法返回 true 的情況:
16:27:02.083 E/Test: message1 16:27:04.084 E/Test: message2 16:27:06.090 E/Test: IdleHandler1 queueIdle 16:27:08.090 E/Test: IdleHandler2 queueIdle 16:27:10.095 E/Test: IdleHandler1 queueIdle 16:27:12.096 E/Test: IdleHandler2 queueIdle 16:27:14.099 E/Test: IdleHandler1 queueIdle 16:27:16.099 E/Test: IdleHandler2 queueIdle 16:27:43.788 E/Test: IdleHandler1 queueIdle 16:27:45.788 E/Test: IdleHandler2 queueIdle 16:27:47.792 E/Test: IdleHandler1 queueIdle 16:27:49.793 E/Test: IdleHandler2 queueIdle
可以看到,當 queueIdle() 方法返回 true時會多次執行,即 IdleHandler 執行一次后不會從 IdleHandler 的隊列中刪除,等下次空閑時間到來時還會繼續執行。
4.來看看它在源碼里的使用場景:
比如在ActivityThread中,就有一個名叫GcIdler的內部類,實現了IdleHandler接口。
它在queueIdle方法被回調時,會做強行GC的操作(即調用BinderInternal的forceGc方法),但強行GC的前提是,與上一次強行GC至少相隔5秒以上。
ActivityThread中GcIdler
void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; //添加GC任務 Looper.myQueue().addIdleHandler(mGcIdler); } mH.removeMessages(H.GC_WHEN_IDLE); }
ActivityThread中GcIdler的詳細聲明:
//GC任務 final class GcIdler implements MessageQueue.IdleHandler { @Override public final boolean queueIdle() { doGcIfNeeded(); //執行后,就直接刪除 return false; } } // 判斷是否需要執行垃圾回收。 void doGcIfNeeded() { mGcIdlerScheduled = false; final long now = SystemClock.uptimeMillis(); //獲取上次GC的時間 if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) { //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!"); BinderInternal.forceGc("bg"); } }
GcIdler方法理解起來很簡單、就是獲取上次GC的時間,判斷是否需要GC操作。如果需要則進行GC操作。這里ActivityThread中還聲明了其他空閑時的任務。如果大家對其他空閑任務感興趣,可以自行研究。
那這個GcIdler會在什么時候使用呢?
當ActivityThread的mH(Handler)收到GC_WHEN_IDLE消息之后。
何時會收到GC_WHEN_IDLE消息?
當AMS(ActivityManagerService)中的這兩個方法被調用之后:
- doLowMemReportIfNeededLocked, 這個方法看名字就知道是不夠內存的時候調用的了。
- activityIdle, 這個方法呢,就是當ActivityThread的handleResumeActivity方法被調用時(Activity的onResume方法也是在這方法里回調)調用的。
5.其他可以想到的場景
1.Activity啟動優化:onCreate,onStart,onResume中耗時較短但非必要的代碼可以放到IdleHandler中執行,減少啟動時間
2.想要在一個View繪制完成之后添加其他依賴於這個View的View,當然這個用View#post()也能實現,區別就是前者會在消息隊列空閑時執行
3.發送一個返回true的IdleHandler,在里面讓某個View不停閃爍,這樣當用戶發呆時就可以誘導用戶點擊這個View,這也是種很酷的操作
4.一些第三方庫中有使用,比如LeakCanary,Glide中有使用到,具體可以自行去查看