IdleHandler原理及使用

一、什么是IdleHandler?

IdleHandlerandroid.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)景

  1. 在應(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í)行。
  2. 想要在一個(gè) View 繪制完成之后添加其他依賴于這個(gè) View 的 View,當(dāng)然這個(gè)用View.post()也能實(shí)現(xiàn),區(qū)別就是前者會(huì)在消息隊(duì)列空閑時(shí)執(zhí)行
  3. 發(fā)送一個(gè)返回 true 的 IdleHandler,在里面讓某個(gè) View 不停閃爍,這樣當(dāng)用戶發(fā)呆時(shí)就可以誘導(dǎo)用戶點(diǎn)擊這個(gè)View,這也是種很酷的操作

五、注意事項(xiàng)

  1. MessageQueue 提供了add/remove IdleHandler方法,但是我們不一定需要成對(duì)使用它們,因?yàn)镮dleHandler.queueIdle() 的返回值返回 false 的時(shí)候可以移除 IdleHanlder。
  2. 不要將一些不重要的啟動(dòng)服務(wù)放到 IdleHandler 中去管理,因?yàn)樗奶幚頃r(shí)機(jī)不可控,如果 MessageQueue 一直有待處理的消息,那么它的執(zhí)行時(shí)機(jī)會(huì)很靠后。
  3. 當(dāng) mIdleHanders 一直不為空時(shí),為什么不會(huì)進(jìn)入死循環(huán)?
  • 只有在 pendingIdleHandlerCount 為 -1 時(shí),才會(huì)嘗試執(zhí)行 mIdleHander;
  • pendingIdlehanderCount 在 next() 中初始時(shí)為 -1,執(zhí)行一遍后被置為 0,所以不會(huì)重復(fù)執(zhí)行;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 文章已授權(quán)『郭霖』公眾號(hào)發(fā)布 前言 很高興遇見(jiàn)你~ 歡迎閱讀我的文章。 關(guān)于Handler的博客可謂是俯拾皆是,而...
    一只修仙的猿閱讀 6,397評(píng)論 2 14
  • use for 相信于此,絕大多數(shù)同學(xué)都會(huì)回答消息機(jī)制是android 為了線程間通信而引入的工具。可以輕松的將一...
    漫步_蝸牛閱讀 528評(píng)論 0 1
  • 我會(huì)通過(guò)講解 Handler/Looper/MessageQueue/Message 這幾個(gè)類的作用以及它們之間的...
    realxz閱讀 604評(píng)論 0 8
  • 久違的晴天,家長(zhǎng)會(huì)。 家長(zhǎng)大會(huì)開(kāi)好到教室時(shí),離放學(xué)已經(jīng)沒(méi)多少時(shí)間了。班主任說(shuō)已經(jīng)安排了三個(gè)家長(zhǎng)分享經(jīng)驗(yàn)。 放學(xué)鈴聲...
    飄雪兒5閱讀 7,818評(píng)論 16 22
  • 今天感恩節(jié)哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開(kāi)了第一次的黨會(huì),身份的轉(zhuǎn)變要...
    余生動(dòng)聽(tīng)閱讀 10,840評(píng)論 0 11

友情鏈接更多精彩內(nèi)容