7 Activity 窗口显示流程介绍
Activity 窗口显示,其实就是Décor View绘制并显示的过程,但是在绘制之前,Décor View需要先做下面两件事情:
1) 确定Décor View的大小
2) 对Décor View进行布局操作,也就是确定DécorView所有child views的显示位置
由于Décor View的LayoutParams中宽高默认设置的是MatchParent,所以Décor View初始状态下,肯定是想要塞满整个屏幕的,也就是宽高都是屏幕的尺寸,但是想归想,实际上真正能拿到多大的区域,这个还是要看WMS这个大总管怎么分配了
简单点说就是,Décor View可以告知WMS窗口想要显示的大小,但是WMS会根据状态栏,输入法窗口是否显示等等,来对这个大小做适当的裁剪,然后将最终结果告知到Décor View,
我们可以称这个过程叫Décor View显示大小确认,这个是通过调用WindowSession. Relayout来完成的
在5.1 Activity Base Window介绍中已经介绍过,上面窗口显示的这些操作,都是由
ViewRootImpl. performTraversals来触发完成的,接下去通过代码做简单介绍
7.1 Activity窗口大小获取
ViewRootImpl.performTraverals函数完整的代码太长了,这里就不全部贴出,接下去分析时会贴出局部代码片段来介绍
首先看ViewRootImpl定义的三个变量:
final Rect mWinFrame; // frame given by window manager. int mWidth; int mHeight; |
mWinFrame,mWidth和mHeight保存的,都是WMS返回的窗口大小,如果是第一次运行(mFirst=True), 这三个值默认都为空,如果不是第一次运行,则为当前窗口的显示大小
接下去设置Activity Window的desire rectangle:
Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true;
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. …… } else { DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } …… } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } } |
如果mFirst为true,将desiredWindowWidth和desiredWindowHeight设置为屏幕宽高,否则就将其设置为mWinFrame中保存的当前窗口的大小值
接着基于desiredWindowWidth和desiredWindowHeight对Décor View做一次measure,确认Décor View的大小:
boolean layoutRequested = mLayoutRequested && !mStopped; if (layoutRequested) { final Resources res = mView.getContext().getResources(); …… // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); } |
Décor View的大小确认后,这个只不过是Activity希望请求到的窗口大小,接着还需要调用relayoutWindow-> mWindowSession.relayout并传入请求的大小,然后WMS会根据当前窗口情况返回最终分配的窗口大小,比如,现在有一个输入法窗口已经弹出,并且Activity设置的windowSoftInputMode为adjustResize,在这种情况下,Activity期望的高度是肯定无法满足的,WMS会返回减去输入法窗口的高度,并将结果返回到Activity
接着看mWindowSession.relayout的调用:
int relayoutResult = mWindowSession.relayout( mWindow, mSeq, params, (int) (mView.getMeasuredWidth() * appScale + 0.5f), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingConfiguration, mSurface); |
mWindow是对应的窗口,第三和第四个参数就是窗口期望的宽和高,这里加上0.5f用以实现四舍五入,WMS那边调整后的宽高数据会返回到mWinFrame中
mWinFrame保存的就是这次Activity窗口显示的最终大小,接着保存数据到mWidth和
mHeight:
if (mWidth != frame.width() || mHeight != frame.height()) { mWidth = frame.width(); mHeight = frame.height(); } |
接着重新设置ViewRootImpl关联的Surface的frame size,以便调整graphic buffer size:
if (mSurfaceHolder != null) { // The app owns the surface; tell it about what is going on. if (mSurface.isValid()) { // XXX .copyFrom() doesn't work! //mSurfaceHolder.mSurface.copyFrom(mSurface); mSurfaceHolder.mSurface = mSurface; } mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight); } |
最后,如果WMS返回的最终窗口大小和之前通过desire Width和desire height测量出来的décor view的mesure width和height不一致,那décor view需要基于最终的窗口大小再次做测量操作:
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); …… // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); …… } |
Activity窗口大小的获取介绍结束,接下去介绍Décor View是怎么测量自身以及childviews.
7.2 Activity Décorview大小测量(measure)
上头调用performMeasure触发measure操作,对每个View的宽高来说,必须设置 Measure Mode,主要有三个mode:
Mode name | Desc |
MeasureSpec.AT_MOST | 对应WRAP_CONTENT |
MeasureSpec.EXACTLY | 对应MATCH_PARENT |
MeasureSpec .UNSPECIFIED | 未定义 |
由于layout默认必须要配置View的宽高属性,所以MeasureSpec .UNSPECIFIED基本是用不到的
如果要调用view.measure对某个view执行measure操作,必须要传入宽高以及对应的measure mode值,android将宽/高的值以及measure mode合并存储到int里头,具体的数据分布是这样的:
Int的0-30位保存的是宽高的值,30-32位保存measure mode
对此,Android专门定义了MeasureSpec辅助类用于将measuremode和value合并成int,或者从int里头取出value或者measure mode:
Method name | Desc |
MeasureSpec. makeMeasureSpec | 基于value和measure mode合成measure spec |
MeasureSpec.getMode | 从measure spec取出高2位的值,也就是mode的值 |
MeasureSpec.getSize | 从measure spec中取出低30位的值,也是就宽/高的值 |
先来看下ViewRootImpl中getRootMeasureSpec的实现:
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; } |
从函数中可以看出,MATCH_PARENT对应MeasureSpec.EXACTLY,WRAP_CONTENT则对应
MeasureSpec.AT_MOST,并且,不管是MeasureSpec.EXACTLY还是MeasureSpec.AT_MOST,其对应的value都是the max size of parentview.
接下去看performMeasure:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } |
直接调用décor view的measure函数,接下去看viewmeasure的完整流程:
由于DecorView是一个FrameLayout,所以接下去专门分析下FrameLayout的measure代码,它先调用DecorView.measure,由于DecorView没有实现这个函数,所以最终调用View.measure:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ……
// Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchingSize = isExactly && getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); if (forceLayout || !matchingSize && (widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec)) {
// first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } …… mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }
mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension } |
mPrivateFlags变量基于位运算保存各种标签值,比如PFLAG_MEASURED_DIMENSION_SET,用于标识view是否已经measure;mOldWidthMeasureSpec和mOldHeightMeasureSpec保存上一次请求完成的measure spec;forceLayout标明这次measure是否是由View.requestLayout触发的,如果为true,则强制执行onMeasure
如果measure行为不是由View.requestLayout触发的,也就是forceLayout为false,那只有如下两种情况,会触发View调用onMeasure做重新测量:
1) Measure mode不全是MeasureSpec.EXACTLY,也就是说,宽和高有一个或者全部被设置成WRAP_CONTENT,这种情况下,isExactly就会为false,还有就是widthMeasureSpec !=mOldWidthMeasureSpec ||
heightMeasureSpec!= mOldHeightMeasureSpec 为true,最后就是mMeasureCache不存在缓存数据
2) Measure mode全是MeasureSpec.EXACTLY,也就是时候,宽和高都被设置成了
MATCH_PARENT,然后measuare spec里设置的宽高跟getMeasuredWidth()和
getMeasuredHeight()的不一致,这个时候matchingSize肯定为false,
widthMeasureSpec!= mOldWidthMeasureSpec和
heightMeasureSpec!= mOldHeightMeasureSpec肯定也为true,最后同样的,mMeasureCache不存在缓存数据
总结下,如果反过来说呢,如果measure被重复调用,什么情况下onMeasure不会被重复执行:
1) 不是通过requestLayout触发的
2) 如果widht和height mode都是MATCH_PARENT,并且请求高度和宽度无变化,那这次请求就被会忽略
3) 如果width或者height存在一个mode为WRAP_CONTENT,那就要看请求measure spec是否是一致的,如果一致,那这次请求会被忽略
上面介绍的是onMeasure的入口控制,避免不必要的measure被重复执行进而影响系统运行效率,接下去介绍onMeasure的内部实现
我们都知道整个View是一个树形结构,这个结构的顶端是Décor View,每一个树节点只有两种类型,一个是leaf view,还有就是ViewGroup,leaf view是末端节点,由于其不包含childview,其内部只处理自身逻辑就好了,相对来说会比较简单;ViewGroup则不一样,因为它是View的群组,那它肯定就包含child view,child view可能是leaf view或者View Group,相对来说就会复杂点。
所以,如果要从Décor View遍历整个树形数据,最重要就是ViewGroup的处理,因为它起到承上启下的作用。
Android定义的ViewGroup有很多,比如RelativeLayout,LinearLayout,FrameLayout,每个ViewGroup的最大的不同之处,表现在对childview的measure,layout的处理
由于Décor View是一个FrameLayout,所以接下去重点介绍FrameLayout.onMeasure实现,其余几个ViewGroup大家可自行研究
由于FrameLayout所有的child view的位置都是相对于FrameLayout的左上角坐标原点来放置的,如果FrameLayout定义的宽高为MATCH_PARENT,那所有child view所能达到的最大宽高就是FrameLayout的宽高,反之,如果FrameLayout的宽高定义为WRAP_CONTENT,那FrameLayout的宽高则会被设置成所有child view中size最大的那个。
接下去看代码:
//FrameLayout.java protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount();
final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear();
int maxHeight = 0; int maxWidth = 0; int childState = 0;
for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { 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); } } } }
// Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); }
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));
count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); int childWidthMeasureSpec; int childHeightMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) { childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeftWithForeground() - getPaddingRightWithForeground() - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); } else { childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, getPaddingLeftWithForeground() + getPaddingRightWithForeground() + lp.leftMargin + lp.rightMargin, lp.width); }
if (lp.height == LayoutParams.MATCH_PARENT) { childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTopWithForeground() - getPaddingBottomWithForeground() - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); } else { childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, getPaddingTopWithForeground() + getPaddingBottomWithForeground() +lp.topMargin + lp.bottomMargin, lp.height); }
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } } } |
measureMatchParentChildren如果为true,说明FrameLayout存在宽/高定义为
WRAP_CONTENT,对于同样是WRAP_CONTENT的child view没问题,但是对于那些
MATCH_PARENT的child view,则需要在FrameLayout的宽/高确定后,需要再一次执行measure操作
函数先循环遍历all child views,然后调用measureChildWithMargins测量child view的宽高,然后通过view的measure width加上对应的margin size得到其在FrameLayout中需要占用的实际大小,然后将allchild views中,宽度和高度最大的保存到maxWidth和maxHeight中
如果measureMatchParentChildren为true,还需要将宽高为MATCH_PARENT的child view保存到mMatchParentChildren中
接着将得到的maxWidth和maxHeight再加上FrameLayout的padding数值,然后再通过
getForeGround拿到fore ground drawable,如果drawable的最小宽高比maxWidth和maxHeight要大,则将其作为maxWidth和maxHeight的大小
maxWidth和maxHeight到目前为止,都是基于childview的数据加上frameLayout的padding和foreground数据算出的,这个可以做为frameLayout的measure result?当然不行,因为child view给出的数据,有可能会超过framelayout对应measure spec的值,所以还需要调用
resolveSizeAndState做调整:
//View.java public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { 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: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState&MEASURED_STATE_MASK); } |
这个函数很简单,如果FrameLayout设置的是AT_MOST,即WRAP_CONTENT,如果size比specSize小,那就用size,反之则用specSize;如果FrameLayout设置的是EXACTLY,也就是MATCH_PARENT,则直接设置成specSize
也就是说maxWidth和maxHeight的值,只在宽高为WRAP_CONTENT并且maxWidth和
maxHeight值比对应measure spec的value值小时才有意义
接着调用setMeasuredDimension设置resolveSizeAndState返回的宽高结果
到这里,FrameLayout的measure width/height确定后,对宽高为MATCH_PARENT的child view再一次执行measure操作,由于FrameLayout的measure width/height已定,这里再得到child view可占用的最大宽高就很简单,直接减去FrameLayout的padding和child view的margin值就可以了。
接下去回过头来分析measureChildWithMargins的实现,由于FrameLayout没有实现该函数,所以调用ViewGroup中的实现:
//ViewGroup.java 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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } |
通过调用getChildMeasureSpec得到child view的measure spec,这个函数传入是第一个参数是parent measure spec,第二个参数是padding,对应FrameLayout的padding值加上child view的margin值再加上已经使用的宽/高度值,由于FrameLayout所有child view都是基于左上角坐标的,所以这里widhtUsed和heightUsed都是0,最后一个参数是layout中设置的widht和height值
接着看getChildMeasureSpec的实现:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0; int resultMode = 0;
switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
// Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
// Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); } |
这个函数先拿到FrameLayout的measure spec,然后再将measurespec的value减去padding保存到size中,其即为child view的desire size
这个函数其实就处理两种情况
1) 如果FrameLayout的measure spec为EXACTLY,接着判断child view的width/height值;如果值大于0,说明其是一个确定的值,直接将其值保存到resultSize并且将resultMode保存为EXACTLY就可以了;如果值为MATCH_PARENT或者WRAP_CONTENT,则将result设置为size然后对应的值保存到resultMode就好了
2) 如果FrameLayout的measure spec为AT_MOST,对于child view的width/height值大于0或者WRAP_CONTENT,处理跟上面都是一样的;唯一不同的是,当值为MATCH_PARENT时,由于FrameLayout这个parent view在目前measure width/height都未确定,作为child view,你这里MATCH_PARENT就没任何意义,所以这里会把result mode改成AT_MOST
函数中调用child.measure执行child view的measure操作,也就是本节介绍的内容,如此反复,直到所有child view都measure完毕为止