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

从上面代码可以知道,当调用post()方法时,首先会判断mAttachInfo是否为空,如果不为空,则调用Handler处理消息,否则,将将消息放入到RunQueue消息队列。

  • 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()创建一个HandlerActionQueue,并将我们使用的runable放入到mActions内。

  • 总结:
    其实我们调用的post方法执行流程有两种,一种是直接使用Handler去执行,第二是将我们的runabale存储到队列中。
二、runable队列被执行
在HandlerActionQueue中我们知道,有一个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内部通过for循环的方式,将消息队列的Runable取出,使用Handler发送到主线程。

三、HandlerActionQueue的executeActions方法合适被执行。
在View的源码中 Ctrl+F 我们搜索mRunQueue.executeActions。中我们找到只有在View的dispatchAttachedToWindow方法中执行。dispatchAttachedToWindow的核心代码如下:
  void dispatchAttachedToWindow(AttachInfo info, int visibility) {
        mAttachInfo = info;
        ...
        if (mRunQueue != null) {
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
        performCollectViewAttributes(mAttachInfo, visibility);
        onAttachedToWindow();

        ...
    }

所以当View执行dispatchAttachedToWindow方法时,才将我们最开始发送的Runable对象发送到主线程处理。

但是,当我们在View内部搜索何时调用dispatchAttachedToWindow时,并没有找到。但是View的绘制、测量、布局,都由有父布局开始,所以我们在父布局的中查找调用的地方。

四、ViewGroup的dispatchAttachedToWindow
在ViewGroup的内部我们找到两个地方调用子View的dispatchAttachedToWindow,一是在addViewInner内调用,addViewInner使用addView调用的,这就是我们在手动创建View时,
没有调用父View的addView()方法将View添加进父View时,我们添加的View的post方法不执行的原因。二是在dispatchAttachedToWindow方法内。
 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()内主要是调用父类的dispatchAttachedToWindow()方法,然后循环子View,调用子View的dispatchAttachedToWindow()。

而ViewGroup的的dispatchAttachedToWindow()方法什么时候被调用呢?从上面我们知道,子View的dispatchAttachedToWindow()是有父View的dispatchAttachedToWindow()调用。而DecorView是我们所有布局的父布局。所以最终我们的View的dispatchAttachedToWindow()调用是由DecorView的dispatchAttachedToWindow()方法发起的。但是DecorView的dispatchAttachedToWindow()什么时候调用呢。由于我们之前学习了View的绘制流程【http://note.youdao.com/noteshare?id=e58f9423ec333f21ad673f971d340f5b&sub=9E9245E12A244C5395734561C7359B37】,我们知道,View的测量、布局、绘制都是从DecorView开始,都是在ViewRootImpl内的performTraversals()内调用,所以我们接下来看一下performTraversals()核心代码

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

从上面我们知道了,原来dispatchAttachedToWindow()的调用是由、ViewRootImpl的 performTraversals() 发起的,但是我们注意到,dispatchAttachedToWindow()的发起是在performMeasure();之前。但是那为什么我们能够在View.post()内获取View的宽高呢。

六、为什么dispatchAttachedToWindow()的发起是在performMeasure(),而我们我们能够在View.post()内获取View的宽高?
由于之前我们分析过ViewRootImpl的performTraversals()执行是在ViewRootImpl的TraversalRunnable内部类中执行。而TraversalRunnable是一个实现Runable的ViewRootImpl的内部类。而scheduleTraversals()的执行是由scheduleTraversals()方法实现。所以分析如下:
  • 我们先看一下performTraversals()的执行
ViewRootImpl.java
    //1、执行mTraversalRunnable
     void scheduleTraversals() {
            ...
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
    }
   // 2、执行doTraversal();
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
  // 3、执行performTraversals();
   void doTraversal() {
        ...
        performTraversals();
        ...
    }

从上面我们知道,TraversalRunnable内最终执行了performTraversals()。而TraversalRunnable的执行是由mChoreographer来实现的,那mChoreographer怎么实现执行的呢。

  • Choreographer执行TraversalRunnable

Choreographer的核心源码如下:

Choreographer.java

public final class Choreographer {
      private final Looper mLooper;
    private final FrameHandler mHandler;
        //保证没个线程内都是不同的Choreographer实例
        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;
    //构造函数
     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发送出去处理。
                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的绘制过程也是基于消息机制实现,在View的加载过程中是在主线程实现的,所以Choreographer的Looper是主线程,内部的Handler也是在主线程处理消息。所以最终View的绘制流程是Choreographer内部主线程的Handler发送消息并处理实现。

到这里,我们回头看一下,我们使用View.post()方法时,最终也是有主线程的Handler发送并处理消息实现。由于之前我们学习的Handler机制【http://note.youdao.com/noteshare?id=8670d5fcdde6bf53683fa58f489ca1d3&sub=ECF647152E3B4D42BF4DA272B156E6FB
在Looper内循环取出消息的方式来处理消息,当一个消息处理完之后再取出另一个消息,由Handler处理。既然这样,我们的View的绘制加载和View.post()都是又主线Handler来,所以只有当View的绘制加载的消息完成之后,才会处理我们View.pos()发送来的消息,所以我们才够在View.post()内获取到View的宽高。

最后附上一张流程图便于理解

image

参考文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容