Everything negative-pressure,challenges-is all an opportunity for me to rise
10 May 2020
这篇文章主要讨论一下Android中的View的Measure,Layout,Draw流程。
在讨论整个流程之前,首先抛出两个问题,这两个问题的答案我会在后续进行解答:
Activity
的onCreate这个生命周期方法中尝试获取某个View的宽高为0?Activity
的onCreate这个生命周期方法中使用View#post()
或viewTreeObserver
可以获取到某个View的宽高?1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val myView = findViewById<MyView>(R.id.myView)
myView.post {
val width = myView.measuredWidth
val height = myView.measuredHeight
}
myView.viewTreeObserver.addOnGlobalLayoutListener(object :ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
myView.viewTreeObserver.removeOnGlobalLayoutListener(this)
val width = myView.measuredWidth
val height = myView.measuredHeight
}
})
}
由于Activity
是Ui展示的重要组件,所以我们从Activity
生命周期的角度去讨论View的Measure,Layout,Draw流程。ActivityThread
是个非常重要的类,它管理主线程的执行,调度,执行应用中的Activities,Broadcasts等组件,这个类中有几个和Activity
密切相关的方法:
Activity
的创建和初始化,并调用Activity
的生命周期方法-onCreate()。DecorView
的创建并添加到与Activity
(Window
)关联的WindowManager
上,开始进行View Tree的Measure, Layout, Draw,并调用Activity
的生命周期方法-onResume()。由上可知,在执行Activity
的生命周期方法onCreate()时,尚未开始进行View Tree的Measure,所以此时获取不到View的宽高,这就解决了第一个问题。
在ResumeActivity
后,开始执行View Tree的Measure, Layout, Draw流程,我们开始详细讨论这一部分。
这是Resume Activity的关键方法,我们摘取部分关键代码:
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
60
61
62
63
64
// ActivityThread.java
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
// TODO Push resumeArgs into the activity for consideration
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
if (mActivitiesToBeDestroyed.containsKey(token)) {
// Although the activity is resumed, it is going to be destroyed. So the following
// UI operations are unnecessary and also prevents exception because its token may
// be gone that window manager cannot recognize it. All necessary cleanup actions
// performed below will be done while handling destruction.
return;
}
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
...
}
我们可以看到,这里创建了一个DecorView
并添加到Activity
的WindowManager
上,我们具体看看WindowManager#addView()
的实现:
1
2
3
4
5
6
7
8
9
10
// WindowManagerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
实际使用的是WindowManagerGlobal
这个单例对象进行DecoreView的添加,我们再进入看看:
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
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
实际又是通过ViewRootImpl
设置DecoreView
的,我们继续进入看看:
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
60
61
62
// ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
注意mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
这个地方,这个意思是在下一帧的时候执行mTraversalRunnable
这个Runnable
,它最终使用的是Handler
。
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
// Choreographer.java
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame on vsync.");
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
我们追踪到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
// ViewRootImpl.java
private void performTraversals() {
...
host.dispatchAttachedToWindow(mAttachInfo, 0);
...
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
...
performLayout(lp, mWidth, mHeight);
...
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
..
performDraw();
}
上面这个方法包含Measure, Layout, Draw整个过程。
ViewRootImpl#measureHierarchy() => ViewRootImpl#performMeasure() => DecorView#measure() => DecorView#onMeasure()
ViewRootImpl#performLayout() => DecorView#layout() => DecorView#onLayout()
ViewRootImpl#performDraw() => ViewRootImpl#draw() => ViewRootImpl#drawSoftware() => DecorView#draw() => DecorView#onDraw()
从ViewRootImpl
的performTraversals
这个方法我们也可以解释第二个问题。
Activity#onCreate
中使用View#post
可以获取到宽高?我们先看一下View#post
的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// View.java
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;
}
private HandlerActionQueue getRunQueue() {
if (mRunQueue == null) {
mRunQueue = new HandlerActionQueue();
}
return mRunQueue;
}
执行Runnable
有两种方式,第一种使用mAttachInfo
,第二种使用mRunQueue
,那mAttachInfo
什么时候被赋值呢?,mRunQueue
什么时候被调用呢?
答案在View
的dispatchAttachedToWindow()
方法中。这个方法在ViewRootImpl
的performTraversals()
中被调用,mAttachInfo
和mRunQueue
使用了ViewRootHandler
去发送消息,当这个消息被分发的时候,performTraversals
已经执行完成,这也意味这DecoreView Tree已经完成Measure,所以此时可以获取到View的宽高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// View.java
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
...
}
Activity#onCreate
中使用View#viewTreeObserver
可以获取到宽高?同样的方法,查看源码:
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
// View.java
public ViewTreeObserver getViewTreeObserver() {
if (mAttachInfo != null) {
return mAttachInfo.mTreeObserver;
}
if (mFloatingTreeObserver == null) {
mFloatingTreeObserver = new ViewTreeObserver(mContext);
}
return mFloatingTreeObserver;
}
public void addOnGlobalLayoutListener(OnGlobalLayoutListener listener) {
checkIsAlive();
if (mOnGlobalLayoutListeners == null) {
mOnGlobalLayoutListeners = new CopyOnWriteArray<OnGlobalLayoutListener>();
}
mOnGlobalLayoutListeners.add(listener);
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
...
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
...
}
当View被attach到Window的时候,mFloatingTreeObserver
会被merge进AttachInfo
的mTreeObserver
中,那AttachInfo
的mTreeObserver
什么时候被调用呢?答案在ViewRootImpl
的performTraversals()
,可以退回上面看这个方法,在执行完Measure和Layout之后就调用AttachInfo#mTreeObserver
的dispatchOnGlobalLayout
方法。因为此时已经完成了Measure,所以可以获取到View的宽高。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// ViewTreeObserver.java
public final void dispatchOnGlobalLayout() {
// 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.
final CopyOnWriteArray<OnGlobalLayoutListener> listeners = mOnGlobalLayoutListeners;
if (listeners != null && listeners.size() > 0) {
CopyOnWriteArray.Access<OnGlobalLayoutListener> access = listeners.start();
try {
int count = access.size();
for (int i = 0; i < count; i++) {
access.get(i).onGlobalLayout();
}
} finally {
listeners.end();
}
}
}
— Lenox Xian