Android延遲加載方案之IdleHandler

一胳挎、背景

我們在做啟動性能優(yōu)化的時候饼疙,需要盡可能多地減少啟動階段主線程執(zhí)行的任務(wù)時長。對一些非啟動階段一定需要完成的任務(wù)慕爬,我們可以把他放到應(yīng)用啟動完成之后去執(zhí)行窑眯,這就是啟動性能優(yōu)化中的延遲加載方案。

二医窿、常規(guī)方案

一般的方案是通過handler.postDelay延遲一段時間執(zhí)行磅甩。但這種方案延遲的時間不好把握,配置高的機(jī)器和配置低的機(jī)器時間也不一樣姥卢。而且如果延遲執(zhí)行的任務(wù)較多卷要, 且需要在主線程中執(zhí)行,則在執(zhí)行延遲任務(wù)時會因?yàn)橐淮涡詧?zhí)行多個任務(wù)而導(dǎo)致主線程被占用一段時間造成用戶操作卡頓独榴。

三僧叉、更優(yōu)方案

IdleHandler能在當(dāng)前線程消息隊(duì)列空閑時執(zhí)行一些事情,且可以一個個任務(wù)單獨(dú)執(zhí)行括眠,不用一次性執(zhí)行所有任務(wù)彪标,緩解主線程卡頓現(xiàn)象。使用方法如下:

//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle() {  
        //你要處理的事情
        return false;    
    }  
});

queueIdle返回false表示只執(zhí)行一次掷豺,如果返回true捞烟,則下次在消息隊(duì)列空閑的時候還會執(zhí)行,這樣就可以實(shí)現(xiàn)一個任務(wù)一個任務(wù)的執(zhí)行当船,不會一次性占用當(dāng)前線程過多時間而造成卡頓题画。

四、IdleHandler源碼分析

/**
 * 獲取當(dāng)前線程隊(duì)列使用Looper.myQueue()德频,獲取主線程隊(duì)列可用getMainLooper().myQueue()
 */
public final class MessageQueue {
    ......
    /**
     * 當(dāng)前隊(duì)列將進(jìn)入阻塞等待消息時調(diào)用該接口回調(diào)苍息,即隊(duì)列空閑
     */
    public static interface IdleHandler {
        /**
         * 返回true就是單次回調(diào)后不刪除,下次進(jìn)入空閑時繼續(xù)回調(diào)該方法壹置,false只回調(diào)單次竞思。
         */
        boolean queueIdle();
    }

    /**
     * <p>This method is safe to call from any thread.
     * 判斷當(dāng)前隊(duì)列是不是空閑的,輔助方法
     */
    public boolean isIdle() {
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            return mMessages == null || now < mMessages.when;
        }
    }

    /**
     * <p>This method is safe to call from any thread.
     * 添加一個IdleHandler到隊(duì)列钞护,如果IdleHandler接口方法返回false則執(zhí)行完會自動刪除盖喷,
     * 否則需要手動removeIdleHandler。
     */
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }

    /**
     * <p>This method is safe to call from any thread.
     * 刪除一個之前添加的 IdleHandler难咕。
     */
    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }
    ......
    //Looper的prepare()方法會通過ThreadLocal準(zhǔn)備當(dāng)前線程的MessageQueue實(shí)例课梳,
    //然后在loop()方法中死循環(huán)調(diào)用當(dāng)前隊(duì)列的next()方法獲取Message距辆。
    Message next() {
        ......
        for (;;) {
            ......
            nativePollOnce(ptr, nextPollTimeoutMillis);
            synchronized (this) {
                ......
                //把通過addIdleHandler添加的IdleHandler轉(zhuǎn)成數(shù)組存起來在mPendingIdleHandlers中
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //循環(huán)遍歷所有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 {
                    //調(diào)用IdleHandler接口的queueIdle方法并獲取返回值。
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }
                //如果IdleHandler接口的queueIdle方法返回false說明只執(zhí)行一次需要刪除暮刃。
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }
            ......
        }
    }
}

五跨算、總結(jié)

綜上所述,在設(shè)計(jì)延遲加載方案的時候椭懊,可以考慮用IdleHandler這種優(yōu)雅的方式來實(shí)現(xiàn)诸蚕,而不是寫死個時間延遲執(zhí)行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灾搏,一起剝皮案震驚了整個濱河市挫望,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌狂窑,老刑警劉巖媳板,帶你破解...
    沈念sama閱讀 221,695評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異泉哈,居然都是意外死亡蛉幸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評論 3 399
  • 文/潘曉璐 我一進(jìn)店門丛晦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奕纫,“玉大人,你說我怎么就攤上這事烫沙∑ゲ悖” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評論 0 360
  • 文/不壞的土叔 我叫張陵锌蓄,是天一觀的道長升筏。 經(jīng)常有香客問我,道長瘸爽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,648評論 1 297
  • 正文 為了忘掉前任剪决,我火速辦了婚禮灵汪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柑潦。我一直安慰自己享言,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評論 6 397
  • 文/花漫 我一把揭開白布渗鬼。 她就那樣靜靜地躺著担锤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乍钻。 梳的紋絲不亂的頭發(fā)上肛循,一...
    開封第一講書人閱讀 52,268評論 1 309
  • 那天,我揣著相機(jī)與錄音银择,去河邊找鬼多糠。 笑死,一個胖子當(dāng)著我的面吹牛浩考,可吹牛的內(nèi)容都是我干的夹孔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼析孽,長吁一口氣:“原來是場噩夢啊……” “哼搭伤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起袜瞬,我...
    開封第一講書人閱讀 39,740評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怜俐,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后邓尤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拍鲤,經(jīng)...
    沈念sama閱讀 46,286評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評論 3 340
  • 正文 我和宋清朗相戀三年汞扎,在試婚紗的時候發(fā)現(xiàn)自己被綠了季稳。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡澈魄,死狀恐怖景鼠,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情痹扇,我是刑警寧澤铛漓,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站帘营,受9級特大地震影響票渠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芬迄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評論 3 333
  • 文/蒙蒙 一问顷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧禀梳,春花似錦杜窄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘴瓤,卻和暖如春扫外,著一層夾襖步出監(jiān)牢的瞬間莉钙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評論 1 272
  • 我被黑心中介騙來泰國打工筛谚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留磁玉,地道東北人。 一個月前我還...
    沈念sama閱讀 48,921評論 3 376
  • 正文 我出身青樓驾讲,卻偏偏與公主長得像蚊伞,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吮铭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評論 2 359