關(guān)于View.post()/postDelay()方法的一些分析

一.引言

經(jīng)常在Android的java代碼中動態(tài)設(shè)置布局的讀者應(yīng)該會對動態(tài)獲取控件寬高不陌生落剪,在最近項(xiàng)目中我也有用到。我們知道直接在onCreate方法中無法通過getWidthgetHeight獲取到想要的控件的寬高具體值。因此有幾種方式來獲取,例如在監(jiān)聽中獲取或者通過View.post的方式獲取。具體方法可以參考下面的這篇文章及穗,寫得比較仔細(xì)。
Activity啟動過程中獲取組件寬高的五種方式
筆者也有使用View.post來獲取绵载,不過始終對這個方法心存疑問埂陆,為什么一個類似handler.post的方法調(diào)用之后就可以正確得到寬高?通過查看源碼以及查閱的一些資料大致弄懂了流程娃豹,接下來說一下我的分析焚虱,如果有不對的地方歡迎指教~

二.View.post方法的調(diào)用機(jī)制

View的post和postDelay方法其實(shí)是類似的,我們點(diǎn)進(jìn)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;
    }
   ...
   public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // Assume that the runnable will be successfully placed after attach.
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    }

這兩個方法中的代碼基本是一致的懂版,不同的只是postDelay中有將delayMillis傳進(jìn)去鹃栽,而在post中最終也是調(diào)用了postDelay這個方法,只是將delayMillis置為0了躯畴,我們可以點(diǎn)進(jìn)getRunQueue().post()方法中看看:

getRunQueue().post()

可以看到確實(shí)如此民鼓。
接下來分析post中具體代碼。代碼比較簡潔私股,我們可以看到其中有對mAttachInfo賦值的attachInfo進(jìn)行判斷摹察,如果為空,則調(diào)用getRunQueue().post()倡鲸,否則直接返回attachInfo.mHandler.post(action)。那么這兩個post有什么區(qū)別黄娘?

1.先來看當(dāng)mAttachInfo不為null時的情況峭状,因?yàn)檫@個較為簡單克滴,點(diǎn)進(jìn)attachInfo.mHandler.post發(fā)現(xiàn)其實(shí)就是調(diào)用的Handler.post()。這里就要看看這里的mHandler是哪里產(chǎn)生的优床。使用AS的ctrl+左鍵一直追根溯源:

final static class AttachInfo {
...
   final Handler mHandler;
...
   AttachInfo(IWindowSession session, IWindow window, Display display,
                ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer,
                Context context) {
            mSession = session;
            mWindow = window;
            mWindowToken = window.asBinder();
            mDisplay = display;
            mViewRootImpl = viewRootImpl;
            mHandler = handler;
            mRootCallbacks = effectPlayer;
            mTreeObserver = new ViewTreeObserver(context);
        }

可以看到mHandler是AttachInfo的一個變量劝赔,在AttchInfo的構(gòu)造方法中被賦值,查看這個構(gòu)造方法的調(diào)用點(diǎn)(這里可以結(jié)合Source Insight的ctrl+/的查找快捷鍵進(jìn)行查找):

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
...
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);

可以看到是在ViewRootImpl方法中將mHandler傳入胆敞,查看mHanlder被賦值的地方:

  final ViewRootHandler mHandler = new ViewRootHandler();

ViewRootImpl是在主線程中被創(chuàng)建着帽,因此這個handler對象是主線程的handler,至此我們可以知道mAttachInfo不為空的時候其實(shí)是直接調(diào)用了主線程handler來處理我們post的消息移层。當(dāng)然這里有個問題是為什么使用了主線程的handler來處理消息之后就可以獲取正確的寬高呢仍翰?先別急,我們繼續(xù)梳理了之后來解釋這個問題~

2.上面已經(jīng)分析了mAttachInfo不為空的情況观话,當(dāng)mAttachInfo為空時予借,會調(diào)用getRunQueue()這個方法:

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

返回了一個HandlerActionQueue的實(shí)例。這個HandlerActionQueue是什么频蛔,點(diǎn)進(jìn)去看看:

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

