探究為何:在onCreate中通過View.post能獲取寬高

筆者解惑原文杰捂,并對原文有所借鑒之處舆床,特此聲明,讀者可一并閱讀原文:鏈接

慣例嫁佳,導語:
最怕一生碌碌無為挨队,還聊以自慰平淡是真。

平淡無為.png

在之前的文章《Android解決在onCreate中獲取View的width蒿往、Height為0的方法》提到過盛垦,可以通過View.post方式:

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

之后有同學問到:

question.png

本著知其然知其所以然的學習態(tài)度,覺得還是有必要把為什么通過View.post方式就能獲取到View的width/height的原理捯飭捯飭熄浓。

首先情臭,觀察View.post方法的實現(xiàn):

public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        // Assume that post will succeed later
        ViewRootImpl.getRunQueue().post(action);
        return true;
    }

主要是根據(jù)attachInfo是否被初始化決定執(zhí)行方式,那么attachInfo在Activity的onCreate()執(zhí)行時到底是不是null呢赌蔑?關于attachInfo的初始化俯在,我們可以在View源碼中找到,其只有在dispatchAttachedToWindow()方法才被賦值娃惯,而dispatchAttachedToWindow()方法的調用是來自于ViewGroup跷乐,繼續(xù)向上層去找,我們就不得不追溯到ViewRootImpl的perFormTraversals()方法了趾浅,熟悉view流程的都知道愕提,view的三大流程就是通過這個稱為“執(zhí)行遍歷”的方法來完成的馒稍。但是這個方法有整整800行代碼,就只取主要流程的代碼了:

private void performTraversals() {
        // cache mView since it is used so much below...
        final View host = mView;
        if (mFirst) {
            ···
            host.dispatchAttachedToWindow(mAttachInfo, 0);
        } 
        ···
        //先于performMeasure被執(zhí)行了
        getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();
 }

在這里浅侨,我們明確了attachInfo的初始化纽谒,在onCreate中執(zhí)行View.post的時候,attachInfo還是null如输」那回到post的代碼,確認執(zhí)行的是 ViewRootImpl.getRunQueue().post(action) 的邏輯:

static final class RunQueue {
        void post(Runnable action) {
            postDelayed(action, 0);//沒有延時
        }

        void postDelayed(Runnable action, long delayMillis) {
            HandlerAction handlerAction = new HandlerAction();
            handlerAction.action = action;
            handlerAction.delay = delayMillis;

            synchronized (mActions) {
                mActions.add(handlerAction);
            }
        }
    }

RunQueue只是將需要執(zhí)行的runnable消息暫時做一個存儲不见,并且此消息沒有延時澳化。在前面ViewRootImpl.performTraversals()方法中我有注釋:

//先于performMeasure被執(zhí)行了
        getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();

getRunQueue().executeActions()竟然先于performMeasure()執(zhí)行了,這還了得嗎稳吮?如果是這樣的話缎谷,我們通過View.post()方式獲取的應該是還沒有測量過的寬高呀!

好吧灶似,我們還要看一下RunQueue.executeActions()的實現(xiàn):

    void executeActions(Handler handler) {
            synchronized (mActions) {
                final ArrayList<HandlerAction> actions = mActions;
                final int count = actions.size();

                for (int i = 0; i < count; i++) {
                    final HandlerAction handlerAction = actions.get(i);
                    handler.postDelayed(handlerAction.action, handlerAction.delay);
                }

                actions.clear();
            }
        }

這里面其實也是調用Handler去post我們的Runnable列林,而ViewRootImpl的Handler就是主線程的Handler,因此在performTraversals()被執(zhí)行的Runnable其實是被主線程的Handler的post到執(zhí)行隊列里面了喻奥。這里說明下席纽,Android的運行其實是一個消息驅動模式,不了解消息機制的也可以看我的另一篇《Android源碼 從runOnUiThread聊聊消息機制》撞蚕。
根據(jù)消息機制原理,我們需要等待主線程的Handler執(zhí)行完當前的任務过牙,才會去執(zhí)行我們View.post的那個Runnable甥厦。
那么當前正在執(zhí)行了什么任務呢?答案是TraversalRunnable寇钉,具體我們也要看ViewRootImpl的源碼刀疙,里面有TraversalRunnable的定義:

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

void doTraversal() {
        if (mTraversalScheduled) {
            ···
            performTraversals();
            ···
        }
    }

