前言
前面说到添加窗口的流程,相关的数据结构,在z轴上的顺序等等,但是仅仅有这些信息还是不够的,窗口在屏幕上的x,y轴如何计算呢?接着前面说到,在ViewRootImpl#setView中,会调用requestLayout,进行布局相关的操作;本章开始梳理这块的逻辑
ViewRootImpl#requestLayout
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
ViewRootImpl#scheduleTraversal
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
7205 final class TraversalRunnable implements Runnable {
7206 @Override
7207 public void run() {
7208 doTraversal();
7209 }
7210 }
7211 final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
1459 void doTraversal() {
1460 if (mTraversalScheduled) {
1461 mTraversalScheduled = false;
1462 mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
1463
1464 if (mProfile) {
1465 Debug.startMethodTracing("ViewAncestor");
1466 }
1467
1468 performTraversals();
1469
1470 if (mProfile) {
1471 Debug.stopMethodTracing();
1472 mProfile = false;
1473 }
1474 }
1475 }
View.requestLayout的本质也是通知ViewRootImp请求一次scheduleTraversals视图树遍历;最终触发ViewRootImpl#requestLayout方法;触发performTraversals(WMS的窗口属性变化,来自控件树的尺寸变化、重绘请求等都引发performTraversals的调用);performTraversals()作为三大流程的起点,创建、参数改变、界面刷新等时都有可能会需要从根部开始measure、layout、draw,就会调用到它;从调试结果看,是先在ViewRootImpl中通过Session调用WMS的addView,然后才会调用到前面的requestLayout请求触发的performTraversals流程
ViewRootImpl#performTraversals
private void performTraversals() {
performTraversals - 1、计算窗口期望尺寸
// cache mView since it is used so much below...
final View host = mView;
// 这个 host 为 Window 下的第一个 View, 它的宽高一般就是制定的 Window 的宽高
// ViewRootImpl, 其实就是用于处理 Window 下最直接的 View, 用 ViewRootImpl 来命名就显而易见了
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (host == null || !mAdded) //判断View绘制的条件是否具备.mAdded是DecorView是否成功添加到
return; //Window的标示位,如果一切顺利的话,这里应该为ture,因此不会return.
mIsInTraversal = true; //是否正在遍历
mWillDrawSoon = true; //是否马上绘制View
boolean windowSizeMayChange = false; //视图的大小可能改变
boolean newSurface = false; //新界面
boolean surfaceChanged = false; //界面改变
WindowManager.LayoutParams lp = mWindowAttributes; //lp中包含window的相关属性,如左上角定点,长宽等等;会在adb shell dumpsys window w 中的mAttrs中打印出来
int desiredWindowWidth; //顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowHeight;
//以PopupWindow menu窗口为例 ,host = PopupWindow$PopupDecorView@7613
final int viewVisibility = getHostVisibility(); //DecorView视图是否可见
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
// Also check for possible double visibility update, which will make current
// viewVisibility value equal to mViewVisibility and we may miss it.
|| mAppVisibilityChanged); //视图可见性改变
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
WindowManager.LayoutParams params = null;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
params = lp;
mFullRedrawNeeded = true;
mLayoutRequested = true;
if (mLastInCompatMode) {
params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = false;
} else {
params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
mLastInCompatMode = true;
}
}
mWindowAttributesChangesFlag = 0;
Rect frame = mWinFrame; //将一个全局的Rect对象赋给了局部frame对象,它用来保存Activity窗口当前的宽度和高度(第一次其值为Rect(0,96-1080,2210),屏幕大小去掉状态栏和导航栏)
if (mFirst) { // mFirst在构造器中被赋值true,表示第一次traversals 在后面的代码中被赋值false
mFullRedrawNeeded = true; //是否需要全部重绘
mLayoutRequested = true; //是否要求重新Layout界面
final Configuration config = mContext.getResources().getConfiguration();
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
// 初始化期望窗口长宽,如果Activity含有状态栏,长宽就去掉状态栏的长宽
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// 如果不含状态栏,宽高就是整个屏幕的宽高
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
/**
* 因为第一次遍历,View树第一次显示到窗口
* 然后对mAttachinfo进行一些赋值
* AttachInfo是View类中的静态内部类AttachInfo类的对象
* 它主要储存一组当View attach到它的父Window的时候视图信息
*/
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true; //使用32位绘图缓存
mAttachInfo.mHasWindowFocus = false;//视图窗口当前不具有焦点
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false; //ViewAncestor应在下次执行遍历时触发全局布局更改
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
// 如果之前未设置布局方向,则设置布局方向(View.LAYOUT_DIRECTION_INHERIT 是默认值)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {
//除了第一次被请求执行测量、布局和绘制操作以外,它的当前宽度desiredWindowWidth和高度
//就等于保存在ViewRootImpl类的成员变量mWinFrame/frame中的宽度和高度值,也就是上一次储存的宽高值.
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
// mWidth和mHeight是上一次traversals时赋frame的值的。
// 如果现在的值不一样了,那么就需要重新draw和layout
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true; //需要重新绘制标志位
mLayoutRequested = true; //要求重新Layout标志位
windowSizeMayChange = true;//Window的尺寸可能改变
}
}
// mWidth和mHeight也是用来描述Activity窗口当前宽度和高度的,它们的值是由应用程序进程上一次主动请求WindowManagerService计算得到的,并且会一直保持不变到应用程序// // 进程下一次再请求WindowManagerService重新计算为止(这个在下文的代码中也有所体现)。
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (viewVisibility == View.GONE) {
// After making a window gone, we will count it as being
// shown for the first time the next time it gets focus.
mHasHadWindowFocus = false;
}
}
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
/**
* 执行HandlerActionQueue中HandlerAction数组保存的Runnable
* 我们平时会通过View.post()或View.postDelayed()方法将一个Runnable对象发送到主线程执行
* 其实就是通过这个mHandler去执行
* public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
*/
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
//目前看起来 desiredWindowWidth 是客户端想要的值,客户端根据上一次窗口大小,窗口属性和屏幕大小自己计算
//mWidth是上一次通过WMS计算出的窗口值
//host.getMeasuredWidth() 客户端测量出来view的大小的结果
//getMeasuredWidth()获取的是view原始的大小,也就是这个view在XML文件中配置或者是代码中设置的大小。getWidth()获取的是这个view最终显示的大小,这个大小有可能等于原始的大小也有可能不等于原始大小
if (layoutRequested) { //进行预测量
final Resources res = mView.getContext().getResources();
if (mFirst) { // 如果是第一次
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode; // 视图窗口当前是否处于触摸模式
ensureTouchModeLocally(mAddedTouchMode); // 确保window的触摸模式已经打开
} else { //如果不是第一次traversals
//比较mPending..Insets和上次存在mAttachInfo中的是否改变;insets是屏幕中显示东西区域周围的一圈,类似于状态栏
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar) {
insetsChanged = true;
}
//如果窗口宽高有设置为wrap_content
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
//设置windowSizeMayChange为true,代表窗口的宽高有可能改变;这个值是决定后面windowShouldResize的值的因素之一;如果为false说明不需要resize。
//重新计算窗口期望宽高。
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// Ask host how big it wants to be
//调用measureHierarchy()去测量窗口宽高,赋值窗口尺寸是否改变 (第一次traversals时mWidth = mHeight = -1,所以肯定不等于host.getMeasuredWidth() 这个值为attrs里面的宽度)
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight); //还是需要看一下measure
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
// If we are in auto resize mode, then we need to determine
// what mode to use now.
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i<N; i++) {
if (mAttachInfo.mScrollContainers.get(i).isShown()) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
}
if (resizeMode == 0) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
}
if ((lp.softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) != resizeMode) {
lp.softInputMode = (lp.softInputMode &
~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) |
resizeMode;
params = lp;
}
}
}
if (params != null) {
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
if (!PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
}
mAttachInfo.mOverscanRequested = (params.flags
& WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) != 0;
}
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
}
}
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false; //暂时清空mLayoutRequested,方便后面用来做判断
}
/* 认为窗口大小发生变化的条件
检查是否需要处理Activity窗口的大小变化事件,如果同时满足下面三个条件,就需要处理,将windowShouldResize标志位变为true:
①layoutRequested等于true,说明程序已经发起(正在执行)了一次测量,布局,绘制流程。
②windowSizeMayChange等于true,说明前面已经检测到了Activity窗口的变化。
③上一段代码的最后一句中,我们说过已经做了一次measure()工作,如果测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight和Activity窗口的当前宽度mWidth和高度mHeight一样,那么即使条件1和条件2能满足,那么也是可以认为是Activity窗口的大小是没有发生变化的。换句话说,只有当测量出来的大小和当前大小不一致时,才认为Activity窗口大小发生了变化。另一方面,如果测量出来的大小和当前大小一致,但是Activity窗口的大小被要求设置成WRAP_CONTENT,即设置成和屏幕的宽度desiredWindowWidth和高度desiredWindowHeight一致,但是WindowManagerService服务请求Activity窗口设置的宽度frame.width()和高度frame.height()与它们不一致,而且与Activity窗口上一次请求WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致,那么也是认为Activity窗口大小发生了变化的。
*/
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
//第一次创建时mWidth = -1,windowShouldResize为true
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity was just relaunched, it might have unfrozen the task bounds (while
// relaunching), so we need to force a call into window manager to pick up the latest
// bounds.
windowShouldResize |= mActivityRelaunched; // 如果activity重新启动
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
/*
是检查Activity窗口是否需要指定有额外的内容边衬区域和可见边衬区域。如果有的话,那么变量attachInfo所指向的一个AttachInfo对象的成员变量mTreeObserver所描述的一个TreeObserver对象的成员函数hasComputeInternalInsetsListerner的返回值ComputeInternalInsets就会等于true。Activity窗口指定额外的内容边衬区域和可见边衬区域是为了放置一些额外的东西。
*/
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets; // 设置是否需要计算insets,设置了监听或存在需要重新设置的空insets
boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;
final int surfaceGenerationId = mSurface.getGenerationId();
performTraversals - 2、 确定窗口尺寸,调用WMS计算并保存
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
/* 开始进入三大阶段的第一个阶段了 6个条件只要满足一个就进入
①Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true。
② 前面得到的变量windowShouldResize的值等于true,即Activity窗口的大小的确是发生了变化。
③ 前面得到的变量insetsChanged的值等于true,即Activity窗口的内容区域边衬发生了变化。
④ Activity窗口的可见性发生了变化,即变量viewVisibilityChanged的值等于true。
⑤ Activity窗口的属性发生了变化,即变量params指向了一个WindowManager.LayoutParams对象。
*/
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 第一次traversals 或 窗口尺寸有变化 或 insets有变化 或 窗口visibility有变化
// 或 窗口属性有变化 或 强迫窗口下一次重新layout
mForceNextWindowRelayout = false;
if (isViewVisible) {
// If this window is giving internal insets to the window
// manager, and it is being added or changing its visibility,
// then we want to first give the window manager "fake"
// insets to cause it to effectively ignore the content of
// the window during layout. This avoids it briefly causing
// other windows to resize/move based on the raw frame of the
// window, waiting until we can finish laying out this window
// and get back to the window manager with the ultimately
// computed insets.
// 如果insets发生改变 并且 是第一次traversals或窗口从不可见变为可见
// 就置insetsPending为true
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
}
boolean hwInitialized = false;
boolean contentInsetsChanged = false;
boolean hadSurface = mSurface.isValid(); //判断ViewRootImpl的mSurface属性是否有效
try {
//调用WMS重新计算并保存
if (DEBUG_LAYOUT) {
Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
host.getMeasuredHeight() + ", params=" + params);
}
if (mAttachInfo.mThreadedRenderer != null) {
// relayoutWindow may decide to destroy mSurface. As that decision
// happens in WindowManager service, we need to be defensive here
// and stop using the surface in case it gets destroyed.
if (mAttachInfo.mThreadedRenderer.pauseSurface(mSurface)) {
// Animations were running so we need to push a frame
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
}
// 调用relayoutWindow()重新计算窗口尺寸以及insets大小
// 会使用IPC去请求 WMS
// params: window attr view可见性 是否有额外的insets
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
//调用relayoutWindow()来请求WindowManagerService服务计算Activity窗口的大小以及过扫描区域边衬大小和可见区域边衬大小(计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中,而Activity窗口的内容区域边衬大小和可见区域边衬大小分别保存在ViewRoot类的成员变量mPendingOverscanInsets和mPendingVisibleInsets中;mWinFrame和mPendingOverscanInsets,mPendingVisibleInsets等经过计算,保存WMS计算出的值)
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+ " overscan=" + mPendingOverscanInsets.toShortString()
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " stable=" + mPendingStableInsets.toShortString()
+ " cutout=" + mPendingDisplayCutout.get().toString()
+ " outsets=" + mPendingOutsets.toShortString()
+ " surface=" + mSurface);
// If the pending {@link MergedConfiguration} handed back from
// {@link #relayoutWindow} does not match the one last reported,
// WindowManagerService has reported back a frame from a configuration not yet
// handled by the client. In this case, we need to accept the configuration so we
// do not lay out and draw with the wrong configuration.
if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+ mPendingMergedConfiguration.getMergedConfiguration());
performConfigurationChange(mPendingMergedConfiguration, !mFirst,
INVALID_DISPLAY /* same display */);
updatedConfiguration = true;
}
/**
* mPendingOverscanInsets等rect在relayoutWindow方法里保存了最新窗口大小值
* 再与上一次测量的保存在mAttachInfo中的值进行比较
*/
// 比较这次计算和上次计算的值是否发生了改变
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean cutoutChanged = !mPendingDisplayCutout.equals(
mAttachInfo.mDisplayCutout);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
final boolean surfaceSizeChanged = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
surfaceChanged |= surfaceSizeChanged;
final boolean alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
if (contentInsetsChanged) { //如果改变了,就重新将最新值保存在mAttachInfo中
mAttachInfo.mContentInsets.set(mPendingContentInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Content insets changing to: "
+ mAttachInfo.mContentInsets);
}
if (overscanInsetsChanged) {
mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Overscan insets changing to: "
+ mAttachInfo.mOverscanInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (stableInsetsChanged) {
mAttachInfo.mStableInsets.set(mPendingStableInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Decor insets changing to: "
+ mAttachInfo.mStableInsets);
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (cutoutChanged) {
mAttachInfo.mDisplayCutout.set(mPendingDisplayCutout);
if (DEBUG_LAYOUT) {
Log.v(mTag, "DisplayCutout changing to: " + mAttachInfo.mDisplayCutout);
}
// Need to relayout with content insets.
contentInsetsChanged = true;
}
if (alwaysConsumeNavBarChanged) {
mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
contentInsetsChanged = true;
}
if (contentInsetsChanged || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
|| mLastOverscanRequested != mAttachInfo.mOverscanRequested
|| outsetsChanged) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
mLastOverscanRequested = mAttachInfo.mOverscanRequested;
mAttachInfo.mOutsets.set(mPendingOutsets);
mApplyInsetsRequested = false;
dispatchApplyInsets(host);
}
if (visibleInsetsChanged) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!hadSurface) { //调用过relayoutWindow之后,原来Window没有对应的有效Surface的,但现在有了
if (mSurface.isValid()) {
// If we are creating a new surface, then we need to
// completely redraw it. Also, when we get to the
// point of drawing it we will hold off and schedule
// a new traversal instead. This is so we can tell the
// window manager about all of the windows being displayed
// before actually drawing them, so it can display then
// all at once.
newSurface = true; //设置 newSurface = true
mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
// Only initialize up-front if transparent regions are not
// requested, otherwise defer to see if the entire window
// will be transparent
if (mAttachInfo.mThreadedRenderer != null) {
try {
hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// Don't pre-allocate if transparent regions
// are requested as they may not be needed
mSurface.allocateBuffers(); //nativeAllocateBuffers(mNativeObject);
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
}
} else if (!mSurface.isValid()) {
// If the surface has been removed, then reset the scroll
// positions.
if (mLastScrolledFocus != null) {
mLastScrolledFocus.clear();
}
mScrollY = mCurScrollY = 0;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
if (mScroller != null) {
mScroller.abortAnimation();
}
// Our surface is gone
if (mAttachInfo.mThreadedRenderer != null &&
mAttachInfo.mThreadedRenderer.isEnabled()) {
mAttachInfo.mThreadedRenderer.destroy();
}
} else if ((surfaceGenerationId != mSurface.getGenerationId()
|| surfaceSizeChanged || windowRelayoutWasForced)
&& mSurfaceHolder == null
&& mAttachInfo.mThreadedRenderer != null) {
mFullRedrawNeeded = true;
try {
// Need to do updateSurface (which leads to CanvasContext::setSurface and
// re-create the EGLSurface) if either the Surface changed (as indicated by
// generation id), or WindowManager changed the surface size. The latter is
// because on some chips, changing the consumer side's BufferQueue size may
// not take effect immediately unless we create a new EGLSurface.
// Note that frame size change doesn't always imply surface size change (eg.
// drag resizing uses fullscreen surface), need to check surfaceSizeChanged
// flag from WindowManager.
mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
}
final boolean freeformResizing = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
final boolean dockedResizing = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
final boolean dragResizing = freeformResizing || dockedResizing;
if (mDragResizing != dragResizing) {
if (dragResizing) {
mResizeMode = freeformResizing
? RESIZE_MODE_FREEFORM
: RESIZE_MODE_DOCKED_DIVIDER;
// TODO: Need cutout?
startDragResizing(mPendingBackDropFrame,
mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets,
mPendingStableInsets, mResizeMode);
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
}
}
if (!mUseMTRenderer) {
if (dragResizing) {
mCanvasOffsetX = mWinFrame.left;
mCanvasOffsetY = mWinFrame.top;
} else {
mCanvasOffsetX = mCanvasOffsetY = 0;
}
}
} catch (RemoteException e) {
}
if (DEBUG_ORIENTATION) Log.v(
TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
// frame指向的是mWinFrame, 此时已经是上面重新请求WMS计算后的值了
// 将值保存在mAttachInfo中
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
// 如果前一次计算的值和这次计算的值有变化就重新赋值
if (mWidth != frame.width() || mHeight != frame.height()) {
//记录WMS测量出的宽和高
mWidth = frame.width();
mHeight = frame.height();
}
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);
mSurfaceHolder.mSurfaceLock.unlock();
if (mSurface.isValid()) {
if (!hadSurface) {
mSurfaceHolder.ungetCallbacks();
mIsCreating = true;
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
surfaceChanged = true;
}
if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) {
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, lp.format,
mWidth, mHeight);
}
}
}
mIsCreating = false;
} else if (hadSurface) {
mSurfaceHolder.ungetCallbacks();
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceDestroyed(mSurfaceHolder);
}
}
mSurfaceHolder.mSurfaceLock.lock();
try {
mSurfaceHolder.mSurface = new Surface();
} finally {
mSurfaceHolder.mSurfaceLock.unlock();
}
}
}
final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
if (threadedRenderer != null && threadedRenderer.isEnabled()) {
if (hwInitialized
|| mWidth != threadedRenderer.getWidth()
|| mHeight != threadedRenderer.getHeight()
|| mNeedsRendererSetup) {
threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
mNeedsRendererSetup = false;
}
}
//是否需要重新测量
//如果窗口不处于停滞状态或提交了下一次的绘制
/**
* mStopped true 说明当前窗口的所有者 比如activity处于暂停状态
* mReportNextDraw true 即ViewRootHandler接收到消息MSG_RESIZED_REPORT ,需要绘制
*/
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
// 判断是否需要重新测量窗口尺寸
// 窗口触摸模式发生改变,焦点发生改变
// 或 测量宽高与WMS计算的宽高不相等
// 或 insets改变了
// 或 配置发生改变,mPendingMergedConfiguration有变化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
// 重新计算decorView的MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be
// 执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
//此时的width和height就是真实的宽和高
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
//是否需要重新测量
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
// 检查窗口发生移动的情况
maybeHandleWindowMove(frame);
}
//performTraversals - 3、 调用layout和draw流程
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 开始layout流程
performLayout(lp, mWidth, mHeight);
//处理透明区域
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
if (computesInternalInsets) { //处理insets
// Clear the original insets.
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
insets.reset();
// Compute new insets in place.
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
// Tell the window manager.
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
// Translate insets to screen coordinates if needed.
final Rect contentInsets;
final Rect visibleInsets;
final Region touchableRegion;
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
} else {
contentInsets = insets.contentInsets;
visibleInsets = insets.visibleInsets;
touchableRegion = insets.touchableRegion;
}
try {
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
}
}
if (mFirst) {
if (sAlwaysAssignFocus || !isInTouchMode()) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
}
if (mView != null) {
if (!mView.hasFocus()) {
mView.restoreDefaultFocus();
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: requested focused view=" + mView.findFocus());
}
} else {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: existing focused view=" + mView.findFocus());
}
}
}
} else {
// Some views (like ScrollView) won't hand focus to descendants that aren't within
// their viewport. Before layout, there's a good change these views are size 0
// which means no children can get focus. After layout, this view now has size, but
// is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
// case where the child has a size prior to layout and thus won't trigger
// focusableViewAvailable).
View focused = mView.findFocus();
if (focused instanceof ViewGroup
&& ((ViewGroup) focused).getDescendantFocusability()
== ViewGroup.FOCUS_AFTER_DESCENDANTS) {
focused.restoreDefaultFocus();
}
}
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
if (regainedFocus) {
mLostWindowFocus = false;
} else if (!hasWindowFocus && mHadWindowFocus) {
mLostWindowFocus = true;
}
if (changedVisibility || regainedFocus) {
// Toasts are presented as notifications - don't present them as windows as well
boolean isToast = (mWindowAttributes == null) ? false
: (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
if (!isToast) {
host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
}
//收尾工作,为下一次traversals准备
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mActivityRelaunched = false;
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
if (hasWindowFocus && !isInLocalFocusMode()) {
final boolean imTarget = WindowManager.LayoutParams
.mayUseInputMethod(mWindowAttributes.flags);
if (imTarget != mLastWasImTarget) {
mLastWasImTarget = imTarget;
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null && imTarget) {
imm.onPreWindowFocus(mView, hasWindowFocus);
imm.onPostWindowFocus(mView, mView.findFocus(),
mWindowAttributes.softInputMode,
!mHasHadWindowFocus, mWindowAttributes.flags);
}
}
}
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw();
}
//在第一次traverslas时并不会调用performDraw(),因为创建了新的Surface(newSurface = true)。scheduleTraversals()中会post一个runnable,会再次调用performTraversals(),等到那次调用时才会去执行draw流程。
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// 没有取消draw也没有创建新的平面 (第一次traversals时newSurface为true)
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations(); // 如果还存在等待执行的动画, 就遍历执行它们
}
mPendingTransitions.clear();
}
// 开始draw流程
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals(); //再次traversal
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
目前看起来
1.desiredWindowWidth 是客户端想要的值,客户端根据上一次窗口大小,窗口属性和屏幕大小自己计算
2.mWidth是上一次通过WMS计算出的窗口值
3.host.getMeasuredWidth() 客户端测量出来view的大小的结果
ViewRootImpl#measureHierarchy
通过上面的代码可以看到desiredWindowWidth和desiredWindowHeight是经过一系列代码考核得到的值,讲道理整个View树是可以按照这个值去测量的,但是我们不仅只是测量和布局,还要尽可能舒适的一个UI去展示给用户
比如在大屏幕上,Dialog的width修饰为WRAP_CONTENT,按照上面的代码,其实desiredWindowWidth还是给了尽量大的值,就是屏幕宽度;但是Dialog可能就是为了显示几个字,那结果就是整个dialog的布局就被拉伸铺满屏幕;显然这种UI是不美丽的,那就需要通过measureHierarchy方法去优化,尝试下更小的宽度是否合适。就这样来到了measureHierarchy方法
desiredWindowWidth,desiredWindowHeight是View想要的宽和高
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec; //用于描述宽度的MeasureSpec
int childHeightMeasureSpec; //用于描述高度的MeasureSpec
boolean windowSizeMayChange = false; //表示测量结果是否可能导致窗口的尺寸发生变化
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false; //表示测量是否能满足控件树充分显示内容的要求
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { //其实测量协商仅仅发生在width被指定为WRAP_CONTENT情况
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
// 进入到这里就让窗口的大小不以屏幕大小去布局,而是给一个固定的值看看是否合适
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
//取出一个固定值来用,来自于config_prefDialogWidth
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
//使用getRootMeasureSpec()函数组合SPEC_MODE与SPEC_SIZE为一个MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
/**
* 控件树的测量结果可以通过mView的getmeasuredWidthAndState()方法获取。
* 如果控件树对这个测量结果不满意,则会在返回值中添加MEASURED_STATE_TOO_SMALL位
*/
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
/**
* 走到这里说明上面的尺寸不适合View树
* 需要对宽度再进行放松,使用上面的预期值与最大值的平均值作为新的宽度
*/
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //第二次测量
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true; //再次判断View树对新的结果是否满意
}
}
}
}
if (!goodMeasure) {
/**
* 如果上面两次尝试测量结果,View树都不满意
* 那也没办法了,做不了优化了,只能以屏幕宽高去给View树测量了
* 这是第三次也是最后一次测量了
*/
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); //第二个参数为attrs中的值
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
//此时host.getMeasuredWidth()和host.getMeasuredHeight()已经是自己测量出的结果了;然后再看看是否等于mWidth 和mHeight
//mWidth 和mHeight第一次traversals时为-1,这两个值代表WMS测量出来的结果;此时没有测量,所以为初始值-1
windowSizeMayChange = true; //windowSizeMayChange置为true
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
这里不属于三大流程,只是一个确定显示窗口的大小。
可以看到这个方法进行了两次优化处理,如果两次优化处理还是不满意,就使用给定的desiredWindowWidth/desiredWindowHeight进行测量;这里也表明了一点,performMeasure方法可能会被调用多次,那onMeasure()方法同样会被回调多次,这样我们在自定义View的时候,就不要在这个回调方法里做过多的new内存操作。
注意到方法里会调用到getRootMeasureSpec这么一个方法,说到这个方法就得先谈下MeasureSpec这个类,它是View类的一个静态内部类:
MeasureSpec封装了从父级传递给子级的布局要求
每个MeasureSpec代表宽度或高度的要求
MeasureSpec由大小和模式组成。 有三种可能
1.UNSPECIFIED :父View没有对子View做任何限制,子View可以是自己想要的尺寸;像我们平时在xml中写View的时候没有设置宽高,这种情况比较少见。它的值是0 << 30 = 0
2.EXACTLY:父View决定了子View确切大小,子View将被限定在给定的边界里;像xml中子view如果是填充父窗体(match_parent)或者确定大小,说明父View已经明确知道子控件想要多大的尺寸了。它的值是1 << 30 = 1073741824
3.AT_MOST:子View可以是它所期望的尺寸,但是不能大于specSize;在布局设置wrap_content,父控件并不知道子控件到底需要多大尺寸(具体值), 需要子控件在measure测量之后再让父控件给他一个尽可能大的尺寸以便让内容全部显示;如果在onMeasure没有指定控件大小,默认会填充父窗体,因为在view的measure源码中, AT_MOST(相当于wrap_content )和EXACTLY (相当于match_parent )两种情况返回的测量宽高都是specSize, 而这个specSize正是父控件剩余的宽高,所以默认measure方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间。它的值是2 << 30 = -2147483648
所以这个类是SPEC_SIZE和SPEC_MODE的组合,结构如下
SPEC_MODE(32,31) - SPEC_SIZE(30,…,1)
也就是高两位表示模式(在父View中是如何定义的),低30位表示大小(父View的建议尺寸)
再来看下performMeasure方法,它有两个参数,一个是包含View的宽度度量规范,一个是包含View的高度度量规范,然后根据这两个宽高的度量规范去测量View所需的大小;那这两个信息怎么来呢,就是通过getRootMeasureSpec获取,这个方法会调用MeasureSpec类的makeMeasureSpec方法,根据给定的大小和模式返回一个度量规范
ViewRootImpl#getRootMeasureSpec
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
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: //对于Popup Menu,其窗口是已经确定了大小的情况,但不是MATCH_PARENT
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); //根据确定大小的size和模式,组合成一个measureSpec的int值
break;
}
return measureSpec;
}
ViewRootImpl#performMeasure
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);
}
}