前言
一个View,从无到有会走三个流程,也就是老生常谈的measure,layout,draw三流程;
我们都知道Android视图是由一层一层构成的层级结构,直白点说,就是父View包含子View而子View又可以包含子View。所以绘制流程是由最外层的View开始,一步一步向内传递执行。而整个过程又是递归等待的,最外层的View需要等内层所有的View执行完绘制流程才结束,所以便有了”减少布局层级,可以有效提升App性能”这一经典总结。
正文
什么时候开始绘制?
而万物有始才有终,你不惹他,他也不会动手打你。View的绘制流程是什么时候开始的?谁触发的?明白这点后,才去考虑这个过程是怎样执行的。
我们都清楚Activity中onCreate()方法在setContentView()后,View的宽高是获取不到的。同时我们知道Activity在onResume()后才完全可见,并且初次在onResume()方法中也是拿不到View的尺寸的,这样可以推算得出:View的绘制流程是在onResume()方法执行结束后才开始的。那Activity的生命周期方法背后是由谁,又何时调用的?
答:ActivityManagerService
ActivityManagerService(以下简称AMS))是Androids上层系统中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用程序的管理和调度等工作。具体详细内容参考以下地址:
https://blog.csdn.net/gaugame...
相对而言ActivityThread的main方法是应用程序的入口,main()方法里做一些初始化工作,其中包括和AMS建立起通信。
public class ActivityThread{
final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
final H mH = new H();
final ArrayMap mActivities = new ArrayMap<>();
public static void main(String[] args) {
//初始化lopper
Looper.prepareMainLooper();
//初始化ActivityThread
ActivityThread thread = new ActivityThread();
//ApplicationThread和AMS建立联系
thread.attach(false);
//取消息
Looper.loop();
//loop()方法如果执行结束,未能取到消息,程序抛出异常退出。
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
ActivityThread会管理和用户打交道的Activity,应用所有的Activity都存在ActivityThread中的mActivities集合中,而ActivityThread响应AMS的号召,需要借助ApplicationThread来接受这个诏令,点进去看全都是生命周期方法。接着调用attach()方法让ApplicationThread和AMS建立联系。H类就是一个Handler类,用于发送生命周期改变的消息,通知响应操作。
private class ApplicationThread extends IApplicationThread.Stub {
//通知相应的进程执行启动Activity的操作
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List pendingResults, List pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
sendMessage(H.LAUNCH_ACTIVITY, r);
}
public final void scheduleResumeActivity(IBinder token, int processState,
boolean isForward, Bundle resumeArgs) {
sendMessage(H.RESUME_ACTIVITY, token, isForward ? 1 : 0, 0, seq);
}
//.....
}
对于AMS我也不太懂在这儿提一下明白是怎么回事就行,以后再慢慢研究。当Activity启动时会先调用到scheduleLaunchActivity()方法,由Handler发送通知消息后执行handleLaunchActivity()->performLaunchActivity()->callActivityOnCreate()->Activity.onCreate()。
private class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
//该方法中会执行Activity的onCreate()方法。
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
//onResume();
case RESUME_ACTIVITY:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
SomeArgs args = (SomeArgs) msg.obj;
handleResumeActivity((IBinder) args.arg1, true, args.argi1 != 0, true,
args.argi3, "RESUME_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
}
}
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
//.............
//执行onResume()方法
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
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;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//将DecorView添加到Window上
wm.addView(decor, l);
}
}
//说法二:执行makeVisible()来添加View,但也是添加到Window里和上面一样的操作。清楚的小伙伴可以告诉我下。
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
}
}
onResume()时也一样,当Activity的状态发生改变,经过层层调用执行到handleResumeActivity()方法,在方法中先调用Activity.onResume()方法,再执行WindowManager的addView()方法将Activity的根View(DecorView)添加上去,进而开始绘制流程。这就解释了为什么初次在onResume()方法中获取不到View的宽高。对DecorView不太明白的可以参考Activity中setContentView浅析。地址如下所示:
https://blog.csdn.net/sinat_3...
而WindowManager实现类为WindowManagerImpl,WindowManagerImpl中addView()方法又会调用WindowManagerGlobal的addView()方法。参考如下:
https://www.jianshu.com/p/c22...
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);
//即将开始流程绘制
root.setView(view, wparams, panelParentView);
·······
}
addView()方法中先创建ViewRootImpl对象,随后执行setView()方法将其和DecorView绑定起来,绘制流程也将由ViewRootImpl()来执行。setView()方法中会执行requestLayout()方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
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();
}
}
requestLayout()方法走下去会异步执行performTraversals()方法,View的三大流程都是在该方法中执行的。到这儿我们算是明白View的绘制流程是从哪儿开始的,接下来分析这个过程到底是怎么做的。
private void performTraversals() {
//计算DecorView根View的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
measure流程
说到measure流程就不得提到一个类,MeausreSpec。使用该类用一个int值就能记录View测量的宽高和宽高的测量模式,大大节约开销。
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
//int类型占4个字节,1个字节=8bit(位)。
private static final int MODE_MASK = 0x3 << MODE_SHIFT; //11000000000000000000000000000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT; //00000000000000000000000000000000 听说用于系统内部,想要多大就给多大。平时也没有用到过,下面不做分析。
public static final int EXACTLY = 1 << MODE_SHIFT; //01000000000000000000000000000000 精确值模式,对应LayoutParams的match_parent或者固定尺寸
public static final int AT_MOST = 2 << MODE_SHIFT; //10000000000000000000000000000000 最大值模式,对应LayoutParams的wrap_content
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
用一句话解释MeasureSepc:
用位运算的方式来”压缩”记录View的测量宽高和测量模式,其中高(前)两位代表测量模式后三十位代表测量后的尺寸。同时提供”解压”的方法转为我们需要的实际数值。
MeasureSpec = MeasureMode+MeasureSize
我们以int mMeausreWidth = makeMeasureSepc(720,MeasureSpec.EXACTLY)为例:
getMode亦是如此
//生成DecorView根View的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
measure流程开始执行之前,会先计算出DecorView的MeasureSpec。此处mWidth和mHeight就为屏幕的宽高,LayoutParmas都为match_parent。
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
计算出DecorView的MeasureSpec后,执行DecorView的measure()方法开始整个View树的测量。
private void performMeasure()(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
measure()方法是被final修饰了的,派生类都不能重写,所有View都会执行到View类的measure()方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
onMeasure()方法意在二种:相对于ViewGroup来说
- 测量出子View的MeasureSpec后,再执行子View的measure流程
- 给自己mMeasureWidth&Height赋值。
View的onMeasure()方法就只干第二件事。我们以下xml布局为例,当我们调用setContentView(R.layout.activity_main)后:
此时此处DecorView有实现onMeausre方法并且会执行父类FrameLayout的onMeausre()方法。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
//core
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
//设置的前景
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
//设置的background
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
//给自己的mMeasuredWidth和mMeasuredHeight赋值
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <
onMeasure()方法中遍历所有子View,通过执行measureChildWithMargins()方法,先计算出子View的MeasureSpec再调用子View的measure()方法传递执行measure流程。
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
//开始LinearLayout的measure流程
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}a
父View在帮助计算子View的MeasureSpec时有着固定的套路:
- 受父View的MeasureSpec影响
- 受子View自身的LayoutParams影响
- 计算父View剩下可用的区域,减去父View的padding和子View的margin距离和父View已经使用(预定)的区域大小。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父View的宽/高测量模式
int specMode = MeasureSpec.getMode(spec);
//父View的宽/高大小
int specSize = MeasureSpec.getSize(spec);
//父View剩下的可用区域
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
//父View_EXACTLY
case MeasureSpec.EXACTLY:
//如果子View写si了宽/高
if (childDimension >= 0) {
//子View的MeasureSpec=EXACTLY+写si的宽/高(si说多了不吉利)
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的MeasureSpec=EXACTLY+父View剩下的区域
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父View_AT_MOST
case MeasureSpec.AT_MOST:
//如果子View写死了宽高
if (childDimension >= 0) {
//子View的MeasureSpec=EXACTLY+写si的宽/高
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子View的MeasureSpec=AT_MOST+父View剩下的区域
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父View_UNSPECIFIED从来没有用到,不做分析
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
getChildMeasureSpec()生产子View的MeasureSpec总结如下:
- 子View写si宽高:测量模式不受父View影响,全都为EXACTLY,宽高为写si的宽高
- 子View没有写si宽高:如果父View都为AT_MOST,子View想都别想还是为AT_MOST,如果父View为EXACTLY且子View的LayoutParams为match_parent,才为EXACTLY。宽高都为父View剩下的区域。这就很好的明白了为什么我们自定义View时,如果没对View的宽高进行处理,View即使是wrap_content也会撑满整个屏幕了。
如果我们写si的尺寸超过了有效范围,比如超出了屏幕或者超过了父View的大小,最终的measureWidth/Height和实际宽高还是写死的尺寸,只不过超出的区域看不见而已。
ViewGroup在所有子View的measure流程都执行结束后,再调用setMeasuredDimension()方法给自己的mMeasureWidth/Height赋值。其实View在执行onMeausre()方法之前,已经由父View(DecorView除外)计算出了一个有效的MeasureSpec,比如在执行performMeasure()方法之前就先一步计算出了DecorView的MeasureSpec,接着在measureChildWithMargins()方法中又先计算出LinearLayout的MeasureSpec,再执行LinearLayout的measure()流程。并且View最终的大小都不会超过这个范围,即使出现以下情况都是如此:
- 在720-1280屏幕下,给View设置了一张1500-1500的图片
- 子View的大小已经超过了自己
View最终的mMeasureWidth/Height,是由自身的测量模式,前/背景和子View的大小共同决定的。
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState <
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("TAG","LinearLayoutWidth="+MeasureSpec.getSize(widthMeasureSpec));
Log.i("TAG","LinearLayoutHeight="+MeasureSpec.getSize(heightMeasureSpec));
Log.i("TAG","MeasureWidth="+getMeasuredWidth());
}
当LinearLayout的LayoutParams时match_parent时好说,LinearLayout的MeasureMode为EXACTLY,size就是父View帮其计算出的MeasureSize。如果LinearLayout的LayoutParams为warp_content,在执行resolveSizeAndState()方法时会走到case MeasureSpec.AT_MOST:里面去。View最终的宽高会从自身的前/背景大小和子View的大小中选则一个最大值。在FrameLayout中会选出最大的子View的measureWidth/Height,因为FrameLayout的子View都是重叠放在左上角的,所以选出最大的那一个就行了。而LinearLayout会累计所有子View的大小。当然如果这个最大值超过了父View为其测量的MeasureSize,最终View的大小还是为父View为其测量的MeasureSize。specSize | MEASURED_STATE_TOO_SMALL;仅仅只是为了标记一个这个View的测量状态,在getMeasureWidth/Height()时值还是不变的。
ViewGroup的onMeausre()方法明白之后,再看View的就简单多了,给View的mMeasureWidth和Height赋值就行了。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
如果我们有给View设置background,getSuggestedMinimumWidth()会获取该大小,但是getDefaultSize()方法还是会选择父View帮助测量的MeasureSize。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
layout流程
相对于measure流程而言,layout和draw流程就简单得多了,通过Layout流程来确定子View在父View中的位置。子View在父View中的位置,需要4个点来确定,同时也可以通过点的距离来计算出View的大小。
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
performLayout方法中会执行DecorView的layout()方法来开始整个View树的layout流程。而DecorView包括其他的ViewGroup都没有另外实现layout()方法,都会执行到View的layout()方法。layout()方法中会先执行setFrme()方法确定View自己在父View中的位置,接着再执行onLayout()方法来遍历所有的子View,计算出子View在自己心中的位置(4个点)后,再执行子View的layout流程。不同的ViewGroup有着不同的方式来安排子View在自己心中的位置。所以View类中的onLayout()是一个空方法,等着View们自己去实现。自定义ViewGroup的时候如果不在onLayout方法中安排子View的位置,将看不见子View。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
}
}
laout流程,相对于ViewGroup而言:
- 确定自己在父View中的位置
- 遍历所有子View,计算出在自己心中的位置(4个点)后,再执行子View的layout流程
相对于View(单个View)而言只干第一件事。
draw流程
performDraw()方法中会执行通过层层调用会执行到View的draw()方法。
private void performDraw() {
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded) {
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
mView.draw(canvas);
}
public void draw(Canvas canvas) {
//绘制自己的背景
drawBackground(canvas);
//空实现,绘制自己的内容,自定义时重写该方法
onDraw(canvas)
//绘制子View
dispatchDraw(canvas);
//绘制前景
onDrawForeground(canvas);
}
draw()方法会绘制一些自己的东西。通过dispatchDraw()方法来传递执行子View的draw流程。ViewGroup类中已经实现:
protected void dispatchDraw(Canvas canvas) {
more |= drawChild(canvas, child, drawingTime);
}
总结
View的绘制流程到此结束,不足支持多多包涵,指出共同探讨。
本文转自 【郭霖】 如有侵权,请联系删除。