今天刚好看到一个问题,为什么onCreate()
中使用 View.post(Runnable)
可以拿取到View的宽高,第一想法就是内部利用handler将Runnbale加入主线程MessageQueue,执行完测量任务之后再执行Runnable。
点开源码之后发现好像没那么简单,不过也没那么难
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
在post()
方法中,如果有attachInfo,直接attachInfo.mHandler.post(),若没有就getRunQueue().post();
// View
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
....
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
}
mAttachInfo 是在diapatchAttachedToWindow()
赋值的,ViewRootImpl的performTraversals()
会调用ViewGroup 的 diapatchAttachedToWindow()
,最后会调用子View的diapatchAttachedToWindow()
// ViewRootImpl
private void performTraversals() {
// 如果是首次绘制
if (mFirst) {
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
}
// ViewRootImpl
final ViewRootHandler mHandler = new ViewRootHandler();
// ViewRootImpl
public ViewRootImpl(Context context, Display display) {
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
}
从上面代码可以看出,mAttachInfo在ViewRootImpl构造,并且performTraversals()
,若i是首次绘制,会把mAttachInfo赋给View。
View里面的Handler其实就是ViewRootImpl的ViewRootHandler 。
ViewRootHandler直接在主线程构造,所以是消息处理都是在主线程中。
diapatchAttachedToWindow()后,mAttachInfo不为null,就可以直接通过ViewRootHandler处理Runnable,最后能够获取View宽高。
但是不是 diapatchAttachedToWindow()之前,就已经测量好宽高了呢?
// View.java
private HandlerActionQueue mRunQueue;
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
// HandlerActionQueue
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
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;
}
}
}
这里就直接说结果了,在attachInfo为null的时候,View.post()
会将Runnable保存在HandlerActionQueue中,等待dispatchAttachedToWindow()
的时候,mRunQueue.executeActions(info.mHandler) 会执行保存在HandlerActionQueue的Runnbale。
可见,我没有考虑到测量绘制还没开始的情况,如果测量还没开始就需要一个数组将Runnable存起来,在测量结束后执行。
但是都同样存在一个问题:是不是 diapatchAttachedToWindow()
之前,就已经测量好宽高了呢?
// ViewRootImpl
private void performTraversals() {
host.dispatchAttachedToWindow(mAttachInfo, 0);
performMeasure();
performLayout();
performDraw();
}
上面的伪代码指出,performTraversals()
中是先执行dispatchAttachedToWindow()
,后面才执行measure()
、layout()
等测量,这个怎么保证post.view()肯定能拿到宽高?
后面终于搞清楚了!
原来无论是perfromTraversals,还是后面的Runnable,都是保存在MessageQueue,Looper会执行完当前的任务,再从MessageQueue取出下个任务接着执行;
第一种情况,attachInfo.mHandler.post(action),和第二种情况,在dispatchAttachedToWindow executeActions()
,都是增加任务到在主线程的MessageQueue中,而Looper只会处理完当前的测量任务才会去执行View.post的任务。
可以看下面的示意图,当前还在执行performTraversals()
,后续的任务还在MessageQueue等待执行。