Android 之你真的了解 View.post() 原理嗎?

UI 優(yōu)化系列專題,來聊一聊 Android 渲染相關(guān)知識,主要涉及 UI 渲染背景知識如何優(yōu)化 UI 渲染兩部分內(nèi)容。


UI 優(yōu)化系列專題
  • UI 渲染背景知識

View 繪制流程之 setContentView() 到底做了什么?
View 繪制流程之 DecorView 添加至窗口的過程
深入 Activity 三部曲(3)View 繪制流程
Android 之 LayoutInflater 全面解析
關(guān)于渲染,你需要了解什么?
Android 之 Choreographer 詳細分析

  • 如何優(yōu)化 UI 渲染

Android 之如何優(yōu)化 UI 渲染(上)
Android 之如何優(yōu)化 UI 渲染(下)


關(guān)于 View.post() 相信每個 Android 開發(fā)人員都不會感到陌生,它最常見的場景主要有兩種。

  1. 更新 UI 操作

  2. 獲取 View 的實際寬高

view.post() 的內(nèi)部也是調(diào)用了 Handler,這可能是絕大多數(shù)開發(fā)人員所了解的,從本質(zhì)來說這樣理解并沒有錯,不過它并能解釋上面提出的第 2 個場景。

在 Activity 中,View 繪制流程的開始時機是在 ActivityThread 的 handleResumeActivity 方法,在該方法首先完成 Activity 生命周期 onResume 方法回調(diào),然后開始 View 繪制任務(wù)。也就是說 View 繪制流程要在 onResume 方法之后,但是我們絕大部分業(yè)務(wù)是在 onCreate 方法,比如要獲取某個 View 的實際寬高,由于 View 的繪制任務(wù)還未開始,所以就無法正確獲取。具體可以參考《View 繪制流程之 setContentView() 到底做了什么 ?

此時大家肯定使用過 View.post() 來解決該問題,注意 View 繪制流程也是向 Handler 添加任務(wù),如果在 onCreate 方法直接使用 Handler.post(),則該任務(wù)一定在 View 繪制任務(wù)之前(同一個線程隊列機制)。

  • 注意這里不考慮使用 ViewTreeObserver 或更長延遲的 postDelayed()。

那 View.post() 內(nèi)部也是使用 Handler,它是如何實現(xiàn)的呢?簡單來說,View.post() 對任務(wù)的運行時機做了調(diào)整。


View.post()

翻開 View 源碼,找到 View 的 post 方法如下:

public boolean post(Runnable action) {
    // 首先判斷AttachInfo是否為null
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        // 如果不為null,直接調(diào)用其內(nèi)部Handler的post
        return attachInfo.mHandler.post(action);
    }

    // 否則加入當前View的等待隊列
    getRunQueue().post(action);
    return true;
}

注意 AttachInfo 是 View 的靜態(tài)內(nèi)部類,每個 View 都會持有一個 AttachInfo,它默認為 null;需要先來看下 getRunQueue().post():

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

getRunQueue() 返回的是 HandlerActionQueue,也就是調(diào)用了 HandlerActionQueue 的 post 方法:

public void post(Runnable action) {
    // 調(diào)用到postDelayed方法,這有點類似于Handler發(fā)送消息
    postDelayed(action, 0);
}

// 實際調(diào)用postDelayed
public void postDelayed(Runnable action, long delayMillis) {
    // HandlerAction表示要執(zhí)行的任務(wù)
    final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

    synchronized (this) {
        if (mActions == null) {
            // 創(chuàng)建一個保存HandlerAction的數(shù)組
            mActions = new HandlerAction[4];
        }
        // 表示要執(zhí)行的任務(wù)HandlerAction 保存在 mActions 數(shù)組中
        mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction);
        // mActions數(shù)組下標位置累加1
        mCount++;
    }
}

HandlerAction 表示一個待執(zhí)行的任務(wù),內(nèi)部持有要執(zhí)行的 Runnable 和延遲時間;類聲明如下:

