Carson帶你學(xué)Android:你真的了解view.post()嗎?

本文主要講解view.post() 的四大常見疑問

  1. 為什么view.post()能保證獲取到view的寬高?
  2. 為什么onCreate()使用view.post()無法立刻執(zhí)行任務(wù)(如獲取寬高)
  3. 若只是創(chuàng)建一個 View & 調(diào)用view.post()傳入要執(zhí)行的任務(wù),為什么該任務(wù)不會被執(zhí)行?
  4. view.pos()傳入的任務(wù)被執(zhí)行的有效期是什么時間節(jié)點?

常見疑問1

a. 描述

為什么view.post()能保證獲取到view的寬高?

.b 原因

View.post()的原理:以Handler為基礎(chǔ),View.post() 將傳入任務(wù)添加到 View繪制任務(wù)所在的消息隊列尾部,從而保證View.post() 任務(wù)的執(zhí)行時機是在View 繪制任務(wù)完成之后的。 其中,幾個關(guān)鍵點:

  1. View.post()實際操作:將view.post()傳入的任務(wù)保存到一個數(shù)組里

  2. View.post()添加的任務(wù) 添加到 View繪制任務(wù)所在的消息隊列尾部的時機:View 繪制流程的開始階段,即 ViewRootImpl.performTraversals()

  3. View.post()添加的任務(wù)執(zhí)行時機:在View繪制任務(wù)之后

所以:

  • 通過View.post()添加的任務(wù)是在View繪制任務(wù)里 - 開始繪制階段時添加到消息隊列尾部的;

  • 所以,View.post() 添加的任務(wù)的執(zhí)行是在View繪制任務(wù)后才執(zhí)行,即在View繪制流程結(jié)束之后執(zhí)行。

  • 即View.post() 添加的任務(wù)能夠保證在所有 View繪制流程結(jié)束之后才被執(zhí)行,所以 執(zhí)行View.post() 添加的任務(wù)時可以正確獲取到 View 的寬高。

具體源碼分析請看:Android:為什么view.post()能保證獲取到view的寬高?


常見疑問2

a. 描述

為什么onCreate()使用view.post()無法立刻執(zhí)行任務(wù)(如獲取寬高),需要在onResume()后才可獲取?

.b 原因

在onCreate()時,AttachInfo還沒被賦值(為null)(是在view.dispatchAttachedToWindow()才被賦值),所以會走下述源碼的過程2;通過上面分析,此過程的作用僅是:保存了通過post()添加的任務(wù),并沒執(zhí)行。

public boolean post(Runnable action) {
    
    // ...
    
    // 判斷AttachInfo是否為null
    final AttachInfo attachInfo = mAttachInfo;

    // 過程1:若不為null,直接調(diào)用其內(nèi)部Handler的post ->>分析1
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }

    // 過程2:若為null,則加入當(dāng)前View的等待隊列
    getRunQueue().post(action); 
    return true;
}

c. 實例代碼演示

@Override
public void onCreate(Bundle savedInstanceState) {
// 執(zhí)行日志1:carsonhe oncreate()

view.post(new Runnable() {
        @Override
        public void run() {

            // 執(zhí)行日志2:carsonhe view.post() do something

        }
    });
}

@Override
protected void onResume() {
    // 執(zhí)行日志3:carsonhe onresume()
}

// 輸出日志展示
日志1:carsonhe oncreate()
日志3:carsonhe onresume()
日志2:carsonhe view.post() do something

常見疑問3

a. 問題描述

若只是創(chuàng)建一個 View & 調(diào)用它的post(),那么post的任務(wù)會不會被執(zhí)行?

final View view = new View(this);

    view.post(new Runnable() {
        @Override
        public void run() {
            // ...
        }
    });

b. 答案

不會。主要原因是:
每個View中post() 需執(zhí)行的任務(wù),必須得添加到窗口視圖-執(zhí)行繪制流程 - 任務(wù)才會被post到消息隊列里去等待執(zhí)行,即依賴于dispatchAttachedToWindow ();

若View未添加到窗口視圖,那么就不會走繪制流程,post() 添加的任務(wù)最終不會被post到消息隊列里,即得不到執(zhí)行。(但會保存到HandlerAction數(shù)組里)

上述例子,因為它沒有被添加到窗口視圖,所以不會走繪制流程,所以該任務(wù)最終不會被post到消息隊列里 & 執(zhí)行

c. 解決方案

此時只需要添加將View添加到窗口,那么post()的任務(wù)即可被執(zhí)行

// 因為此時會重新發(fā)起繪制流程,post的任務(wù)會被放到消息隊列里,所以會被執(zhí)行
contentView.addView(view);

常見疑問4

a. 描述

view.pos()傳入的任務(wù)被執(zhí)行的有效期是多久?

b. 結(jié)論

在整個 Activity 的生命周期內(nèi)都可以正常使用 View.post() 任務(wù)

c.原因

任務(wù)被執(zhí)行是構(gòu)造AttachInfo,所以任務(wù)釋放即時釋放AttachInfo (置為null)。而AttachInfo 的釋放操作(置為null)是在 Activity 生命周期 onDestory 方法之后

.d 原因分析

  • 目標
    跟蹤 AttachInfo 的釋放過程(即何時置為null)

  • 方向
    AttachInfo的賦值依賴于DecorView.dispatchAttachedToWindow(),那么釋放過程,容易聯(lián)想到是對應(yīng)的:DecorView.dispatchDetachedFromWindow()

  • 具體源碼分析
