高级UI<第九篇>:源码分析UI的绘制流程(Android 9.
本文其实是第七篇的续篇,第七篇主要讲解Activity的绘制流程,本文主要讲解View本身是如何绘制的,即UI的绘制流程。
花了几个小时的源码分析,最终确认,UI实际上从Activity的onResume
之后才真正开始绘制,或者说,UI绘制的入口是ActivityThread
类中的handleResumeActivity
方法。
源码如下:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
//...(省略)
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);
}
}
//...(省略)
}
以上代码给我们的信息如下:
【通过Activity获取Window对象】
r.window = r.activity.getWindow();
Window对象是UI的顶级视图,一般所谓的Window就是PhoneWindow。
【通过Window获取DecorView对象】
View decor = r.window.getDecorView();
DecorView是显示Activity的根视图,其作用是现实状态栏、显示ActionBar、显示setContentView设置的布局以及底部导航栏。
【通过DecorView获取ViewRootImpl对象】
ViewRootImpl impl = decor.getViewRootImpl();
ViewRootImpl对象主要负责View的绘制,DecorView和ViewRootImpl 互相绑定,一个DecorView只能有一个ViewRootImpl 实例。
【将DecorView添加到Window中】
wm.addView(decor, l);
那么,ViewRootImpl是如何绘制View的呢?
当DecorView添加到Window中时,View就开始了绘制,那么就跟踪以下其过程吧,请看下面的跟踪流程:
【第一步】
wm.addView(decor, l);
【第二步】
执行WindowManagerImpl中的addView
方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
【第三步】
执行WindowManagerGlobal的addView
方法
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//...(省略)
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
代码中最核心代码是:
root.setView(view, wparams, panelParentView);
这里的root就是ViewRootImpl对象。
【第四步】
执行ViewRootImpl的setView
方法
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
//...(省略)
// 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();
//...(省略)
}
requestLayout
:在添加到窗口管理器之前安排第一个布局,以确保在从系统接收任何其他事件之前执行重新布局。
【第五步】
执行ViewRootImpl的requestLayout
方法,以完成初步布局
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
【第六步】
执行ViewRootImpl的scheduleTraversals
方法,向主线程发送消息请求遍历。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
核心代码如下:
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
所以,执行TraversalRunnable的run方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
【第七步】
执行ViewRootImpl的doTraversal
方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
【第八步】
执行ViewRootImpl的performTraversals
方法,开始进入UI的绘制
private void performTraversals() {
//...(省略)
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//...(省略)
performLayout(lp, mWidth, mHeight);
//...(省略)
performDraw();
//...(省略)
}
最终会依次执行performMeasure
、performLayout
、performDraw
这三个方法。
查看这三个方法的源码之后发现,绘制UI,其实就是分别执行了以下三个方法:
//布局的测量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
//布局的摆放
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//布局的绘制
mAttachInfo.mTreeObserver.dispatchOnDraw()
listeners.get(i).onDraw();
最终执行View的以下三个方法:
//布局的测量
onMeasure(widthMeasureSpec, heightMeasureSpec);
//布局的摆放
onLayout(changed, l, t, r, b);
//布局的绘制
onDraw()
总结:
当执行到handleResumeActivity时,Activity的onResume方法被调用,然后WindowManager会将DecorView设置给ViewRootImpl,这样,DecorView就被加载到Window中了,此时界面还没有显示出来,还需要经过View的measure,layout和draw方法,才能完成View的工作流程。我们需要知道View的绘制是由ViewRoot来负责的,每一个DecorView都有一个与之关联的ViewRoot,这种关联关系是由WindowManager维护的,将DecorView和ViewRoot关联之后,ViewRootImpl的requestLayout会被调用以完成初步布局,通过scheduleTraversals方法向主线程发送消息请求遍历,最终调用ViewRootImpl的performTraversals方法,这个方法会执行View的measure、layout和draw流程。
[本章完...]