工作一段时间了,但是感觉自己对View的三大流程还不是理解透彻。所以主要根据《Android开发艺术探索》一书和查看源码去了解下View的三大流程。
在《Android开发艺术探索》中说到,ViewRoot是连接WindowManager和DectorView的纽带,View的三大流程都是通过ViewRoot实现的。而ViewRootImpl是ViewRoot的实现。在Activity被创建之后,会将DectorView添加到window上,同时创建ViewRootImpl,并将两者关联起来(通过ViewRootImpl.setView)。
View的绘制流程是从ViewRoot.performTraversals()开始的,并通过调用performMeasure、performLayout、performDraw完成顶级View的三大流程。
performTraversals方法很长,分段阅读毕竟容易理解和消化。首先是很长的一段(哈哈),主要作用就是确定当前窗体大小并进行view树的测量(测量会提出来下一段分析)
// cache mView since it is used so much below...
final View host = mView;
if (host == null || !mAdded)
return;
//一些窗口变量的处理
Rect frame = mWinFrame;
if (mFirst) {//是否第一次请求,构造方法中为true
....
//---Activity当前的Window的宽高的确定---
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
//视图大小发生改变,重绘相关标志位置为true
mFullRedrawNeeded = true;//需要重新绘制标志位
mLayoutRequested = true; //要求重新Layout标志位
windowSizeMayChange = true;//Window的尺寸可能改变
}
}
....
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
...
//--检测边衬区域是否发生变化,有变化则 insetsChanged 变量置为true
//测量Window的可能大小,实际上进行了measure()测量过程,只不过这个测量过程不属于三大流程
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
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;
}
//前面已经做了一次measure()工作,host宽高和当前窗口宽高不一致则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));
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.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
...
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
//1.Activity窗口是第一次执行测量、布局和绘制操作,即mFirst == true
//2.前面windowShouldResize==true,即Activity窗口的大小发生了变化
//3.前面insetsChanged==true,即Activity窗口的内容区域边衬发生了变化
//4.viewVisibilityChanged==true,Activity窗口的可见性发生了变化
//5.变量params指向了一个WindowManager.LayoutParams对象,Activity窗口的属性发生了变化
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
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.
//Activity窗口是否指定了额外的内容区域边衬和可见区域边衬
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
.....
try {
....
//请求WindowManagerService服务计算Activity窗口的大小以及过扫描区域边衬大小和可见区域边衬大小
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
.....
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
....
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
}
....
} catch (RemoteException e) {
}
...
//计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中
//变量frame和mWinFrame引用的是同一个Rect对象
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()) {
mWidth = frame.width();
mHeight = frame.height();
}
....
//-------进行测量过程,下面分析-------
} 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.
//判断window是否有移动,发生移动则执行移动动画
maybeHandleWindowMove(frame);
}
上面这一大段代码,主要作用其实就是为了确定Activity窗口的大小,包括内容窗口大小和相关的边衬区域大小的确定。
- Activity窗口是第一次执行测量、布局和绘制操作,即mFirst == true
- 前面windowShouldResize==true,即Activity窗口的大小发生了变化
- 前面insetsChanged==true,即Activity窗口的内容区域边衬发生了变化
- viewVisibilityChanged==true,Activity窗口的可见性发生了变化
- 变量params != null,指向了一个WindowManager.LayoutParams对象,Activity窗口的属性发生了变化
当上面5中情况中一种情况为true,则activity窗口大小发生变化需要重新测量。并通过WMS请求计算activity窗口大小。然后开始测量流程。
//mStopped==true,该窗口activity处于停止状态
//mReportNextDraw,Window上报下一次绘制
if (!mStopped || mReportNextDraw) {
//触摸模式发生了变化,且检测焦点的控件发生了变化
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//1. 焦点控件发生变化
//2. 窗口宽高测量值 != WMS计算的mWinFrame宽高
//3. contentInsetsChanged==true,边衬区域发生变化
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//------开始执行测量操作--------
// 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
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) {
//----有相关权重,需要重新测量-----
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
- 焦点控件发生变化
- 窗口宽高测量值 != WMS计算的mWinFrame宽高
- contentInsetsChanged==true,边衬区域发生变化
当上述情况之一出现则进行测量流程。测量完成后根据是否有配置权重进行再次测量。
//layout布局要求标志位
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//--------开始执行布局操作---------
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
//计算透明区域
....
}
.....
mFirst = false;
....
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw();
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
....
//-----开始执行绘制操作-------
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();//重新执行performTraversals
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
Invalidate、postInvalidate、requestLayout应用场景?
哪一个流程可以放在子线程中去执行?
参考资料
View绘制流程及源码解析(一)——performTraversals()源码分析
Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析
《Android开发艺术探索》