這個類包含了兩個全局變量灵迫,一個應(yīng)該是用來計(jì)數(shù),另一個mActions則是一個HandlerAction數(shù)組晦溪,HandlerAction中封裝了一個Runnable對象和一個延時delay瀑粥。
繼續(xù)看,當(dāng)調(diào)用了post之后三圆,實(shí)際只是生成了一個默認(rèn)長度為4的HandlerAction數(shù)組狞换,將要實(shí)現(xiàn)的Runnable傳入。那我們什么時候使用Runnable呢嫌术?我們可以注意到這個類中有個executeActions方法:

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

這個方法就是將我們post進(jìn)來的Runnable使用handler進(jìn)行處理哀澈,可以查看一下這個方法在哪里被調(diào)用以及handler是來自哪里。在View中可以查找到該方法在View.java類中的dispatchAttachedToWindow方法中被調(diào)用:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        ...
}

這里可以看到傳入的就是AttachInfo 的mHandler變量度气,上面我們分析過最終mHandler就是主線程的Handler割按,那么現(xiàn)在的問題就是這個info是從哪里傳入的。繼續(xù)追查dispatchAttachedToWindow方法使用的地方(Source Insight):可以看到總共有四個類有調(diào)用這個方法:AttachInfo_Accessor磷籍,View适荣,ViewGroupViewRootImpl院领。

image.png

其中AttachInfo_Accessor.java類中沒有我們需要關(guān)注的地方弛矛,View.java就是當(dāng)前類,顯然沒有被調(diào)用的地方比然,進(jìn)入ViewGroup.java看看:

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

        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()));
        }
        final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
        for (int i = 0; i < transientCount; ++i) {
            View view = mTransientViews.get(i);
            view.dispatchAttachedToWindow(info,
                    combineVisibility(visibility, view.getVisibility()));
        }
    }

可以看到ViewGroupdispatchAttachedToWindow方法中丈氓,調(diào)用了ViewdispatchAttachedToWindow方法,將父中的AttachInfo全部傳入子類child中實(shí)現(xiàn)賦值,而此時的child就是View類万俗,我們不是要查看View類中的這個方法在哪里調(diào)用嗎湾笛?這里貌似陷入死循環(huán)了。闰歪。沒事嚎研,我們在看最后的ViewRootImpl方法中有沒有什么信息:

private void performTraversals() {
  ...
  host.dispatchAttachedToWindow(mAttachInfo, 0);
  ...
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
  performLayout(lp, mWidth, mHeight);
  ...
  performDraw();
  ...
}

performTraversals方法中我們看到了這個方法。host是由mView賦值库倘,而mView就是由頂層視圖DecorView所賦值的临扮。這個performTraversals方法就是用來調(diào)用測量、布局教翩、繪制方法的地方杆勇。mAttachInfo唯一賦值的地方上面我們也有分析過:

mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);

因此到這里我們就可以梳理一下了:在Activity的DecorView中調(diào)用dispatchAttachedToWindow時,將mAttachInfo傳入到View中迂曲,并調(diào)用mRunQueue.executeActions的方法靶橱,該方法使用mAttachInfohandler將我們最初調(diào)用View.post(runnable)方法post進(jìn)來的消息post到消息隊(duì)列中進(jìn)行處理,并且我們知道mAttachInfohandler是主線程的handler路捧,因此其實(shí)就是post到了主線程的消息隊(duì)列中等待處理关霸。這里我們也就分析完畢。
不過這里有兩個問題:
(1)前面我們知道由mAttachInfo的值來決定調(diào)用哪個post杰扫。那么mAttachInfo什么時候不為空队寇?查看mAttachInfo被賦值的地方有兩處:

void dispatchAttachedToWindow(AttachInfo info, int visibility) {
...
    mAttachInfo = info;
...
}
void dispatchDetachedFromWindow() {
...
        mAttachInfo = null;
...
}

