我们都知道可以通过View去post一个Runnable对象,在其中就可以通过getWidth和getHeight获取到View的宽高,那么为什么呢?
1 2 3 4 5 6 7 8 9 10 11 public boolean post (Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null ) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true ; }
这边判断了attachInfo对象是否为空,不为空时通过attachInfo.mHandler去post该Runnable,为空时则调用getRunQueue()去post该Runnable,我们先看看getRunQueue().post方法
1 2 3 4 5 6 private HandlerActionQueue getRunQueue () { if (mRunQueue == null ) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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对象,该对象的post方法会调用到postDelayed方法,新建了一个HandlerAction对象,并执行了GrowingArrayUtils.append把handlerAction添加到数组中,然后就没了。那我们返回去看attachInfo是什么时候赋值的
1 2 3 4 5 void dispatchAttachedToWindow (AttachInfo info, int visibility) { mAttachInfo = info; ... }
可以看到在View的dispatchAttachedToWindow中传入了AttachInfo对象
1 2 3 4 5 6 public ViewRootImpl (Context context, Display display) { ... mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this , mHandler, this , context); ... }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ... View mView; ... private void performTraversals () { final View host = mView; ... host.dispatchAttachedToWindow(mAttachInfo, 0 ); ... getRunQueue().executeActions(mAttachInfo.mHandler); ... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, mWidth, mHeight); ... performDraw(); ... } 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 ; } }
在ViewRootImpl构造函数中创建了AttachInfo对象,并于performTraversals中调用了host.dispatchAttachedToWindow,接着调用了getRunQueue().executeActions方法,该方法中可以看到是将mActions数组中的Runnable对象用handler的postDelayed方法放入了消息队列中,这个mActions数组就是上面我们分析的在attachInfo为空的情况下会将Runnable对象添加到该数组中。再后面执行的就是我们非常熟悉的测量、布局和绘制过程,在performLayout之后就能拿到view的宽高了。虽然host.dispatchAttachedToWindow和getRunQueue().executeActions的调用都在测量之前,但是因为他们实际上都是在消息队列中加入了一个消息,所以实际执行到Runnable的时候view已经展示了出来,也就能获取到宽高了。
拓展知识:
1 ApplicationThread#scheduleResumeActivity->ActivityThread#handleResumeActivity-> Activity#makeVisible->WindowManager#addView->WindowManagerImpl#addView->WindowManagerGlobal#addView->ViewRootImpl#setView->ViewRootImpl#requestLayout->ViewRootImpl#scheduleTraversals->ViewRootImpl#performTraversals
一些关键方法的执行顺序,视图是在Activity#onResume之后才被添加进window并开始测量布局的,所以onResume也没办法直接获取到宽高。