0%

view.post为啥可以拿到view的宽高

我们都知道可以通过View去post一个Runnable对象,在其中就可以通过getWidth和getHeight获取到View的宽高,那么为什么呢?

1
2
3
4
5
6
7
8
9
10
11
//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;
}

这边判断了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
//HandlerActionQueue
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
//View#dispatchAttachedToWindow
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
}

可以看到在View的dispatchAttachedToWindow中传入了AttachInfo对象

1
2
3
4
5
6
//ViewRootImpl
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
//ViewRootImpl
...
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也没办法直接获取到宽高。