View的post()為什么可以獲取View的寬高

一分预、View.post()
  • post(Runnable action)
 public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }

從上面代碼可以知道,當(dāng)調(diào)用post()方法時(shí)放闺,首先會(huì)判斷mAttachInfo是否為空胀糜,如果不為空颅拦,則調(diào)用Handler處理消息,否則教藻,將將消息放入到RunQueue消息隊(duì)列距帅。

  • HandlerActionQueue
View.java
  private HandlerActionQueue getRunQueue() {
        if (mRunQueue == null) {
            mRunQueue = new HandlerActionQueue();
        }
        return mRunQueue;
    }
HandlerActionQueue.java

private HandlerAction[] mActions;
 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++;
        }
    }

從上面代碼知道, getRunQueue()創(chuàng)建一個(gè)HandlerActionQueue括堤,并將我們使用的runable放入到mActions內(nèi)碌秸。

  • 總結(jié):
    其實(shí)我們調(diào)用的post方法執(zhí)行流程有兩種,一種是直接使用Handler去執(zhí)行悄窃,第二是將我們的runabale存儲(chǔ)到隊(duì)列中讥电。
二、runable隊(duì)列被執(zhí)行
在HandlerActionQueue中我們知道轧抗,有一個(gè)executeActions(handler)方法恩敌。方法源碼如下:
HandlerActionQueue.java

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

從上面源碼我們知道,executeActions內(nèi)部通過for循環(huán)的方式横媚,將消息隊(duì)列的Runable取出纠炮,使用Handler發(fā)送到主線程。

三灯蝴、HandlerActionQueue的executeActions方法合適被執(zhí)行恢口。
在View的源碼中 Ctrl+F 我們搜索mRunQueue.executeActions。中我們找到只有在View的dispatchAttachedToWindow方法中執(zhí)行穷躁。dispatchAttachedToWindow的核心代碼如下:
  void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ...
    }

所以當(dāng)View執(zhí)行dispatchAttachedToWindow方法時(shí)耕肩,才將我們最開始發(fā)送的Runable對(duì)象發(fā)送到主線程處理。

但是,當(dāng)我們?cè)赩iew內(nèi)部搜索何時(shí)調(diào)用dispatchAttachedToWindow時(shí)猿诸,并沒有找到婚被。但是View的繪制、測量两芳、布局摔寨,都由有父布局開始,所以我們?cè)诟覆季值闹胁檎艺{(diào)用的地方怖辆。

四、ViewGroup的dispatchAttachedToWindow
在ViewGroup的內(nèi)部我們找到兩個(gè)地方調(diào)用子View的dispatchAttachedToWindow删顶,一是在addViewInner內(nèi)調(diào)用竖螃,addViewInner使用addView調(diào)用的,這就是我們?cè)谑謩?dòng)創(chuàng)建View時(shí)逗余,
沒有調(diào)用父View的addView()方法將View添加進(jìn)父View時(shí)特咆,我們添加的View的post方法不執(zhí)行的原因。二是在dispatchAttachedToWindow方法內(nèi)录粱。
 void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        ...
        super.dispatchAttachedToWindow(info, visibility);
        ...
        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()));
        }
        ...
    }

ViewGroup的dispatchAttachedToWindow()內(nèi)主要是調(diào)用父類的dispatchAttachedToWindow()方法腻格,然后循環(huán)子View,調(diào)用子View的dispatchAttachedToWindow()啥繁。

