Android View.post()原理分析

一.背景

???????別人問:View.post() 為什么能夠獲取到 View 的寬高 ?
???????別人答案:post()是在View繪制完成后執(zhí)行;
???????仔細一想:View必須在繪制完成后才能得到寬高,那么post()又是在View繪制完成后執(zhí)行,那么post()內(nèi)就能夠獲取到View的寬高。
???????帶著問題和答案,沒有別的途徑,只能通過源碼來找到答案,下面就直接從View.post()執(zhí)行開始:

二.View.post()

???????通過源碼,先看一下View內(nèi)部post()執(zhí)行邏輯:

a.View.java
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()內(nèi)部有兩個分支可能執(zhí)行:
???????1.attachInfo對象不為空,執(zhí)行通過attachInfo內(nèi)部的Handler執(zhí)行Runnable;
???????2.若attachInfo為空,則執(zhí)行getRunQueue().post();
???????分支1涉及到AttachInfo,該AttachInfo涉及到View的繪制流程,后面進行詳細分析,先分析分支2中的getRunQueue():

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

???????getRunQueue()內(nèi)部創(chuàng)建了一個HandlerActionQueue對象,再看一下HandlerActionQueue這個類:

b.HandlerActionQueue.java
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);
        }
    }
}

???????HandlerActionQueue是一個初始容量是4的 HandlerAction數(shù)組。HandlerAction有兩個成員變量:需要執(zhí)行的Runnable和延遲執(zhí)行的時間。
???????隊列的執(zhí)行邏輯在 executeActions(handler) 方法中,通過傳入的handler進行任務分發(fā)。
???????前面分析到:在View.java內(nèi)的getRunQueue()內(nèi)部創(chuàng)建了HandlerActionQueue對象mRunQueue,那么executeActions()的調用也應該是在View內(nèi)部,通過搜索發(fā)現(xiàn),在View.java內(nèi)部有一處調用到了executeActions()方法,一起看一下dispatchAttachedToWindow():

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

    ......
    ......
}

???????在dispatchAttachedToWindow()內(nèi)部主要執(zhí)行了兩件事:
???????1.對mAttachInfo進行賦值,也就是說:如果沒有執(zhí)行dispatchAttachedToWindow(),那么mAttachInfo為空,post()內(nèi)部也就不會執(zhí)行分支1;
???????2.執(zhí)行HandlerActionQueue的executeActions()方法來執(zhí)行runnable;
???????那么接下來需要分析dispatchAttachedToWindow()是在什么地方被調用的?熟悉View繪制流程可以參考之前寫的一篇文章Android Activity 顯示詳解,可以清楚的看到在ViewRootImpl內(nèi)部會執(zhí)行dispatchAttachedToWindow(),再一起看一下:

c.ViewRootImpl.java

???????先看一下ViewRootImpl的創(chuàng)建,即ViewRootImpl的構造方法,此處只分析跟View.post()相關的知識點:

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

???????可以看到,在ViewRootImpl的構造方法內(nèi)部,創(chuàng)建了AttachInfo對象和Choreographer實例(執(zhí)行繪制流程Runnable)。AttachInfo傳入了mHandler,后續(xù)的任務執(zhí)行都是通過該Handler,簡單看一下mHandler的創(chuàng)建:

final ViewRootHandler mHandler = new ViewRootHandler();
final class ViewRootHandler extends Handler {
}

???????該Handler是使用的是默認的Looper,即主線程的Looper,因此該Handler進行的消息處理是工作在主線程。
???????然后根據(jù)繪制的流程,一步一步的最終執(zhí)行到熟悉的performTraversals()方法:

private void performTraversals() {
    final View host = mView;
    ......
    ......
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    ......
    //Measure()
    //Layout()
    //Draw()
    ......
}

???????在performTraversals()內(nèi)部執(zhí)行了host的dispatchAttachedToWindow()方法,將創(chuàng)建的AttachInfo對象作為參數(shù)傳入,該host是ViewGroup(setContentView加載的LayoutID),看一下ViewGroup內(nèi)dispatchAttachedToWindow()的執(zhí)行邏輯:

@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    .....
    super.dispatchAttachedToWindow(info, visibility);
    .....

    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        child.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, child.getVisibility()));
    }
    ......
}