關于TraversalRunnable的調度時機,不再此篇范圍了扫倡。
到這里谦秧,我能回答開篇有同學提到的問題了吧:

View.post(runnable)方法的代碼會在view的draw方法之前調用么?

如果按照我們剛分析的performTraversals()方法的執(zhí)行流程:

getRunQueue().executeActions(attachInfo.mHandler);
        ...
        performMeasure();
        ...
        performLayout();
        ...
        performDraw();

那么答案是明確的:View.post(runnable)方法的代碼會在view的draw方法之前調用。

但撵溃,這是真的嗎疚鲤?不是!

OMG! 為毛缘挑?我曾也天真的以為集歇。

我還是去做了實驗,結果:

textview.png

注意到了沒语淘?measure被執(zhí)行了三次诲宇,layout被執(zhí)行了兩次际歼,中間穿插了post的Runnable的執(zhí)行結果,然后在第二次的layout之后才會去執(zhí)行draw流程姑蓝!

通過上面的分析鹅心,可以明確的是:第一次layout和第二次layout應該是兩個不同的任務。因為在這中間已經有了View.post的Runnable的執(zhí)行結果纺荧,所以有了結論是:一共有三個任務旭愧,第一次performTraversals、我們的Runnable虐秋、第二次performTraversals榕茧。

那么為什么會執(zhí)行兩次performTraversals呢?還是要回到performTraversal()方法中客给,取出與performDraw相關的代碼:

           ......
    if (!cancelDraw && !newSurface) {
        if (!skipDraw || mReportNextDraw) {
            ......
            performDraw();
        }
    } else {
        if (viewVisibility == View.VISIBLE) {
            
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            for (int i = 0; i < mPendingTransitions.size(); ++i) {
                mPendingTransitions.get(i).endChangingAnimations();
            }
            mPendingTransitions.clear();
        }
    }
    ......

可以看出用押,當newSurface為真時,performTraversals函數(shù)并不會調用performDraw函數(shù)靶剑,而是調用scheduleTraversals函數(shù)蜻拨,從而再次調用一次performTraversals函數(shù),從而再次進行一次測量桩引,布局和繪制過程缎讼。

到這里終于有了明確答案了:

View.post(runnable)方法的代碼不會在view的draw方法之前調用。

但是Android系統(tǒng)設計時坑匠,為什么要將整個初始化過程設計成這樣血崭?為什么當Surface為新的時候,要推遲繪制厘灼,重新進行一輪初始化?

希望有經驗的同學解惑啊夹纫,歡迎討論。

together.jpeg
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末设凹,一起剝皮案震驚了整個濱河市舰讹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌闪朱,老刑警劉巖月匣,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奋姿,居然都是意外死亡锄开,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進店門胀蛮,熙熙樓的掌柜王于貴愁眉苦臉地迎上來院刁,“玉大人,你說我怎么就攤上這事粪狼⊥诵龋” “怎么了任岸?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狡刘。 經常有香客問我享潜,道長,這世上最難降的妖魔是什么嗅蔬? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任剑按,我火速辦了婚禮,結果婚禮上澜术,老公的妹妹穿的比我還像新娘艺蝴。我一直安慰自己,他們只是感情好鸟废,可當我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布猜敢。 她就那樣靜靜地躺著,像睡著了一般盒延。 火紅的嫁衣襯著肌膚如雪缩擂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天添寺,我揣著相機與錄音胯盯,去河邊找鬼。 笑死计露,一個胖子當著我的面吹牛博脑,可吹牛的內容都是我干的。 我是一名探鬼主播票罐,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼趋厉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了胶坠?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤繁堡,失蹤者是張志新(化名)和其女友劉穎沈善,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體椭蹄,經...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡闻牡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了绳矩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罩润。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖翼馆,靈堂內的尸體忽然破棺而出割以,到底是詐尸還是另有隱情金度,我是刑警寧澤,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布严沥,位于F島的核電站猜极,受9級特大地震影響,放射性物質發(fā)生泄漏消玄。R本人自食惡果不足惜跟伏,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望翩瓜。 院中可真熱鬧受扳,春花似錦、人聲如沸兔跌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浮定。三九已至相满,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間桦卒,已是汗流浹背立美。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留方灾,地道東北人建蹄。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像裕偿,于是被迫代替她去往敵國和親洞慎。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內容