0%

View.post(Runnable)为何能拿到View的测量宽高

前言

在上一篇文章自定义字体选择控件中,我们使用了View.post(Runnable)的方法获取了View的测量宽高,为什么这样是可行的呢?

分析

下面,就让我们从源码角度来分析一下吧。注:本文所用源码为Android 7.0 (API 24)

View.post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* <p>Causes the Runnable to be added to the message queue.
* The runnable will be run on the user interface thread.</p>
*
* @param action The Runnable that will be executed.
*
* @return Returns true if the Runnable was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting.
*
* @see #postDelayed
* @see #removeCallbacks
*/
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.getRunQueue()

1
2
3
4
5
6
7
8
9
10
11
/**
* Returns the queue of runnable for this view.
*
* @return the queue of runnables for this view
*/
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}

HandlerActionQueue

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

我们可以看到,这些Runnable其实是没有被立即执行,而是保存到了HandlerActionQueue的mActions中,那它们什么时候被执行呢?答案是executeActions

HandlerActionQueue.executeActions()

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

其实就是调用了Handler.postDelayed(),那什么时候调用executeActions(Handler handler)呢?我们回到View中,搜索executeActions,发现只有一处调用了该方法

View.dispatchAttachedToWindow()

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* @param info the {@link android.view.View.AttachInfo} to associated with
* this 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();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
// Calling onVisibilityChanged directly here since the subtree will also
// receive dispatchAttachedToWindow and this same call
onVisibilityAggregated(vis == VISIBLE);
}
}
// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}

到这里,好像我们的线索断了,因为我们发现这里只是把哪些Runnable添加到了info.handler中,就没了。显然我们对这个结果是不满意的。接下来我们再看一下这个handler之前到底有么有添加其他的Runnable.

全局搜索了一下,发现mAttachInfo只有在View.dispatchAttachedToWindow()的时候赋非空值,而在View.dispatchDetachedFromWindow()的时候赋值为空。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* A set of information given to a view when it is attached to its parent
* window.
*/
final static class AttachInfo {
// ... 省略部分代码
/**
* A Handler supplied by a view's {@link android.view.ViewRootImpl}. This
* handler can be used to pump events in the UI events queue.
*/
final Handler mHandler;
// ... 省略部分代码
}

看了AttachInfo类中mHandler的定义,我们发现这个Handler就是主线程的Handler。到这里,显然还没有没有发现Handler之前到底有没有其他的Runnable。不甘心,于是又去Debug源码,发现是ViewRootImpl这个类里调用了View.dispatchAttachedToWindow(),而且只有一处,真是天助我也!

ViewRootImpl.performTraversals()

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
31
32
33
34
35
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
// ... 省略部分源码
if (mFirst) {
// ... 省略部分源码
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mHasWindowFocus = false;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfiguration.setTo(host.getResources().getConfiguration());
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
//Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
// ... 省略部分源码
}
// ... 省略部分源码
peformMeasure();
// ... 省略部分源码
peformLayout();
// ... 省略部分源码
peformDraw();

// ... 省略部分源码

}

继续看mAttachInfo在哪里初始化,我们发现mAttachInfo在构造方法里被赋值了。

ViewRootImpl.ViewRootImpl()

1
2
3
4
5
public ViewRootImpl(Context context, Display display) {
// ... 省略部分源码
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
// ... 省略部分源码
}

继续看Handler在哪里赋值,直接就在成员变量中赋值了。

final ViewRootHandler mHandler = new ViewRootHandler();
到这里,我们该关心mHandler什么时候往消息队列中发消息了。还记得之前说的performTraversals吗?再继续看,哪个地方调用了该方法。

ViewRootImpl.doTraversal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

又是哪里调用了doTraversal()呢?

1
2
3
4
5
6
7
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

到这里,你应该有点眉目了,是放在一个Runnable中。

调用这个Runnable,也即往主线程队列发消息的地方有两处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}

这里又不得不提一下Android的消息驱动模型了。我们知道,Android程序的入口其实是在ActivityThread里面的main方法

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
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

可以看到这里创建了主线程的Handler和Looper,并且最后执行了Looper.loop()进入循环。它一直在等待消息去驱动它继续执行下去。而像我们post的那个Runnable就是消息的一种了。

讲到这里,你大概明白了。因为Android应用程序是靠消息驱动的,当前运行的performTraversals()其实是通过一个mTraversalRunnable投递到主线程的消息队列中执行的,因为performTraversals()中又执行了peformMeasure()、peformLayout()、peformDraw(),因此执行完performTraversals()之后是可以拿到View的宽高的。而通过View.post(Runnable)投递过来的消息需要等到当前消息(即mTraversalRunnable,当中执行了performTraversals())执行完成之后,才能接着执行。因此是能够拿到View的宽高的。

参考