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

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

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

常見疑問1

a. 描述

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

.b 原因

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

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

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

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

所以:

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

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

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

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


常見疑問2

a. 描述

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

.b 原因

在onCreate()時(shí),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的等待隊(duì)列
    getRunQueue().post(action); 
    return true;
}

c. 實(shí)例代碼演示

@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)建一個(gè) View & 調(diào)用它的post()畦粮,那么post的任務(wù)會不會被執(zhí)行散址?

final View view = new View(this);

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

b. 答案

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

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

上述例子犁罩,因?yàn)樗鼪]有被添加到窗口視圖齐蔽,所以不會走繪制流程,所以該任務(wù)最終不會被post到消息隊(duì)列里 & 執(zhí)行

c. 解決方案

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

// 因?yàn)榇藭r(shí)會重新發(fā)起繪制流程含滴,post的任務(wù)會被放到消息隊(duì)列里,所以會被執(zhí)行
contentView.addView(view);

常見疑問4

a. 描述

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

b. 結(jié)論

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

c.原因

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

.d 原因分析

  • 目標(biāo)
    跟蹤 AttachInfo 的釋放過程(即何時(shí)置為null)

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

  • 具體源碼分析
/**
  * 入口分析:DecorView.dispatchDetachedFromWindow()
  * 實(shí)際上是調(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;
}

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

此時(shí)需從 將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
    // 因?yàn)樵陉P(guān)注1處是在回調(diào) Activity.onDestory()后联喘,故在整個(gè)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的下標(biāo)
    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中移除時(shí):回調(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:動(dòng)畫


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

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


請點(diǎn)贊渊跋!因?yàn)槟愕墓膭?lì)是我寫作的最大動(dòng)力腊嗡!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末着倾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子燕少,更是在濱河造成了極大的恐慌卡者,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件客们,死亡現(xiàn)場離奇詭異崇决,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)底挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門恒傻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人建邓,你說我怎么就攤上這事盈厘。” “怎么了官边?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵沸手,是天一觀的道長。 經(jīng)常有香客問我注簿,道長契吉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任诡渴,我火速辦了婚禮捐晶,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妄辩。我一直安慰自己惑灵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布恩袱。 她就那樣靜靜地躺著泣棋,像睡著了一般胶哲。 火紅的嫁衣襯著肌膚如雪畔塔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天鸯屿,我揣著相機(jī)與錄音澈吨,去河邊找鬼。 笑死寄摆,一個(gè)胖子當(dāng)著我的面吹牛谅辣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婶恼,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼桑阶,長吁一口氣:“原來是場噩夢啊……” “哼柏副!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚣录,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤割择,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后萎河,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荔泳,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年虐杯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玛歌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡擎椰,死狀恐怖支子,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情确憨,我是刑警寧澤译荞,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站休弃,受9級特大地震影響吞歼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塔猾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一篙骡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丈甸,春花似錦糯俗、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至顿仇,卻和暖如春淘正,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背臼闻。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工鸿吆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人述呐。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓惩淳,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乓搬。 傳聞我的和親對象是個(gè)殘疾皇子思犁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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