而ViewGroup的的dispatchAttachedToWindow()方法什么時(shí)候被調(diào)用呢菜职?從上面我們知道,子View的dispatchAttachedToWindow()是有父View的dispatchAttachedToWindow()調(diào)用旗闽。而DecorView是我們所有布局的父布局酬核。所以最終我們的View的dispatchAttachedToWindow()調(diào)用是由DecorView的dispatchAttachedToWindow()方法發(fā)起的。但是DecorView的dispatchAttachedToWindow()什么時(shí)候調(diào)用呢适室。由于我們之前學(xué)習(xí)了View的繪制流程【http://note.youdao.com/noteshare?id=e58f9423ec333f21ad673f971d340f5b&sub=9E9245E12A244C5395734561C7359B37】嫡意,我們知道,View的測量捣辆、布局蔬螟、繪制都是從DecorView開始,都是在ViewRootImpl內(nèi)的performTraversals()內(nèi)調(diào)用汽畴,所以我們接下來看一下performTraversals()核心代碼

五旧巾、ViewRootImpl的 performTraversals()
performTraversals的核心代碼如下
 private void performTraversals() {
    //是DecorView
     final View host = mView;
     ...
     host.dispatchAttachedToWindow(mAttachInfo, 0);
     ...
     performMeasure();
     ...
     performLayout();
     ...
     performDraw();
     ...
 }

從上面我們知道了,原來dispatchAttachedToWindow()的調(diào)用是由整袁、ViewRootImpl的 performTraversals() 發(fā)起的菠齿,但是我們注意到,dispatchAttachedToWindow()的發(fā)起是在performMeasure();之前坐昙。但是那為什么我們能夠在View.post()內(nèi)獲取View的寬高呢绳匀。

六、為什么dispatchAttachedToWindow()的發(fā)起是在performMeasure(),而我們我們能夠在View.post()內(nèi)獲取View的寬高疾棵?
由于之前我們分析過ViewRootImpl的performTraversals()執(zhí)行是在ViewRootImpl的TraversalRunnable內(nèi)部類中執(zhí)行戈钢。而TraversalRunnable是一個(gè)實(shí)現(xiàn)Runable的ViewRootImpl的內(nèi)部類。而scheduleTraversals()的執(zhí)行是由scheduleTraversals()方法實(shí)現(xiàn)是尔。所以分析如下:
  • 我們先看一下performTraversals()的執(zhí)行
ViewRootImpl.java
    //1殉了、執(zhí)行mTraversalRunnable
     void scheduleTraversals() {
            ...
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
    }
   // 2、執(zhí)行doTraversal();
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
  // 3拟枚、執(zhí)行performTraversals();
   void doTraversal() {
        ...
        performTraversals();
        ...
    }

從上面我們知道薪铜,TraversalRunnable內(nèi)最終執(zhí)行了performTraversals()。而TraversalRunnable的執(zhí)行是由mChoreographer來實(shí)現(xiàn)的恩溅,那mChoreographer怎么實(shí)現(xiàn)執(zhí)行的呢隔箍。

  • Choreographer執(zhí)行TraversalRunnable

Choreographer的核心源碼如下:

Choreographer.java

public final class Choreographer {
      private final Looper mLooper;
    private final FrameHandler mHandler;
        //保證沒個(gè)線程內(nèi)都是不同的Choreographer實(shí)例
        private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };
    //主線程的Choreographer
    private static volatile Choreographer mMainInstance;
    //構(gòu)造函數(shù)
     private Choreographer(Looper looper, int vsyncSource) {
        mLooper = looper;
        mHandler = new FrameHandler(looper);
        ...
    }
    
    //處理Runable
      public void postCallback(int callbackType, Runnable action, Object token) {
        postCallbackDelayed(callbackType, action, token, 0);
    }
      @TestApi
    public void postCallbackDelayed(int callbackType,
            Runnable action, Object token, long delayMillis) {
        ...
        postCallbackDelayedInternal(callbackType, action, token, delayMillis);
    }
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        ...

        synchronized (mLock) {
           ...
                使用Handler將Runable發(fā)送出去處理。
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            ...
        }
    }
    private final class FrameHandler extends Handler {
        public FrameHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DO_FRAME:
                    doFrame(System.nanoTime(), 0);
                    break;
                case MSG_DO_SCHEDULE_VSYNC:
                    doScheduleVsync();
                    break;
                case MSG_DO_SCHEDULE_CALLBACK:
                    doScheduleCallback(msg.arg1);
                    break;
            }
        }
    }
}