/**
  * 入口分析:DecorView.dispatchDetachedFromWindow()
  * 實際上是調(diào)用父類ViewGroup.dispatchDetachedFromWindow()
  */

  void dispatchDetachedFromWindow() {

    // ... 

    final int count = mChildrenCount;
    final View[] children = mChildren;

    // 遍歷所有childView
    for (int i = 0; i < count; i++) {
        // 遍歷所有childView & dispatchDetachedFromWindow()
        // 分析1
        children[i].dispatchDetachedFromWindow();
    }
}

/**
  * 分析1:childView.dispatchDetachedFromWindow()
  */
  void dispatchDetachedFromWindow() {

    // ... 

    AttachInfo info = mAttachInfo;
   
    // 1. 回調(diào)View.onDetachedFromWindow()
    onDetachedFromWindow();
    
    // 2. 通知所有監(jiān)聽View.onAttachToWindow的監(jiān)聽者回調(diào)onViewDetachedFromWindow()
    ListenerInfo li = mListenerInfo;
    final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li != null ? li.mOnAttachStateChangeListeners : null;
    if (listeners != null && listeners.size() > 0) {
        for (OnAttachStateChangeListener listener : listeners) {
            listener.onViewDetachedFromWindow(this);
        }
    }

    // 3. 將AttachInfo置為null
    mAttachInfo = null;
}

下面,我們將分析,什么時候調(diào)用上述入口,即DecorView.dispatchDetachedFromWindow();

此時需從 將DecorView從WindowManager中移除 開始講起:移除 Window 窗口任務(wù)是通過 ActivityThread.handleDestoryActivity()完成。

/**
 * 入口
 */
private void handleDestroyActivity(IBinder token, boolean finishing,
        int configChanges, boolean getNonConfigInstance) {

    // 關(guān)注1:回調(diào) Activity.onDestory()
    ActivityClientRecord r = performDestroyActivity(token, finishing,
            configChanges, getNonConfigInstance);

    // 獲取當(dāng)前Window的WindowManager
    WindowManager wm = r.activity.getWindowManager();
    // 當(dāng)前Window的DecorView
    View v = r.activity.mDecor;
       
    // 關(guān)注2:通知WindowManager,移除當(dāng)前 Window窗口
    wm.removeViewImmediate(v);
    // 此處即會釋放AttachInfo
    // 因為在關(guān)注1處是在回調(diào) Activity.onDestory()后,故在整個Activity的生命周期內(nèi)都可以正常使用 View.post() 任務(wù)
    // 下面繼續(xù)分析如何移除 ->> 分析1
                
}

/**
 * 分析1:WindowManager.removeViewImmediate()
 */
public void removeViewImmediate(View view) {
    
    mGlobal.removeView(view, true);
    // 調(diào)用WindowManagerGlobal的removeView()
    // ->> 分析2
}

/**
 * 分析2:WindowManagerGlobal.removeView()
 */
public void removeView(View view, boolean immediate) {
    // ...
   
    // 找到保存該DecorView的下標
    int index = findViewLocked(view, true);

    // 找到對應(yīng)的ViewRootImpl,內(nèi)部的DecorView
    View curView = mRoots.get(index).getView();

    // 從WindowManager中移除該DecorView
    // immediate 表示是否立即移除
    removeViewLocked(index, immediate);
    // ->> 分析3

}

/**
 * 分析3
 */
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);
    // ->> 分析4
}

/**
 * 分析4
 */
boolean die(boolean immediate) {

    // immediate 表示立即執(zhí)行
    // mIsInTraversal 表示是否正在執(zhí)行繪制任務(wù)
    if (immediate && !mIsInTraversal) {
        
        doDie();
        // ->> 分析5
    }

    // ...
}

/**
  * 分析5
  */
void doDie() {

    // ...
    if (mAdded) {
        
        dispatchDetachedFromWindow();
        // 回調(diào)View的dispatchDetachedFromWindow
        // ->> 即一開始分析的DecorView.dispatchAttachedToWindow()
    }

    // 將其從WindowManagerGlobal中移除DecorView
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

.d 最終原因 & 結(jié)論

View.post() 任務(wù)被執(zhí)行的有效期是在 Activity 生命周期 onDestory()后。本質(zhì)是追蹤AttachInfo的釋放過程(置為null)

AttachInfo的釋放過程是在 將DecorView從WindowManager中移除時:回調(diào)DecorView.dispatchDetachedFromWindow(),其具體行為是:

  1. 回調(diào)View.onDetachedFromWindow()
  2. 通知所有監(jiān)聽View.onAttachToWindow的監(jiān)聽者回調(diào)onViewDetachedFromWindow()
  3. 將AttachInfo置為null

而上述過程是在ActivityThread.handleDestoryActivity()中回調(diào) Activity.onDestory()之后。

至此,關(guān)于view.post()的四大常見疑問 (坑)內(nèi)容講解完畢。


總結(jié)

  • 本文主要總結(jié)了常用的view.post() 的四大常見疑問
  • 接下來推出的文章,我將繼續(xù)講解Android的相關(guān)知識,感興趣的讀者可以繼續(xù)關(guān)注我的博客哦:Carson_Ho的Android博客

相關(guān)系列文章閱讀
Carson帶你學(xué)Android:學(xué)習(xí)方法
Carson帶你學(xué)Android:四大組件
Carson帶你學(xué)Android:自定義View
Carson帶你學(xué)Android:異步-多線程
Carson帶你學(xué)Android:性能優(yōu)化
Carson帶你學(xué)Android:動畫


歡迎關(guān)注Carson_Ho的簡書

不定期分享關(guān)于安卓開發(fā)的干貨,追求短、平、快,但卻不缺深度。


請點贊!因為你的鼓勵是我寫作的最大動力!

最后編輯于
?著作權(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ù)。

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

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