private static class HandlerAction {
    // post的任務(wù)
    final Runnable action;
    // 延遲時間
    final long delay;

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

    // 比較是否是同一個任務(wù)
    // 用于匹配某個 Runnable 和對應(yīng)的HandlerAction
    public boolean matches(Runnable otherAction) {
        return otherAction == null && action == null
                || action != null && action.equals(otherAction);
    }
}

注意 postDelayed() 創(chuàng)建一個默認長度為 4 的 HandlerAction 數(shù)組,用于保存 post() 添加的任務(wù);跟蹤到這,大家是否有這樣的疑惑:View.post() 添加的任務(wù)沒有被執(zhí)行?

實際上,此時我們要回過頭來,重新看下 AttachInfo 的創(chuàng)建過程,先看下它的構(gòu)造方法:

AttachInfo(IWindowSession session, IWindow window, Display display,
               ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
               Context context) {
        mSession = session;
        mWindow = window;
        mWindowToken = window.asBinder();
        mDisplay = display;
        // 持有當前ViewRootImpl
        mViewRootImpl = viewRootImpl;
        // 當前渲染線程Handler
        mHandler = handler;
        mRootCallbacks = effectPlayer;
        // 為其創(chuàng)建一個ViewTreeObserver
        mTreeObserver = new ViewTreeObserver(context);
    }

注意 AttachInfo 中持有當前線程的 Handler。翻閱 View 源碼,發(fā)現(xiàn)僅有兩處對 mAttachInfor 賦值操作,一處是為其賦值,另一處是將其置為 null。

  • mAttachInfo 賦值過程:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    // 給當前View賦值A(chǔ)ttachInfo,此時所有的View共用同一個AttachInfo(同一個ViewRootImpl內(nèi))
    mAttachInfo = info;
    // View浮層,是在Android 4.3添加的
    if (mOverlay != null) {
        // 任何一個View都有一個ViewOverlay
        // ViewGroup的是ViewGroupOverlay
        // 它區(qū)別于直接在類似RelativeLaout/FrameLayout添加View,通過ViewOverlay添加的元素沒有任何事件
        // 此時主要分發(fā)給這些View浮層
        mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
    }
    mWindowAttachCount++;

     // ... 省略

    if ((mPrivateFlags & PFLAG_SCROLL_CONTAINER) != 0) {
        mAttachInfo.mScrollContainers.add(this);
        mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
    }
    //  mRunQueue,就是在前面的 getRunQueue().post()
    // 實際類型是 HandlerActionQueue,內(nèi)部保存了當前View.post的任務(wù)
    if (mRunQueue != null) {
        // 執(zhí)行使用View.post的任務(wù)
        // 注意這里是post到渲染線程的Handler中
        mRunQueue.executeActions(info.mHandler);
        // 保存延遲任務(wù)的隊列被置為null,因為此時所有的View共用AttachInfo
        mRunQueue = null;
    }
    performCollectViewAttributes(mAttachInfo, visibility);
    // 回調(diào)View的onAttachedToWindow方法
    // 在Activity的onResume方法中調(diào)用,但是在View繪制流程之前
    onAttachedToWindow();

    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
            li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        for (OnAttachStateChangeListener listener : listeners) {
            // 通知所有監(jiān)聽View已經(jīng)onAttachToWindow的客戶端,即view.addOnAttachStateChangeListener();
            // 但此時View還沒有開始繪制,不能正確獲取測量大小或View實際大小
            listener.onViewAttachedToWindow(this);
        }
    }

    // ...  省略

    // 回調(diào)View的onVisibilityChanged
    // 注意這時候View繪制流程還未真正開始
    onVisibilityChanged(this, visibility);

    // ... 省略
}

方法最開始為當前 View 賦值 AttachInfo。注意 mRunQueue 就是保存了 View.post() 任務(wù)的 HandlerActionQueue;此時調(diào)用它的 executeActions 方法如下:

public void executeActions(Handler handler) {
    synchronized (this) {
        // 任務(wù)隊列
        final HandlerAction[] actions = mActions;
        // 遍歷所有任務(wù)
        for (int i = 0, count = mCount; i < count; i++) {
            final HandlerAction handlerAction = actions[i];
            //發(fā)送到Handler中,等待執(zhí)行
            handler.postDelayed(handlerAction.action, handlerAction.delay);
        }

        //此時不在需要,后續(xù)的post,將被添加到AttachInfo中
        mActions = null;
        mCount = 0;
    }
}

遍歷所有已保存的任務(wù),發(fā)送到 Handler 中排隊執(zhí)行;將保存任務(wù)的 mActions 置為 null,因為后續(xù) View.post() 直接添加到 AttachInfo 內(nèi)部的 Handler 。所以不得不去跟蹤 dispatchAttachedToWindow() 的調(diào)用時機。

ViewRootImpl

同一個 View Hierachy 樹結(jié)構(gòu)中所有 View 共用一個 AttachInfo,AttachInfo 的創(chuàng)建是在 ViewRootImpl 的構(gòu)造方法中:

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
  • 一般 Activity 包含多個 View 形成 View Hierachy 的樹形結(jié)構(gòu),只有最頂層的 DecorView 才是對 WindowManagerService “可見的”。

dispatchAttachedToWindow() 的調(diào)用時機是在 View 繪制流程的開始階段。在 ViewRootImpl 的 performTraversals 方法,在該方法將會依次完成 View 繪制流程的三大階段:測量、布局和繪制,不過這部分不是今天要分析的重點。

// View 繪制流程開始在 ViewRootImpl
private void performTraversals() {
    // mView是DecorView
    final View host = mView;
    if (mFirst) {
        .....
        // host為DecorView
        // 調(diào)用DecorVIew 的 dispatchAttachedToWindow,并且把 mAttachInfo 給子view
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
        dispatchApplyInsets(host);
        .....
    } 
   mFirst=false
   ...
   // Execute enqueued actions on every traversal in case a detached view   enqueued an action
   getRunQueue().executeActions(mAttachInfo.mHandler);
   // View 繪制流程的測量階段
   performMeasure();
   // View 繪制流程的布局階段
   performLayout();
   // View 繪制流程的繪制階段
   performDraw();
   ...

}

host 的實際類型是 DecorView,DecorView 繼承自 FrameLayout。

  • 每個 Activity 都有一個關(guān)聯(lián)的 Window 對象,用來描述應(yīng)用程序窗口,每個窗口內(nèi)部又包含一個 DecorView 對象,DecorView 對象用來描述窗口的視圖 — xml 布局。通過 setContentView() 設(shè)置的 View 布局最終添加到 DecorView 的 content 容器中。

跟蹤 DecorView 的 dispatchAttachedToWindow 方法的執(zhí)行過程,DecorView 并沒有重寫該方法,而是在其父類 ViewGroup 中:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
    super.dispatchAttachedToWindow(info, visibility);
    mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;

    // 子View的數(shù)量
    final int count = mChildrenCount;
    final View[] children = mChildren;
    // 遍歷所有子View
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        // 遍歷調(diào)用所有子View的dispatchAttachedToWindow
        // 為每個子View關(guān)聯(lián)AttachInfo
        child.dispatchAttachedToWindow(info,
                combineVisibility(visibility, child.getVisibility()));
    }
    // ...
}

for 循環(huán)遍歷當前 ViewGroup 的所有 childView,為其關(guān)聯(lián) AttachInfo。子 View 的 dispatchAttachedToWindow 方法在前面我們已經(jīng)分析過了:首先為當前 View 關(guān)聯(lián) AttachInfo,然后將之前 View.post() 保存的任務(wù)添加到 AttachInfo 內(nèi)部的 Handler

注意回到 ViewRootImpl 的 performTraversals 方法,咋一看,這個過程好像沒有太多新奇的地方。不過你是否注意到這一過程是在 View 的繪制任務(wù)中。