從上面知道脚乡,在View的繪制過程也是基于消息機(jī)制實(shí)現(xiàn)蜒滩,在View的加載過程中是在主線程實(shí)現(xiàn)的,所以Choreographer的Looper是主線程奶稠,內(nèi)部的Handler也是在主線程處理消息俯艰。所以最終View的繪制流程是Choreographer內(nèi)部主線程的Handler發(fā)送消息并處理實(shí)現(xiàn)。

到這里锌订,我們回頭看一下竹握,我們使用View.post()方法時(shí),最終也是有主線程的Handler發(fā)送并處理消息實(shí)現(xiàn)瀑志。由于之前我們學(xué)習(xí)的Handler機(jī)制【http://note.youdao.com/noteshare?id=8670d5fcdde6bf53683fa58f489ca1d3&sub=ECF647152E3B4D42BF4DA272B156E6FB
在Looper內(nèi)循環(huán)取出消息的方式來處理消息涩搓,當(dāng)一個(gè)消息處理完之后再取出另一個(gè)消息,由Handler處理劈猪。既然這樣昧甘,我們的View的繪制加載和View.post()都是又主線Handler來,所以只有當(dāng)View的繪制加載的消息完成之后战得,才會(huì)處理我們View.pos()發(fā)送來的消息充边,所以我們才夠在View.post()內(nèi)獲取到View的寬高。

最后附上一張流程圖便于理解

image

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末常侦,一起剝皮案震驚了整個(gè)濱河市浇冰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聋亡,老刑警劉巖肘习,帶你破解...
    沈念sama閱讀 222,865評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異坡倔,居然都是意外死亡漂佩,警方通過查閱死者的電腦和手機(jī)脖含,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來投蝉,“玉大人养葵,你說我怎么就攤上這事〈窭拢” “怎么了关拒?”我有些...
    開封第一講書人閱讀 169,631評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵,是天一觀的道長庸娱。 經(jīng)常有香客問我着绊,道長,這世上最難降的妖魔是什么涌韩? 我笑而不...
    開封第一講書人閱讀 60,199評(píng)論 1 300
  • 正文 為了忘掉前任畔柔,我火速辦了婚禮,結(jié)果婚禮上臣樱,老公的妹妹穿的比我還像新娘。我一直安慰自己腮考,他們只是感情好雇毫,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,196評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著踩蔚,像睡著了一般棚放。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上馅闽,一...
    開封第一講書人閱讀 52,793評(píng)論 1 314
  • 那天飘蚯,我揣著相機(jī)與錄音,去河邊找鬼福也。 笑死局骤,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暴凑。 我是一名探鬼主播峦甩,決...
    沈念sama閱讀 41,221評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼现喳!你這毒婦竟也來了凯傲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤嗦篱,失蹤者是張志新(化名)和其女友劉穎冰单,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體灸促,經(jīng)...
    沈念sama閱讀 46,699評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诫欠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,770評(píng)論 3 343
  • 正文 我和宋清朗相戀三年涵卵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呕诉。...
    茶點(diǎn)故事閱讀 40,918評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缘厢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甩挫,到底是詐尸還是另有隱情贴硫,我是刑警寧澤,帶...
    沈念sama閱讀 36,573評(píng)論 5 351
  • 正文 年R本政府宣布伊者,位于F島的核電站英遭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏亦渗。R本人自食惡果不足惜挖诸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,255評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望法精。 院中可真熱鬧多律,春花似錦、人聲如沸搂蜓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帮碰。三九已至相味,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間殉挽,已是汗流浹背丰涉。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斯碌,地道東北人一死。 一個(gè)月前我還...
    沈念sama閱讀 49,364評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像输拇,于是被迫代替她去往敵國和親摘符。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,926評(píng)論 2 361

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