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.javaprivate 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.javapublic 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.javapublic final class Choreographer { private final Looper mLooper; private final FrameHandler mHandler; //保证没个线程内都是不同的Choreographer实例 private static final ThreadLocal sThreadInstance = new ThreadLocal() { @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的宽高。
最后附上一张流程图便于理解 View的post()为什么可以获取View的宽高
文章图片
image 【View的post()为什么可以获取View的宽高】参考文章

    推荐阅读