通過 View.post() 添加的任務(wù),是在 View 繪制流程的開始階段,將所有任務(wù)重新發(fā)送到消息隊列的尾部,此時相關(guān)任務(wù)的執(zhí)行已經(jīng)在 View 繪制任務(wù)之后,即 View 繪制流程已經(jīng)結(jié)束,此時便可以正確獲取到 View 的寬高了。

View.post() 添加的任務(wù)能夠保證在所有 View(同一個 View Hierachy 內(nèi)) 繪制流程結(jié)束之后才被執(zhí)行

碎片化問題來了,如果我們只是創(chuàng)建一個 View,調(diào)用它的 post 方法,它會不會被執(zhí)行呢?代碼如下:

final ImageView view = new ImageView(this);
    view.post(new Runnable() {
        @Override
        public void run() {
            // do something
        }
    });

答案是否定的,因為它沒有添加到窗口視圖,不會走繪制流程,自然也就不會被執(zhí)行。此時只需要添加如下代碼即可:

// 將View添加到窗口
// 此時重新發(fā)起繪制流程,post任務(wù)會被執(zhí)行
contentView.addView(view);

不過該問題在 API Level 24 之前不會發(fā)生,看下之前的代碼實現(xiàn):

// API Level 24之前的post實現(xiàn)
public boolean post(Runnable action) {
    // 這里的邏輯與API Level 24及以后一致
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    // 主要是這里,此時管理待執(zhí)行的任務(wù)直接交給了 ViewRootImpl 中。
    // 而在API Level 24及以后,每個View自行維護待執(zhí)行任務(wù)隊列,
    // 故,如果View不添加到Window視圖,dispatchAttachedToWindow 不會被調(diào)用,
    // View中的post任務(wù)將永遠得不到執(zhí)行
    ViewRootImpl.getRunQueue().post(action);
    return true;
}

在 API Level 24 之前,通過 View.post() 任務(wù)被直接添加到 ViewRootImpl 中,在 24 及以后,每個 View 自行維護待執(zhí)行的 post() 任務(wù),它們要依賴于 dispatchAttachedToWindow 方法,如果 View 未添加到窗口視圖,post() 添加的任務(wù)將永遠得不到執(zhí)行。

這樣的碎片化問題在 Android 中可能數(shù)不勝數(shù),這也告誡我們?nèi)绻麑δ稠椆δ茳c了解的不夠充分,最后可能導致程序未按照意愿執(zhí)行。

至此,View.post() 的原理我們就算搞清楚了,不過還是有必要跟蹤下 AttachInfo 的釋放過程。

  • mAttachInfo 置 null 的過程:

先看下表示 DecorView 的 dispatchDetachedFromWindow 方法,實際是調(diào)用其父類 ViewGroup 中:

// ViewGroup 的 dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {

    // ... 省略

    final int count = mChildrenCount;
    final View[] children = mChildren;
    // 遍歷所有childView
    for (int i = 0; i < count; i++) {
        // 通知childView dispatchDetachedFromWindow
        children[i].dispatchDetachedFromWindow();
    }

    // ... 省略

    super.dispatchDetachedFromWindow();
}

不出所料 ViewGroup 的 dispatchDetachedFromWindow 方法會遍歷所有 childView。

void dispatchDetachedFromWindow() {
    AttachInfo info = mAttachInfo;
    if (info != null) {
        int vis = info.mWindowVisibility;
        if (vis != GONE) {
            // 通知 Window顯示狀態(tài)發(fā)生變化
            onWindowVisibilityChanged(GONE);
            if (isShown()) {
                onVisibilityAggregated(false);
            }
        }
    }
    // 回調(diào)View的onDetachedFromWindow
    onDetachedFromWindow();
    onDetachedFromWindowInternal();

    // ... 省略

    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
            li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        // 通知所有監(jiān)聽View已經(jīng)onAttachToWindow的客戶端,即view.addOnAttachStateChangeListener();
        for (OnAttachStateChangeListener listener : listeners) {
            // 通知回調(diào) onViewDetachedFromWindow
            listener.onViewDetachedFromWindow(this);
        }
    }

    // ... 省略

    // 將AttachInfo置為null
    mAttachInfo = null;
    if (mOverlay != null) {
        // 通知浮層View
        mOverlay.getOverlayView().dispatchDetachedFromWindow();
    }

    notifyEnterOrExitForAutoFillIfNeeded(false);
}

