Handler.post和View.post的區(qū)別

緣起

在Android開發(fā)中,我們經(jīng)常會見到下面的代碼,比如:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.out.println("onCreate===");
        setContentView(R.layout.activity_main);

        rootBtn = findViewById(R.id.rootBtn);
        // 代碼1
        UIHandler.post(new Runnable() {
            @Override
            public void run() {
                System.out.println("Handler.post===");
            }
        });
        // 代碼2
        rootBtn.post(new Runnable() {
            @Override
            public void run() {
                System.out.println("View.post===");
            }
        });
    }

你曾經(jīng)有沒有想過這兩者到底有什么區(qū)別?我該使用哪種呢?

常見的Handler.post揭秘

Handler的工作機(jī)制,網(wǎng)上介紹的文章太多了,這里我就不贅述了,想繼續(xù)了解的同學(xué)可以參考下這篇文章:Handler源碼分析。一句話總結(jié)就是通過Handler對象,不論是post Msg還是Runnable,最終都是構(gòu)造了一個Msg對象,插入到與之對應(yīng)的Looper的MessageQueue中,不同的是Running時(shí)msg對象的callback字段設(shè)成了Runnable的值。稍后這條msg會從隊(duì)列中取出來并且得到執(zhí)行,UI線程就是這么一個基于事件的循環(huán)。所以可以看出Handler.post相關(guān)的代碼在onCreate里那一刻時(shí)就已經(jīng)開始了執(zhí)行(加入到了隊(duì)列,下次循環(huán)到來時(shí)就會被真正執(zhí)行了)。

View.post揭秘

要解釋它的行為,我們就必須深入代碼中去找答案了,其代碼如下:

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            // 注意這個判斷,這個變量時(shí)機(jī)太早的話是沒值的,
           // 比如在act#onCreate階段 
            return attachInfo.mHandler.post(action);
        }

        // 仔細(xì)閱讀下面這段注釋?。?!
        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().post(action);
        return true;
    }

從上面的源碼,我們大概可以看出mAttachInfo字段在這里比較關(guān)鍵,當(dāng)其有值時(shí),其實(shí)和普通的Handler.post就沒區(qū)別了,但有時(shí)它是沒值的,比如我們上面示例代碼里的onCreate階段,那么這時(shí)執(zhí)行到了getRunQueue().post(action);這行代碼,從這段注釋也大概可以看出來真正的執(zhí)行會被延遲(這里的Postpone注釋);我們接著往下看看getRunQueue相關(guān)的代碼,如下:

/**  其實(shí)這段注釋已經(jīng)說的很清楚明了了?。。?     * Queue of pending runnables. Used to postpone calls to post() until this
     * view is attached and has a handler.
     */
private HandlerActionQueue mRunQueue;

private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

從上面我們可以看出,mRunQueue就是View用來處理它還沒attach到window(還沒對應(yīng)的handler)時(shí),客戶代碼發(fā)起的post調(diào)用的,起了一個臨時(shí)緩存的作用。不然總不能丟棄吧,這樣開發(fā)體驗(yàn)就太差了!??!
緊接著,我們繼續(xù)看下HandlerActionQueue類型的定義,代碼如下:

public class HandlerActionQueue {
    private HandlerAction[] mActions; 
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4];
            }
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0;
        }
    }

    private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null
                    || action != null && action.equals(otherAction);
        }
    }
}

注意:這里的源碼部分,我們只摘錄了部分關(guān)鍵代碼,其余不太相關(guān)的直接略去了。
從這里可以看出,我們前面的View.post調(diào)用里的Runnable最終會被存儲在這里的mActions數(shù)組里,這里最關(guān)鍵的一點(diǎn)就是其executeActions方法,因?yàn)檫@個方法里我們之前post的Runnable才真正通過handler.postDelayed方式使其進(jìn)入handler對應(yīng)的消息隊(duì)列里等待執(zhí)行;

到此為止,我們還差知道View里的mAttachInfo字段何時(shí)被賦值以及這里的executeActions方法是什么時(shí)候被觸發(fā)的,答案就是在View的dispatchAttachedToWindow方法,其關(guān)鍵源碼如下:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        // Transfer all pending runnables.
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();
        ...
}

而通過之前的文章,我們已經(jīng)知道了此方法是當(dāng)Act Resume之后,在ViewRootImpl.performTraversals()中觸發(fā)的,參考View.onAttachedToWindow調(diào)用時(shí)機(jī)分析。

總結(jié)

  1. Handler.post,它的執(zhí)行時(shí)間基本是等同于onCreate里那行代碼觸達(dá)的時(shí)間;

  2. View.post,則不同,它說白了執(zhí)行時(shí)間一定是在Act#onResume發(fā)生后才開始算的;或者換句話說它的效果相當(dāng)于你上面的View.post方法是寫在Act#onResume里面的(但只執(zhí)行一次,因?yàn)閛nCreate不像onResume會被多次觸發(fā));

  3. 當(dāng)然,雖然這里說的是post方法,但對應(yīng)的postDelayed方法區(qū)別也是類似的。

平時(shí)當(dāng)你項(xiàng)目很小,MainActivity的邏輯也很簡單時(shí)是看不出啥區(qū)別的,但當(dāng)act的onCreateonResume之間耗時(shí)比較久時(shí)(比如2s以上),就能明顯感受到這2者的區(qū)別了,而且本身它們的實(shí)際含義也是很不同的,前者的Runnable真正執(zhí)行時(shí),可能act的整個view層次都還沒完整的measure、layout完成,但后者的Runnable執(zhí)行時(shí),則一定能保證act的view層次結(jié)構(gòu)已經(jīng)measure、layout并且至少繪制完成了一次。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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