View的绘制流程
一、前言
下面这个概念,相信大家都耳熟能详了:
view 的绘制流程主要分为三个阶段:measure、layout、draw
measure:根据父view传递的MeasureSpec计算子View大小
layout:根据measure子view所得到的布局大小和布局参数,将子view放在合适的位置上。
draw:把view对象绘制到屏幕上。
但是,你知道DecorView是怎样添加到PhoneWindow的吗?DecorView又是从那里开始执行绘制的?
本篇幅就带大家从源码的角度分析一下view的绘制入口,下面我从源码的角度看一下这个流程。
在这之前,先要清楚一个概念。一个 Activity 包含一个Window,Window的实现类是PhoneWindow。一个PhoneWindow又会包含一个 DecorView ,DecorView是ViewTree里面的顶层布局,是继承于FrameLayout。
activity通过 setContent()方法加载布局的时候是将布局加载到DecorView 的ContentView上。
二、绘制入口
下面进入正题,我们先看看顶层视图DecorView是怎样被添加到PhoneWindow上的。
这就得从activity的启动说起了,当Activity初始化window和将布局添加到DecorView类之后,ActivityThread类会调用handleResumeActivity方法将顶层视图DecorView添加到PhoneWindow窗口中。
ActivityThread就是我们常说的主线程或UI线程,ActivityThread的main方法是一个APP的真正入口,MainLooper在它的main方法中被创建。
ActivityThread.java:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
..................
if (r.window == null && !a.mFinished && willBeVisible) {
//获得当前Activity的PhoneWindow对象
r.window = r.activity.getWindow();
//获得当前phoneWindow内部类DecorView对象
View decor = r.window.getDecorView();
//设置窗口顶层视图DecorView可见度
decor.setVisibility(View.INVISIBLE);
//得当当前Activity的WindowManagerImpl对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
//标记根布局DecorView已经添加到窗口
a.mWindowAdded = true;
//将根布局DecorView添加到当前Activity的窗口上面
wm.addView(decor, l);
.....................
将根布局DecorView添加到当前Activity的窗口上,wm.addView(decor, l); 这行代码的wm指的是WindowManagerImpl类,在WindowManagerImpl的addView中,又会调用mGlobal.addView(view, params, mDisplay, mParentWindow);这行代码,其中,mGlobal对象是WindowManagerGlobal类,在WindowManagerGlobal的addView方法中,会创建一个ViewRootImpl对象root,然后调用ViewRootImpl类中的setView方法。
WindowManagerGlobal类:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
............
ViewRootImpl root;
View panelParentView = null;
............
//获得ViewRootImpl对象root
root = new ViewRootImpl(view.getContext(), display);
...........
// do this last because it fires off messages to start doing things
try {
//将传进来的参数DecorView设置到root中
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...........
}
}
下面继续往下看WindowManagerGlobal类的ViewRootImpl.setView()方法,方法中,将传进来的DecorView对象赋值给全局的mView,标记已添加了DecorView,然后调用requestLayout()请求布局。
ViewRootImpl类:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//将顶层视图DecorView赋值给全局的mView
mView = view;
.............
//标记已添加DecorView
mAdded = true;
.............
//请求布局
requestLayout();
.............
}
}
接下来,继续进入ViewRootImpl的requestLayout()方法,看看里面做了啥:
ViewRootImpl类:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
................
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
..............
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
...............
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
}
............
requestLayout()方法中,会再调用scheduleTraversals(),然后会执行doTraversal()方法,最终在doTraversal()方法中调用了performTraversals()方法。所以,DecorView的绘制会进入到ViewRootImpl类中的performTraversals()方法执行。
下面再看看performTraversals方法又做了啥:
ViewRootImpl类:
private void performTraversals() {
//mView就是DecorView根布局
final View host = mView;
//成员变量mAdded已被赋值为true,因此条件不成立
if (host == null || !mAdded)
return;
//是否正在遍历
mIsInTraversal = true;
//是否马上绘制View
mWillDrawSoon = true;
.............
//顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
.....................
//在构造方法中mFirst已经设置为true,表示是否是第一次绘制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的类型是有状态栏的,那么顶层视图DecorView所需要窗口的宽度和高度就是除了状态栏
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {//否则顶层视图DecorView所需要窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
............
//获得view宽高的测量规格,mWidth和mHeight表示窗口的宽高,lp.widthhe和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
........................
//执行布局操作
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
.......................
//执行绘制操作
performDraw();
}
可以看到,在performTraversals方法中,最终调用performMeasure、performLayout、performDraw这三个方法,分别执行测量、布局和绘制操作。
下面我们做个总结:
在Activity启动过程中,会执行到onResume生命周期,这个过程会执行到
ActivityThread
的handleResumeActivity
方法,
handleResumeActivity
方法中,会执行WindowManagerImpl.addView
方法,进而执行WindowManagerGlobal.addView
方法,最终在WindowManagerGlobal.addView
方法中,会创建一个ViewRootImpl对象
,并调用ViewRootImpl.setView
方法,将DecorView赋值给ViewRootImpl的mView
这个成员变量,将根布局DecorView添加到Window上,然后执行requestLayout()
方法,requestLayout()
方法又会执行到scheduleTraversals()
方法,在
scheduleTraversals()
方法中,会通过Choreographer这个类post一个TraversalRunnable,并在下一个VSync信号到来时,才会执行TraversalRunnable,TraversalRunnable的run方法,会执行doTraversal()
方法,然后执行performTraversals()
方法,从而执行performMeasure
、performLayout
、performDraw
这三大流程。
由上可知,DecorView添加到PhoneWindow上并进入绘制的流程是这样的: