1. 程式人生 > 實用技巧 >基於CefSharp開發(六)瀏覽器網頁縮放

基於CefSharp開發(六)瀏覽器網頁縮放

緣起

在Android開發中,我們經常會見到下面的程式碼,比如:

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===");
            }
        });
    }

你曾經有沒有想過這兩者到底有什麼區別?我該使用哪種呢?

常見的Handler.post揭祕

Handler的工作機制,網上介紹的文章太多了,這裡我就不贅述了,想繼續瞭解的同學可以參考下這篇文章:Handler原始碼分析。一句話總結就是通過Handler物件,不論是post Msg還是Runnable,最終都是構造了一個Msg物件,插入到與之對應的Looper的MessageQueue中,不同的是Running時msg物件的callback欄位設成了Runnable的值。稍後這條msg會從佇列中取出來並且得到執行,UI執行緒就是這麼一個基於事件的迴圈。所以可以看出Handler.post相關的程式碼在onCreate裡那一刻時就已經開始了執行(加入到了佇列,下次迴圈到來時就會被真正執行了)。

View.post揭祕

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

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

        // 仔細閱讀下面這段註釋!!!
        // 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欄位在這裡比較關鍵,當其有值時,其實和普通的Handler.post就沒區別了,但有時它是沒值的,比如我們上面示例程式碼裡的onCreate階段,那麼這時執行到了getRunQueue().post(action);這行程式碼,從這段註釋也大概可以看出來真正的執行會被延遲(這裡的Postpone註釋);我們接著往下看看getRunQueue相關的程式碼,如下:

/**  其實這段註釋已經說的很清楚明瞭了!!!
     * 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(還沒對應的handler)時,客戶程式碼發起的post呼叫的,起了一個臨時快取的作用。不然總不能丟棄吧,這樣開發體驗就太差了!!!
緊接著,我們繼續看下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);
        }
    }
}

注意:這裡的原始碼部分,我們只摘錄了部分關鍵程式碼,其餘不太相關的直接略去了。
從這裡可以看出,我們前面的View.post呼叫裡的Runnable最終會被儲存在這裡的mActions數組裡,這裡最關鍵的一點就是其executeActions方法,因為這個方法裡我們之前post的Runnable才真正通過handler.postDelayed方式使其進入handler對應的訊息佇列裡等待執行;

到此為止,我們還差知道View裡的mAttachInfo欄位何時被賦值以及這裡的executeActions方法是什麼時候被觸發的,答案就是在View的dispatchAttachedToWindow方法,其關鍵原始碼如下:

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();
        ...
}

而通過之前的文章,我們已經知道了此方法是當Act Resume之後,在ViewRootImpl.performTraversals()中觸發的,參考View.onAttachedToWindow呼叫時機分析

總結

  1. Handler.post,它的執行時間基本是等同於onCreate裡那行程式碼觸達的時間;

  2. View.post,則不同,它說白了執行時間一定是在Act#onResume發生後才開始算的;或者換句話說它的效果相當於你上面的View.post方法是寫在Act#onResume裡面的(但只執行一次,因為onCreate不像onResume會被多次觸發);

  3. 當然,雖然這裡說的是post方法,但對應的postDelayed方法區別也是類似的。

平時當你專案很小,MainActivity的邏輯也很簡單時是看不出啥區別的,但當act的onCreateonResume之間耗時比較久時(比如2s以上),就能明顯感受到這2者的區別了,而且本身它們的實際含義也是很不同的,前者的Runnable真正執行時,可能act的整個view層次都還沒完整的measure、layout完成,但後者的Runnable執行時,則一定能保證act的view層次結構已經measure、layout並且至少繪製完成了一次。



作者:tmp_zhao
連結:https://www.jianshu.com/p/7280b2d3b4d1
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。