可以看到在 dispatchDetachedFromWindow 方法,首先回調(diào) View 的 onDetachedFromWindow(),然后通知所有監(jiān)聽者 onViewDetachedFromWindow(),最后將 mAttachInfo 置為 null。

由于 dispatchAttachedToWindow 方法是在 ViewRootImpl 中完成,此時很容易想到它的釋放過程肯定也在 ViewRootImpl,跟蹤發(fā)現(xiàn)如下調(diào)用過程:

void doDie() {
    // 檢查執(zhí)行線程
    checkThread();

    synchronized (this) {
        if (mRemoved) {
            return;
        }
        mRemoved = true;
        if (mAdded) {
            // 回調(diào)View的dispatchDetachedFromWindow
            dispatchDetachedFromWindow();
        }

        if (mAdded && !mFirst) {
            destroyHardwareRenderer();

            // mView是DecorView
            if (mView != null) {
                int viewVisibility = mView.getVisibility();
                // 窗口狀態(tài)是否發(fā)生變化
                boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                if (mWindowAttributesChanged || viewVisibilityChanged) {
                    try {
                        if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                            mWindowSession.finishDrawing(mWindow);
                        }
                    } catch (RemoteException e) {
                    }
                }
                // 釋放畫布
                mSurface.release();
            }
        }

        mAdded = false;
    }

    // 將其從WindowManagerGlobal中移除
    // 移除DecorView
    // 移除DecorView對應(yīng)的ViewRootImpl
    // 移除DecorView
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

可以看到 dispatchDetachedFromWindow 方法被調(diào)用,注意方法最后將 ViewRootImpl 從 WindowManager 中移除。

經(jīng)過前面的分析我們已經(jīng)知道 AttachInfo 的賦值操作是在 View 繪制任務(wù)的開始階段,而它的調(diào)用者是 ActivityThread 的 handleResumeActivity 方法,即 Activity 生命周期 onResume 方法之后。

那它是在 Activity 的哪個生命周期階段被釋放的呢?在 Android 中, Window 是 View 的容器,而 WindowManager 則負責管理這些窗口,具體可以參考《View 繪制流程之 DecorView 添加至窗口的過程》。

我們直接找到管理應(yīng)用進程窗口的 WindowManagerGlobal,查看 DecorView 的移除工作:

/**
 * 將DecorView從WindowManager中移除
 */
public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }

    synchronized (mLock) {
        // 找到保存該DecorView的下標,true表示找不到要拋出異常
        int index = findViewLocked(view, true);
        // 找到對應(yīng)的ViewRootImpl,內(nèi)部的DecorView
        View curView = mRoots.get(index).getView();
        // 從WindowManager中移除該DecorView
        // immediate 表示是否立即移除
        removeViewLocked(index, immediate);
        if (curView == view) {
            // 判斷要移除的與WindowManager中保存的是否為同一個
            return;
        }

        // 如果不是同一個View(DecorView),拋異常
        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

根據(jù)要移除的 DecorView 找到在 WindowManager 中保存的 ViewRootImpl,真正移除是在 removeViewLocked 方法:

private void removeViewLocked(int index, boolean immediate) {
    // 找到對應(yīng)的ViewRootImpl
    ViewRootImpl root = mRoots.get(index);
    // 該View是DecorView
    View view = root.getView();

    // ... 省略
    
    // 調(diào)用ViewRootImpl的die
    // 并且將當前ViewRootImpl在WindowManagerGlobal中移除
    boolean deferred = root.die(immediate);
    if (view != null) {
        // 斷開DecorView與ViewRootImpl的關(guān)聯(lián)
        view.assignParent(null);
        if (deferred) {
            // 返回 true 表示延遲移除,加入待死亡隊列
            mDyingViews.add(view);
        }
    }
}

可以看到調(diào)用了 ViewRootImpl 的 die 方法,回到 ViewRootImpl 中:

boolean die(boolean immediate) {
    // immediate 表示立即執(zhí)行
    // mIsInTraversal 表示是否正在執(zhí)行繪制任務(wù)
    if (immediate && !mIsInTraversal) {
        // 內(nèi)部調(diào)用了View的dispatchDetachedFromWindow
        doDie();
        // return false 表示已經(jīng)執(zhí)行完成
        return false;
    }

    if (!mIsDrawing) {
        // 釋放硬件加速繪制
        destroyHardwareRenderer();
    } 
    // 如果正在執(zhí)行遍歷繪制任務(wù),此時需要等待遍歷任務(wù)完成
    // 故發(fā)送消息到尾部
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

注意 doDie 方法(源碼在前面已經(jīng)貼出),它最終會調(diào)用 dispatchDetachedFromWindow 方法。

最后,移除 Window 窗口任務(wù)是通過 ActivityThread 完成的,具體調(diào)用在 handleDestoryActivity 方法完成:

private void handleDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance) {
    // 回調(diào) Activity 的 onDestory 方法
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance);
    if (r != null) {
        cleanUpPendingRemoveWindows(r, finishing);

        // 獲取當前Window的WindowManager, 實際是WindowManagerImpl
        WindowManager wm = r.activity.getWindowManager();
        // 當前Window的DecorView
        View v = r.activity.mDecor;
        if (v != null) {
            if (r.activity.mVisibleFromServer) {
                mNumVisibleActivities--;
            }
            IBinder wtoken = v.getWindowToken();
            // Window 是否添加過,到WindowManager
            if (r.activity.mWindowAdded) {
                if (r.mPreserveWindow) {
                    r.mPendingRemoveWindow = r.window;
                    r.mPendingRemoveWindowManager = wm;
                    r.window.clearContentView();
                } else {
                    // 通知 WindowManager,移除當前 Window窗口
                    wm.removeViewImmediate(v);
                }
            }
} 

注意 performDestoryActivity() 將完成 Activity 生命周期 onDestory 方法回調(diào)。然后調(diào)用 WindowManager 的 removeViewImmediate():

/**
 * WindowManagerImpl
 */
@Override
public void removeViewImmediate(View view) {
    // 調(diào)用WindowManagerGlobal的removeView方法
    mGlobal.removeView(view, true);
}

即 AttachInfo 的釋放操作是在 Activity 生命周期 onDestory 方法之后,在整個 Activity 的生命周期內(nèi)都可以正常使用 View.post() 任務(wù)。

總結(jié)
  1. 關(guān)于 View.post() 要注意在 API Level 24 前后的版本差異,不過該問題也不用過于擔心,試想,會有哪些業(yè)務(wù)場景需要創(chuàng)建一個 View 卻不把它添加到窗口視圖呢?

  2. View.post() 任務(wù)能夠保證在所有 View 繪制流程結(jié)束之后被調(diào)用,故如果需要依賴 View 繪制任務(wù),此時可以優(yōu)先考慮使用該機制。


最后,如果需要更好的理解 View.post() 執(zhí)行原理,可能還需要進一步理解 AttachInfo 的創(chuàng)建過程,關(guān)于這部分的詳細分析,你可以參考《Android 之 ViewTreeObserver 全面解析》。

文中如有不妥或有更好的分析結(jié)果,歡迎您的分享留言或指正。

文章如果對你有幫助,請留個贊吧!


擴展閱讀

其他專題

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

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

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