由線上問題引發的思考——View.post到底何時執行

前言

今天遇到一個很莫名其妙的問題,就是一個view有兩個狀態,狀態A與狀態B,在Activity的onCreate的時候先顯示狀態A,並在onCreate裏面請求接口,接口回來後顯示B。
斷點的時候,這個邏輯沒問題,但是放開斷點,很大機率顯示的是view的狀態A,原因就在於View.post()執行的時機不定,今天這篇文章就來追溯一下View.post()執行的時機。

使用場景

相信大家都知道,我們如果想在Activity.onCreate()裏面計算寬高,就可以使用View.post()的方法,並且,在沒有Handler的時候,我們可以拿某個View代替Handler的功能,使用View.post(),但是View.post()並不一定會執行,下面我們來看下源碼。

源碼跟蹤

    /**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     *
     * @param action The Runnable that will be executed.
     *
     * @return Returns true if the Runnable was successfully placed in to the
     *         message queue.  Returns false on failure, usually because the
     *         looper processing the message queue is exiting.
     *
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            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;
    }

相信大家跟我一樣,很容易忽略,原來post是有返回值的,不過這個返回值其實沒有意義,註釋也說了,一般是隊列退出的時候才這樣。
從這裏可以看到,這裏的attachInfo很關鍵,這個attchInfo是什麼東西呢?
其實,在之前的博客隱約提過這個,attachInfo是在ViewRootImpl裏面賦值的,ViewRootImpl是個很複雜的類,幾乎跟view有關的所有核心代碼都在這裏面,他也是連接WMS和View的橋樑,是所有View的根View,注意,ViewRootImpl並不是個View,只是View。網上關於ViewRoot的解析文章汗牛充棟,樓主也是看他們的文章,這裏就不打算自己寫了,只聊聊我們這裏的問題。
View中AttchInfo的賦值是在performTraversals裏面

//ViewRootImpl performTraversals()

        Rect frame = mWinFrame;
        if (mFirst) {
        	.....省略代碼
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
	        ...
        }
       //View dispatchOnWindowAttachedChange()
    public void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...省略
        if (mRunQueue != null) {
        //這裏就是消耗的地方
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
    }     
       

但是,ViewRotImpl裏面的AttachInfo的卻是在初始化裏面

public ViewRootImpl(Context context, Display display) {
	mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
}

由此可見,mAttachInfo的賦值,是在perfomTravels裏面,而perfomTravels又是在響應onVsync信號的時候才賦值,而監聽垂直信號量又是在Activity的生命週期performRusume()裏面的wm.addView()裏面執行的。由此可見,在onCreate裏面執行的post,並不會執行,而是會走getRunQueue().post(action);

    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            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;
    }

    /**
     * Returns the queue of runnable for this view.
     *
     * @return the queue of runnables for this view
     */
    private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }

HandlerActionQueue是什麼呢?官方對於這個類的解釋已經很清楚了

Class used to enqueue pending work from Views when no Handler is attached.

也就是說,是在View沒有被attach的時候,臨時存儲工作的。


    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];
            }
            //這個地方其實有一個小小的疑惑,那就是GrowingArrayUtils是V7包裏面的類,android原生包並沒有,這裏是怎麼調用成功的。又或者GrowingArrayUtils其實存在只是被隱藏了?
            mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
            mCount++;
        }
    }

HanderAction就是用來存儲Runnable和delay時間的。
而HandlerAction的消耗就是在executeActions,上面已經在代碼裏面寫過,就是在View.dispatchAttachedToWindow()裏面


    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;
        }
    }

好了,這個問題基本在這裏就解決了,那麼,上面幾個問題答案也就出來了。

Activity.onCreate()裏面計算寬高

弄清楚這個問題,我們就需要搞清楚,在dispatchAttachedToWindow之前,view寬高是否已經測量好了。這個問題其實也很簡單,留給讀者自己去閱讀源碼,因爲View的測量繪製時超級麻煩的情況,過段時間再把這一塊補上。

View.post()並不一定會執行

這就是因爲view不一定會AttachTo到ViewRootImpl上面,如果沒有添加到這個ViewRoot裏面,顯然就不會執行了。並且,前面也分析過,這個執行時間是無法確定的,所以經常出現斷點時可以,但是放開斷點就有問題的情況。

後記

這個知識點很危險,如果不知道,就經常容易用錯,由此可見,平時多看源碼,多看博客是多麼重要。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章