從這里我們可以看到在dispatchAttachedToWindowmAttachInfo被賦值為info,這里的info不就是從performTraversals方法中調(diào)用的時候傳入的嗎~所以就是當(dāng)attachedToWindow的時候被賦值章姓,detachedFromWindow時被置為空佳遣。當(dāng)mAttachInfo為空的時候?qū)?code>Runnable存放到HandlerAction中,當(dāng)ViewdispatchAttachedToWindow方法被調(diào)用時使用主線程handler將其post到消息隊(duì)列中凡伊。當(dāng)mAttachInfo不為空的時候直接調(diào)用主線程handler即可零渐。
(2)在performTraversals方法中我們是先調(diào)用dispatchAttachedToWindow方法之后才開始調(diào)用measurelayout等方法進(jìn)行測量布局的,而在dispatchAttachedToWindow中我們就有調(diào)用了handler將消息post到消息隊(duì)列了準(zhǔn)備執(zhí)行了系忙,那此時我們?yōu)槭裁茨軌蛟?code>View.post方法中獲取到正確的長寬呢诵盼?
我們來查看performTraversals的調(diào)用時機(jī):

 void doTraversal() {
          ...
          performTraversals();
          ...
    }

就一處被調(diào)用,繼續(xù)看doTraversal被調(diào)用的地方:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

我們可以看到其實(shí)它是在一個Runnable中的run方法中被調(diào)用银还,這個TraversalRunnable方法被實(shí)例化之后风宁,

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

分別在scheduleTraversalsunscheduleTraversals方法中被調(diào)用,這兩個方法直觀上看上去就是成對出現(xiàn)的方法蛹疯,我們來看scheduleTraversals方法中有一行代碼:

 void scheduleTraversals() {
           ...
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
        }
    }

這個postCallback方法貌似也是post的近親戒财?我們查看Choreographer中的這個方法可以看到其實(shí)最終就是調(diào)用了Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);方法。這個mHandler就是主線程Handler~
因此謎底揭開捺弦,首先在主線程的Handler中就已經(jīng)post進(jìn)去了一個Runnable來執(zhí)行performTraversals方法饮寞,當(dāng)然就按照順序執(zhí)行了post我們調(diào)用View.post(runnable)方法到主線程Handler孝扛、測量、布局骂际、繪制等一系列操作疗琉。然而由于Handler的機(jī)制冈欢,它是將所有的message都post到一個MessageQueue中歉铝,按照順序執(zhí)行這些消息。因此只有當(dāng)執(zhí)行完測量凑耻、布局太示、繪制之后,才能執(zhí)行我們的Runnable香浩,所以我們這時就能夠獲取到正確的寬高了~

三.感想

第一次分析源碼確實(shí)感覺很多知識點(diǎn)都不太理解类缤,不過希望自己能堅(jiān)持下來不斷前行~分析的過程中有借鑒兩位大神的文章,很感謝_
1.【Andorid源碼解析】View.post() 到底干了啥
2.通過View.post()獲取View的寬高引發(fā)的兩個問題

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邻吭,一起剝皮案震驚了整個濱河市餐弱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囱晴,老刑警劉巖膏蚓,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異畸写,居然都是意外死亡驮瞧,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進(jìn)店門枯芬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來论笔,“玉大人,你說我怎么就攤上這事千所】衲В” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵淫痰,是天一觀的道長最楷。 經(jīng)常有香客問我,道長黑界,這世上最難降的妖魔是什么管嬉? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮朗鸠,結(jié)果婚禮上蚯撩,老公的妹妹穿的比我還像新娘。我一直安慰自己烛占,他們只是感情好胎挎,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布沟启。 她就那樣靜靜地躺著,像睡著了一般犹菇。 火紅的嫁衣襯著肌膚如雪德迹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天揭芍,我揣著相機(jī)與錄音胳搞,去河邊找鬼。 笑死称杨,一個胖子當(dāng)著我的面吹牛肌毅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姑原,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼悬而,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锭汛?” 一聲冷哼從身側(cè)響起笨奠,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎唤殴,沒想到半個月后般婆,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眨八,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年腺兴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廉侧。...
    茶點(diǎn)故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡页响,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出段誊,到底是詐尸還是另有隱情闰蚕,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布连舍,位于F島的核電站没陡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏索赏。R本人自食惡果不足惜盼玄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望潜腻。 院中可真熱鬧埃儿,春花似錦、人聲如沸融涣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至剃斧,卻和暖如春轨香,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背幼东。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工臂容, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人筋粗。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓策橘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親娜亿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評論 2 359

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