View绘制的原因
导致View绘制的原因不外乎以下几种:
1、ViewRootImpl执行了addView()、updateViewLayout()、removeView等操作;
2、View本身的size尺寸大小或者position位置发生了变化;
3、View本身的可见性如 VISIBLE、INVISIBLE、GONE 等发生了变化;
4、View的内部状态发生了变化:press、enable、background、foreground等;
虽然performTraversals()、setView()、setLayoutParams()、performLayout()、invalidate()等方法也是发起View绘制的路径之一。比如View从0到1时的绘制,起点则是ActivityThread 的 handleResumeActivity()方法中的wm.addView(decor, l)方法:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
......
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
final Activity a = r.activity;
.......
// 要加载的Activity的Window为空 && 要加载的Activity没有被finished && 要加载的Activity即将可见。
if (r.window == null && !a.mFinished && willBeVisible) {//向Window添加Activity的布局UI。
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 (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);//这里执行addView()操作,开始从0到1的View绘制;
} else {
......
}
}
// 如果窗口已经被添加,但是在恢复期间我们启动了另一个activity,那么还不要使窗口可见。
} else if (!willBeVisible) {
......
}
......
// 如果窗口已经添加,那么它现在是可见的,我们不是简单地完成,也不是开始另一个activity。
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
......
WindowManager.LayoutParams l = r.window.getAttributes();
if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) {
l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit;
if (r.activity.mVisibleFromClient) {
ViewManager wm = a.getWindowManager();
View decor = r.window.getDecorView();
wm.updateViewLayout(decor, l);//这里执行updateViewLayout()操作,当View发生本质性的变化时,会执行View的更新操作;
}
}
......
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();// Activity可见
}
}
......
}
但这些方法调用和执行,最后都归集到了scheduleTraversals()方法。然后从这里开始没有异议的执行同一条路线去执行View的绘制工作。因而作为View绘制的起点,scheduleTraversals()方法有着举足轻重的作用。
scheduleTraversals()源码如下:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 核心重点代码就是这行,其中 Runnable对象 mTraversalRunnable是核心中的核心
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
是的,没看错,View绘制的秘密就在mTraversalRunnable中:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
mTraversalRunnable对象的调用只有两个,一个是上面的scheduleTraversals()方法,另一个则是unscheduleTraversals()方法(执行着与scheduleTraversals()方法相反的逻辑)。
下面我们来看看TraversalRunnable接口内部的doTraversal()方法的实现:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 核心实现,内部会执行 performMeasure()、performLayout()、performDraw()方法,依次展开View的测量、布局和绘制工作
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals()方法源码伪代码如下:
private void performTraversals() {
......
int relayoutResult = 0;
boolean updatedConfiguration = false;
final boolean isViewVisible = viewVisibility == View.VISIBLE;
if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
......
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// 执行 Measure 操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
boolean measureAgain = false;
......
if (measureAgain) {
// 因为某些原因,再次执行 Measure 操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
......
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
......
if (didLayout) {
// 执行 Layout布局操作
performLayout(lp, mWidth, mHeight);
......
}
......
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
......
// 执行 Draw 绘制操作
performDraw();
} else {
if (isViewVisible) {
// 因为某些原因,再次执行 scheduleTraversals() 操作
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
......
}
}
}
performTraversals()方法主要干以下几件事情:
1、开启预测量:windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
2、开启窗口预布局:relayoutResult = relayoutWindow(params, viewVisibility, insetsPending)。relayoutWindow()方法是重新布局窗口的重要方法,它确保窗口中的所有视图都能按照最新的布局规则正确显示。
3、开启控件树测量:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
4、开启控件树布局:performLayout(lp, mWidth, mHeight);
5、判断各View是否需要重新绘制:if (!performDraw() && mActiveSurfaceSyncGroup != null) { mActiveSurfaceSyncGroup.markSyncReady(); }
基于上述几点,作为View绘制的起点,一般流程是:
scheduleTraversals()—>mTraversalRunnable—>doTraversal()—>performTraversals()—>performMeasure()(onMeasure())—>performLayout()(onLayout())—>performDraw()(onDraw())
performMeasure()
该方法会调用View的measure()方法开展View的measure()测量工作,但其实在View.measure()方法里面,则是调用onMeasure()方法具体展开测量工作;而ViewGroup则是没有自己的onMeasure()方法,但其子类重写了继承自View的onMeasure()方法。而且不同的子ViewGroup类,其onMeasure()方法实现也不同。但有一点是确定的,他们都会通过for循环递归遍历的调用子View的onMeasure()方法完成整个View树的子View的measure测量工作。
performLayout()
该方法会调用View的layout()方法内部会调用View的onLayout()方法来具体展开布局工作。但是View的onLayout()方法是一个空的实现,具体的Layout布局逻辑由其各个子View具体实现;而ViewGroup更狠,他的onLayout()方法直接是一个抽象方法,因此具体的Layout布局逻辑也只能由其各个子View去实现了。而在各个子viewGroup的onLayout()方法中,则是通过for循环递归遍历各个子View,并调用child.layout()方法完成各个子View的布局工作。
在布局流程上,具体是他先是调用父View的layout()方法来完成对本级View(他自己本身)的布局和定位。layout()方法接收4个参数:left、top、right、bottom,也就是一个View的四个顶点位置坐标,以此确定在父View中的位置。然后再通过for循环递归遍历View树上的各个子View,并调用子View的layout()方法完成各个子View的布局工作。
performDraw()
是调用我们根布局ViewGroup的onDraw()方法,该方法里面会依次调用backGround.Draw()方法绘制自己的背景,然后调用onDraw()方法绘制自身,接着调用dispatchDraw()方法绘制各个子View,最后是调用onDrawScrollBar()方法绘制装饰信息,至此View走完一个完整的绘制流程。
MeasureSpec
MeasureSpec,他是一个32位的整型数据,高2位表示SpecMod,低30位表示SpecSize,SpecMod有三种状态:一是AT_MOST,开发中对应wrap_content;二是EXACTLY,开发中对应match_parent或者具体的数值;三是UNSPECIFIED,对应的是无限制宽或者高的场景,比如RecycleView垂直滑动时,高度是不受限制的;在水平滑动时,长度是不受限制的。同样的,还有其他的View,如ScrollView/NestedScrollView的宽度/高度也是不受限制的。各级View中,DecorView的大小由其LayoutParams参数确定,其他的子View由自身的LayoutParams参数、自身的MeasureSpec参数和父View的LayoutParams参数共同确定。ViewGroup中子View的MeasureSpec参数是通过getChildMeasureSpec()方法来获取的。