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

    // 否則加入當(dāng)前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;
        // 持有當(dāng)前ViewRootImpl
        mViewRootImpl = viewRootImpl;
        // 當(dāng)前渲染線程Handler
        mHandler = handler;
        mRootCallbacks = effectPlayer;
        // 為其創(chuàng)建一個ViewTreeObserver
        mTreeObserver = new ViewTreeObserver(context);
    }

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

  • mAttachInfo 賦值過程:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    // 給當(dāng)前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)部保存了當(dāng)前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);

    // ... 省略
}

方法最開始為當(dāng)前 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)遍歷當(dāng)前 ViewGroup 的所有 childView,為其關(guān)聯(lián) AttachInfo。子 View 的 dispatchAttachedToWindow 方法在前面我們已經(jīng)分析過了:首先為當(dā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了解的不夠充分纪吮,最后可能導(dǎo)致程序未按照意愿執(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 則負責(zé)管理這些窗口确憨,具體可以參考《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
    // 并且將當(dāng)前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);

        // 獲取當(dāng)前Window的WindowManager, 實際是WindowManagerImpl
        WindowManager wm = r.activity.getWindowManager();
        // 當(dāng)前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,移除當(dāng)前 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 前后的版本差異跪帝,不過該問題也不用過于擔(dān)心今膊,試想,會有哪些業(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)系作者
禁止轉(zhuǎn)載梭纹,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者众辨。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子殊橙,更是在濱河造成了極大的恐慌袜瞬,老刑警劉巖耿币,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件翠胰,死亡現(xiàn)場離奇詭異,居然都是意外死亡耀销,警方通過查閱死者的電腦和手機楼眷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熊尉,“玉大人罐柳,你說我怎么就攤上這事≌。” “怎么了张吉?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長催植。 經(jīng)常有香客問我肮蛹,道長勺择,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任伦忠,我火速辦了婚禮省核,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘昆码。我一直安慰自己芳撒,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布未桥。 她就那樣靜靜地躺著,像睡著了一般芥备。 火紅的嫁衣襯著肌膚如雪冬耿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天萌壳,我揣著相機與錄音亦镶,去河邊找鬼。 笑死袱瓮,一個胖子當(dāng)著我的面吹牛缤骨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播尺借,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼绊起,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了燎斩?” 一聲冷哼從身側(cè)響起虱歪,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎栅表,沒想到半個月后笋鄙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡怪瓶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年萧落,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片洗贰。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡找岖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出哆姻,到底是詐尸還是另有隱情宣增,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布矛缨,位于F島的核電站爹脾,受9級特大地震影響帖旨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜灵妨,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一解阅、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泌霍,春花似錦货抄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藤为,卻和暖如春怪与,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背缅疟。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工分别, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人存淫。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓耘斩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親桅咆。 傳聞我的和親對象是個殘疾皇子括授,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容