一、什么是IdleHandler?
IdleHandler是android.os.MessageQueue的一個(gè)內(nèi)部接口,其定義如下
/**
* 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();
}
可以把它理解為Runnable。IdleHandler會(huì)在MessageQueue中沒(méi)有Message要處理或者要處理的Message都是延時(shí)任務(wù)的時(shí)候得到執(zhí)行,這種特性很重要,因?yàn)楫?dāng)MessageQueue中沒(méi)有Message要處理或者要處理的Message都是延時(shí)任務(wù)的時(shí)候,就表明當(dāng)前線程為空閑狀態(tài),如果是在主線程,則表明當(dāng)前UI沒(méi)有繪制動(dòng)作,所以可以根據(jù)監(jiān)聽(tīng)IdleHandler是否執(zhí)行來(lái)判斷UI是否繪制完成,從而避免在UI繪制的時(shí)候進(jìn)行耗時(shí)操作,影響UI繪制效率。
二、IdleHandler原理
上面說(shuō)到IdleHander會(huì)在MessageQueue處于空閑狀態(tài)時(shí)執(zhí)行,既然跟MessageQueue有關(guān),那就很容易讓人想到Handler原理。簡(jiǎn)單介紹一下Handler原理,就是Handler將任務(wù)封裝成Message,通過(guò)與Handler綁定的Looper,放到MessageQueue里面,Looper會(huì)不斷從MessageQueue里去取Message執(zhí)行。一旦當(dāng)前MessageQueue沒(méi)有任務(wù)需要執(zhí)行,那么就會(huì)觸發(fā)IdleHandler執(zhí)行,所以這一步的核心就是在MessageQueue中取Message,因?yàn)橹挥腥〔坏讲艜?huì)觸發(fā)IdleHandler執(zhí)行邏輯。下面是MessageQueue#next()方法:
Message next() {
...
int pendingIdleHandlerCount = -1; // -1 only during first iteration
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
...
-----------------------IdleHanlder處理邏輯--------------------
// 當(dāng)mMessages鏈表為null或者下一個(gè)需要執(zhí)行的Message是個(gè)delay任務(wù)
// 的時(shí)候,開(kāi)始統(tǒng)計(jì)IdlerHandler數(shù)量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 如果沒(méi)有IdlerHandler,則進(jìn)行下一個(gè)循環(huán)
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
// 新建一個(gè)數(shù)組,用來(lái)復(fù)制存放IdleHandler,因?yàn)楹竺鏁?huì)對(duì)mIdleHandlers進(jìn)行remove元素操作
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 執(zhí)行IdleHandler
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);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
-----------------------IdleHanlder處理邏輯--------------------
...
}
}
在取Message的時(shí)候,如果當(dāng)前沒(méi)有需要處理的Message,就會(huì)進(jìn)入IdleHandler處理邏輯,IdleHandler執(zhí)行的返回值表示需不需要從mIdleHandlers中移除該IdleHandler。
這里有一個(gè)細(xì)節(jié)需要注意,pendingIdleHandlerCount的初始值為-1,在執(zhí)行完所有
IdleHandler后會(huì)被置成0,而這里僅當(dāng)pendingIdleHandlerCount < 0的時(shí)候才會(huì)進(jìn)入,這樣就保證了所有IdleHandler只會(huì)在MessageQueue#next()的循環(huán)里執(zhí)行一次。下一次通過(guò)Looper#loop()進(jìn)入MessageQueue#next()的時(shí)候pendingIdleHandlerCount會(huì)重置為-1,繼續(xù)判斷是不是Idle狀態(tài)。
另外,當(dāng)pendingIdleHandlerCount=0的時(shí)候,mBlocked會(huì)被置為true,這個(gè)字段涉及線程喚醒的操作,有興趣的同學(xué)可以自行查閱源碼。
三、IdleHandler使用
MessageQueue提供了添加IdleHandler的方法
/**
* Add a new {@link IdleHandler} to this message queue. This may be
* removed automatically for you by returning false from
* {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
* invoked, or explicitly removing it with {@link #removeIdleHandler}.
*
* <p>This method is safe to call from any thread.
*
* @param handler The IdleHandler to be added.
*/
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
下面通過(guò)一個(gè)例子展示怎么使用IdleHandler
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Looper.getMainLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.e("DBL", "queueIdle");
// UI第一幀繪制完成(可以理解為頁(yè)面可見(jiàn))
return false; // 返回false表示MessageQueue在執(zhí)行完這段代碼后將該IdleHandler刪除,反之不刪除,下一次繼續(xù)執(zhí)行
}
});
}
@Override
protected void onResume() {
super.onResume();
Log.e("DBL", "onResume");
}
上面例子將IdleHandler添加到主線程MessageQueue中,queueIdle()方法回調(diào),說(shuō)明UI第一幀繪制完成,可以理解為UI首次可見(jiàn),這個(gè)比onResume精確的多,因?yàn)閛nResume回調(diào)的時(shí)候界面還沒(méi)有開(kāi)始繪制,此時(shí)界面是不可見(jiàn)的。
四、IdleHandler應(yīng)用場(chǎng)景
- 在應(yīng)用啟動(dòng)時(shí)我們可能希望把一些優(yōu)先級(jí)沒(méi)那么高的操作延遲一點(diǎn)處理,一般會(huì)使用 Handler.postDelayed(Runnable r, long delayMillis)來(lái)實(shí)現(xiàn),但是又不知道該延遲多少時(shí)間比較合適,因?yàn)槭謾C(jī)性能不同,有的性能較差可能需要延遲較多,有的性能較好可以允許較少的延遲時(shí)間。所以在做項(xiàng)目性能優(yōu)化的時(shí)候可以使用 IdleHandler,它在主線程空閑時(shí)執(zhí)行任務(wù),而不影響其他任務(wù)的執(zhí)行。
- 想要在一個(gè) View 繪制完成之后添加其他依賴于這個(gè) View 的 View,當(dāng)然這個(gè)用View.post()也能實(shí)現(xiàn),區(qū)別就是前者會(huì)在消息隊(duì)列空閑時(shí)執(zhí)行
- 發(fā)送一個(gè)返回 true 的 IdleHandler,在里面讓某個(gè) View 不停閃爍,這樣當(dāng)用戶發(fā)呆時(shí)就可以誘導(dǎo)用戶點(diǎn)擊這個(gè)View,這也是種很酷的操作
五、注意事項(xiàng)
- MessageQueue 提供了add/remove IdleHandler方法,但是我們不一定需要成對(duì)使用它們,因?yàn)镮dleHandler.queueIdle() 的返回值返回 false 的時(shí)候可以移除 IdleHanlder。
- 不要將一些不重要的啟動(dòng)服務(wù)放到 IdleHandler 中去管理,因?yàn)樗奶幚頃r(shí)機(jī)不可控,如果 MessageQueue 一直有待處理的消息,那么它的執(zhí)行時(shí)機(jī)會(huì)很靠后。
- 當(dāng) mIdleHanders 一直不為空時(shí),為什么不會(huì)進(jìn)入死循環(huán)?
- 只有在 pendingIdleHandlerCount 為 -1 時(shí),才會(huì)嘗試執(zhí)行 mIdleHander;
- pendingIdlehanderCount 在 next() 中初始時(shí)為 -1,執(zhí)行一遍后被置為 0,所以不會(huì)重復(fù)執(zhí)行;