???????可以看到,在ViewGroup內(nèi)部的dispatchAttachedToWindow()會遍歷子View,來執(zhí)行子View的dispatchAttachedToWindow()方法,前面分析到,在View的dispatchAttachedToWindow()內(nèi)部執(zhí)行了AttachInfo對象的賦值操作,所有的子View共用一個AttachInfo對象。
???????開始看到這里有一點疑惑:明明是先調用的 dispatchAttachedToWindow() ,再進行的Measure()、Layout()、Draw(),為什么 dispatchAttachedToWindow() 中可以獲取到View的寬高呢?
???????performTraversals()是在主線程消息隊列的一次消息處理過程中執(zhí)行的,而dispatchAttachedToWindow()間接調用的mRunQueue.executeActions() 發(fā)送的任務也是通過Handler發(fā)送到主線程消息隊列的,由于Handler執(zhí)行的有序性,那么它的執(zhí)行就一定在這次的performTraversals()方法執(zhí)行完之后,因此在post()里面是可以獲取View的寬高的。

簡單總結

???????執(zhí)行View.post()后,根據(jù)AttachInfo是否為空,即ViewRootImpl是否已經(jīng)創(chuàng)建,View.post() 會執(zhí)行不同的邏輯:
???????分支1:ViewRootImpl 已經(jīng)創(chuàng)建,即mAttachInfo已經(jīng)初始化,直接通過Handler發(fā)送消息來執(zhí)行任務。
???????分支2:ViewRootImpl 未創(chuàng)建,即View尚未開始繪制,會將任務保存為 HandlerAction,暫存在HandlerActionQueue 中,等到View開始繪制,執(zhí)行 performTraversal() 方法時,在 dispatchAttachedToWindow() 方法中通過Handler分發(fā)執(zhí)行HandlerActionQueue中暫存的任務。

流程圖如下:
image.png

三. 問題延伸

???????通過上面的分析,我們可以看到,在執(zhí)行View.post()后,如果AttachInfo沒有創(chuàng)建,則會先加入HandlerActionQueue中,不會立刻執(zhí)行,需要等View繪制完成才能執(zhí)行post(),結合View的繪制流程,會有以下延伸問題:

a.為什么onCreate()使用view.post()無法立刻執(zhí)行任務?

???????還是從View繪制原理出發(fā),View的繪制是在onResume()后進行繪制的,在onCreate()時,View還未進行繪制,所以AttachInfo也就沒有被創(chuàng)建,那么就只能將起放入隊列里面,后續(xù)等繪制完成后才能執(zhí)行任務。

b.若只是創(chuàng)建一個 View,調用它的post(),那么post的任務會不會被執(zhí)行?
View view = new View(this);
view.post(new Runnable() {
    @Override
    public void run() {
        //...................
    }
});

???????還是從View繪制原理出發(fā),不會。每個View中post() 需執(zhí)行的任務,必須得添加到窗口視圖--->執(zhí)行繪制流程,任務才會被post到消息隊列里去等待執(zhí)行,即依賴于dispatchAttachedToWindow ();
???????若View未添加到窗口視圖,那么就不會走繪制流程,post() 添加的任務最終不會被post到消息隊列里,即得不到執(zhí)行。(但會保存到HandlerAction數(shù)組里)
???????上述例子,因為它沒有被添加到窗口視圖,所以不會走繪制流程,所以該任務最終不會被post到消息隊列里執(zhí)行,執(zhí)行addView(view)就可以進行繪制然后post()任務就可以執(zhí)行了。

c.View.pos()傳入的任務被執(zhí)行的有效期

???????通過post()代碼可以看到,只要attachInfo對象不為空,那么任務就會被執(zhí)行,attachInfo賦值是在dispatchAttachedToWindow(),那么置空是在什么地方呢?

void dispatchDetachedFromWindow() {
    AttachInfo info = mAttachInfo;
    if (info != null) {
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            onWindowVisibilityChanged(GONE);
            ......
        }
    }

    onDetachedFromWindow();
    onDetachedFromWindowInternal();

    .......

    mAttachInfo = null;
    .....
}

???????通過分析源碼知道,Activity在執(zhí)行Destroy()后的執(zhí)行流程如下:


image.png

???????通過以上可以看到:View.post() 任務被執(zhí)行的有效期是在 Activity 生命周期 onDestory()后。

以上就是對View.post()執(zhí)行原理及延伸問題的分析!

???????感謝該文章http://www.itdecent.cn/p/e2a8cd384eda作者的分析。

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

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

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