通常在onCreate方法中我们会将布局中的View初始化,此时如果你调用View.getWidth()或View.getHeight()方法获取到的值都是0。那么为什么此时在View.post中却可以或得到呢?下面为大家一探究竟
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
复制代码
View.post中处理了两种情况,dispatchAttachedToWindow成功后,既attachInfo不为空的时候直接发送到主线程消息队列中等待执行,如果没attach那么会缓存在队列中。
dispatchAttachedToWindow会在ViewRootImpl.performTraversals函数中调用。
performTraversals中关键执行方法如下:
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
...
//关联窗口
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
//如果View有缓存的任务添加到主线程消息队列中等待执行
getRunQueue().executeActions(mAttachInfo.mHandler);
...
//测量绘制流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
}
复制代码
我们知道在Android中事件驱动都是基于Handler消息机制,本质上都是从消息队列中依次取出事件执行那么可以获得到View宽高的真相如下图:
在当前执行的任务(doTraversal)里面,会取出先前View缓存队列中的任务,继续投递到消息队列,而此时,后面的测量、布局、绘制因为是在doTraversal这个任务里面的,处于正在执行状态,所以肯定优先于缓存的任务。等到doTraversa执行完毕(包括绘制流程),在缓存的任务中就可以正常的获取到宽高了
- 如果View执行过dispatchAttachedToWindow,那么在你获取宽高时代表他已经执行过doTraversal消息流程了既调用过绘制流程,所以此时是可以获得宽高的。
tips onResume时触发WindowManagerImpl.addView -> ViewRootImpl.setView -> requestLayout -> scheduleTraversals ->mChoreographer.postCallback(mTraversalRunnable); 所以我们在onResume之前的onCreate中无法直接获取View宽高
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
复制代码
- 当View没有dispatchAttachedToWindow时缓存的消息会在绘制流程之后进行执行,所以View.post可以在onCreate中获得View的宽高。