工作中应该都有碰到过这个问题,解决方案一般有view.post(Runnable)、view.getViewTreeObserver().addOnGlobalLayoutListener(OnGlobalLayoutListener listener)。 昨天仔细了解了一下view.post(Runnable)方法,为什么可以在里面拿到view的宽度呢?
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;
}
可以看到attachInfo不为空,调用的是attachInfo.mHandler来处理这个action的,否则getRunQueue().post(action)来处理的。来看一下getRunQueue():
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
public class HandlerActionQueue {
private HandlerAction[] mActions;
private int mCount;
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++;
}
}
...
}
这里创建了一个存放HandlerAction的队列(数组),所有之前通过post传进来的任务全部,在mAttachInfo == null时,都存在这里面。队列初始大小为4,可以通过GrowingArrayUtils扩容。HandlerAction是一个简单封装了Runnable和延时delay(long)的类。具体执行队列里面HandlerAction的方法为:
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()具体调用的地方有4个,View中1处,ViewGroup中有3处。在View中调用的位置为:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
** mAttachInfo = info**;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
registerPendingFrameMetricsObservers();
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// Transfer all pending runnables.
if (mRunQueue != null) {
**mRunQueue**.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
...
}
我们看一下方法名的意思:分发一个将View附加到Window的操作。 为什么在这个方法里面可以调用mRunQueue.executeActions()来执行之前所有存储的任务呢,答案当然是因为mAttachInfo = info在第一行被赋值了,而处理mRunQueue队列中任务的handler就在mAttachInfo中:
/**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
* 这个handler可以把事件传给UI线程进行处理
*/
final Handler mHandler;
这里我猜测handler是UI线程给ViewRootImpl,ViewRootImpl再给View的。(如有不对请指出)
ViewGroup中的dispatchAttachedToWindow()也类似,在ViewRootImpl的performTraversals()中递归遍历所有的子view,并把mAttachInfo付给所有的子view,这里ViewGroup和子view的mAttachInfo都是同一个。
在performTraversals()方法里面,performMeasure()在2155行、performLayout()在2200行、performDraw()在2347行;而dispatchAttachedToWindow()在1658行,我们知道view在绘制完成之后才可以拿到宽高,那为什么先执行dispatchAttachedToWindow()却可以拿到宽高呢,因为Android是基于消息驱动的,也就是performTraversals()和dispatchAttachedToWindow()最终都是转化位Message被looper从message里面取出来按顺序执行的。
结论:view.post()需要执行的任务在mAttachInfo == null时,会前被缓存起来,在mAttachInfo被赋值后依次执行;此后如果还有post进来的任务,则会立即执行。