ViewRootImpl
ViewRoot
(android 2.2之前的老版本)对应于ViewRootImpl(替代ViewRoot)类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot完成的。当Activity对象被创建完毕后,会将DecorView添加到Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联
关联过程
创建Activity实例后会调用Activity.attach()
来初始化一些内容,Window对象在这里创建
Activity # attach()
final void attach(...) {
...
mWindow = new PhoneWindow(this);
mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
...
}
每个Activity会有一个WindowManager对象,这个mWindowManager就是和WindowManagerService(WMS)进行通信,也是WMS识别View具体属于那个Activity的关键。
Activity # makeVisible()
在ActivityThread.handleResumeActivity()中,调用r.activity.makeVisible
void makeVisible() {
if (!mWindowAdded) {
// 创建WindowManagerImpl(实现WindowManager接口)对象
ViewManager wm = getWindowManager();
// 传入DecorView,调用WindowManagerImpl的addView()
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
辨析getWindowManager().addView(mView,wl)和addContentView(mView,wmParams)
在Activity中调用WindowManager.LayoutParams wl = new WindowManager.LayoutParams(); getWindowManager().addView(mView,wl)
- getWindowManager().addView(mView,wl)
会调用到WindowManagerGlobal.addView,这时会创建一个新的ViewRootImpl,和原来的DecoView不在一条View链上 - addContentView(mView,wmParams)
直接将mView添加到DecoView中,会使ViewRootImpl链下的所有View重绘。
WindowManagerImpl # addView()
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 调用WindowManagerGlobal的addView()
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerGlobal # addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ......
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// ......
root = new ViewRootImpl(view.getContext(), display);
// final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params
view.setLayoutParams(wparams);
// 将相关加入WindowManagerGlobal的三个全局集合中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 将DecorView(view)与ViewRootImpl连接起来,以后DecorView的事件都由ViewRootImpl来管理了,比如在DecorView上增删改View
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
ViewRootImpl # setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
// 将DecorView赋值给mView,表示根View
if (mView == null) {
mView = view;
// ......
}
}
}
ViewRootImpl实现ViewParent这个接口(这个接口最常见的一个方法是requestLayout()),在DecoView添加View时,就会将View中的ViewParent设为DecoView所在的ViewRootImpl,View的ViewParent相同时,理解为这些View在一个View链上。所以每当调用View的requestLayout()时,其实是调用到ViewRootImpl,ViewRootImpl会控制整个事件的流程。可以看出一个ViewRootImpl对添加到DecoView的所有View进行事件管理。
注:requestLayout
方法会导致View的onMeasure、onLayout、onDraw方法被调用;invalidate
方法则只会导致View的onDraw方法被调用
关系图
注意:Activty里面持有一个Window,这个Window有一个唯一实现子类PhoneWindow,而PhoneWindow对象里面又持有一个DecorView对象
mView和DecoView是同级关系
performTraversals()
performTraversals()是ViewRootImpl的一个方法,performTraversals()作为三大流程的起点,创建、参数改变、界面刷新等时都有可能会需要从根部开始measure、layout、draw,就会调用到它。
performTraversals()的调用流程
在ViewRootImpl中只有一处调用了它
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
再看doTraversal()在哪调用
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
这个mTraversalRunnable又在哪用到呢
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 往MessageQueue中加入了一个同步屏障(说白了同步屏障是一个特殊的Message)然后由于同步屏障的作用MessageQueue中那些非异步的消息都不进行获取操作了,这么做就是保障刷新的Message能够第一时间得到Looper的调用
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 将runnable发送给handler执行
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
由此可以看出performTraversals()是在一个runnable中被调用的,通过把这个runnable加入队列执行
而scheduleTraversals()方法在requestLayout()方法中调用,而requestLayout()方法在setView()中调用
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
...
mAdded = true;
// 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();
...
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
以上所述,我们可以看到performTraversals()被调用的步骤:
- setView()
- requestLayout()
- scheduleTraversals()
- 创建了一个TraversalRunnable对象
- 在其run方法中调用 doTraversal()
- performTraversals()
performTraversals()的具体代码
可分为三个阶段
1.计算窗口期望尺寸
// 这里的mView在setView中被赋值为DecorView
final View host = mView;
// mAdded指DecorView是否被成功加入到window中,在setView()中被赋值为true
if (host == null || !mAdded)
return;
初始化窗口的期望宽高
以下为API为27的源码
// 用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸
Rect frame = mWinFrame;
// mFirst在构造器中被初始化为true,表示第一次performTraversals()
if (mFirst) {
// 设置需要全部重新draw
mFullRedrawNeeded = true;
// 需要重新layout
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
// 判断要绘制的窗口是否包含状态栏,然后确定要绘制的Decorview的高度和宽度
// 有的话就去掉
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 {
// 宽度和高度为整个屏幕的值
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
...
// 如果不是第一次performTraversals()
} else {
// 它的当前宽度和高度就从之前的保存在ViewRootImpl类的成员变量mWinFrame/frame获取
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
// mWidth和mHeight是由WindowManagerService服务计算出的窗口大小,
// 如果这次测量的窗口大小与这两个值不同,说明WMS单方面改变了窗口的尺寸
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
// 需要进行全部重绘以适应新的窗口尺寸
mFullRedrawNeeded = true;
// 需要重新布局
mLayoutRequested = true;
// window窗口大小改变
windowSizeMayChange = true;
}
}
注意!ViewRoot类的另外两个成员变量mWidth和mHeight也是用来描述Activity窗口当前的宽度和高度的,但是它们的值是由应用程序进程上一次主动请求WindowManagerService服务计算得到的,并且会一直保持不变到应用程序进程下一次再请求WindowManagerService服务来重新计算为止。Activity窗口的当前宽度和高度有时候是被WindowManagerService服务主动请求应用程序进程修改的,修改后的值就会保存在ViewRoot类的成员变量mWinFrame中,它们可能会与ViewRoot类的成员变量mWidth和mHeight的值不同。
再对窗口期望大小赋值并开始测量
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
// 要求重新测量
// 进行预测量
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;
// 确保window的触摸模式已经打开
// 内部 mAttachInfo.mInTouchMode = inTouchMode
ensureTouchModeLocally(mAddedTouchMode);
// private boolean ensureTouchModeLocally(boolean inTouchMode) {
// if (DBG) Log.d("touchmode", "ensureTouchModeLocally(" + inTouchMode + "), current "
// + "touch mode is " + mAttachInfo.mInTouchMode);
// if (mAttachInfo.mInTouchMode == inTouchMode) return false;
// mAttachInfo.mInTouchMode = inTouchMode;
// mAttachInfo.mTreeObserver.dispatchOnTouchModeChanged(inTouchMode);
// return (inTouchMode) ? enterTouchMode() : leaveTouchMode();
// }
// 如果不是第一次
} else {
// mOverscanInsets 记录屏幕中的 overscan 区域
// mContentInsets 记录了屏幕中的控件在布局时必须预留的空间
// mStableInsets 记录了Stable区域,比mContentInsets区域大
// mVisibleInsets 记录了被遮挡的区域
// mPending...Insets是这一次请求traversals还未生效的值
// mAttachInfo中的值是上一次traversals时保存的insets值
// 比较两者看是否有变化,如果有变化就将insetsChanged置为true
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
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;
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);
}
}
}
// 进行预测量窗口大小,以达到更好的显示大小
// 比如dialog可能就是显示几个字而被铺满屏幕是奇怪的,通过measureHierarchy()去优化,尝试更小的宽度是否合适,这个方法会调用performMeasure()确定window大小,返回窗口大小是否会改变
// host: Decor lp: window attr rs: decor res
// desiredWindowWidth/Height: 上面初始的窗口期望宽高
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
正式确定是否重置窗口尺寸,是否需要重新计算insets
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;
}
// 同时满足三个条件
// 1.layoutRequested为true,已经发起了一次新的layout。
// 2.上面赋值的窗口尺寸可能发生改变
// 3.上面measureHierarchy()中测量的值和上一次保存的值不同 或
// 宽或高设置为wrap_content,WindowManagerService服务请求Activity窗口设置的宽度frame.width()和高度frame.height()与窗口期望大小不一致,而且与Activity窗口上一次请求WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致
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;
// 如果activity重新启动,需要强制通过wms获取最新的值
windowShouldResize |= mActivityRelaunched;
// 如果没有监听器,那么我们可能仍然需要计算insets,以防旧insets不为空,必须重置。
// 设置是否需要指定insets,设置了监听或存在需要重新设置的insets(不为空)
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
2.确定窗口尺寸,调用WMS计算并保存
// 第一次performTraversals() 或 窗口尺寸有变化 或 insets有变化 或 窗口可见性有变化
// 或 params窗口属性有变化指向了mWindowAttributes 或 强迫窗口下一次重新layout
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
// 清空这个标记位
mForceNextWindowRelayout = false;
// 设置insetsPending
if (isViewVisible) {
// 如果insets发生改变 并且 是第一次performTraversals()或窗口从不可见变为可见
// 就置insetsPending为true
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
...
// 调用WMS重新计算并保存
try {
...
// 调用relayoutWindow()(使用IPC)去请求WMS 重新计算窗口尺寸以及insets大小
// 计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中
// params: window attr view可见性 是否有额外的insets
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
...
// 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 alwaysConsumeNavBarChanged =
mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
// 如果发生了改变,就会进行重新赋值等操作
if (contentInsetsChanged) {
mAttachInfo.mContentInsets.set(mPendingContentInsets);
}
if (overscanInsetsChanged) {
mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
contentInsetsChanged = true;
}
...
if (visibleInsetsChanged) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
}
...
} catch (RemoteException e) {
}
// frame指向的是mWinFrame(也就是变量frame和mWinFrame引用的是同一个Rect对象), 此时已经是上面重新请求WMS计算后的值了
// 将值保存在mAttachInfo中
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// 如果前一次计算的值和这次计算的值有变化就重新赋值
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
...
// 是否需要重新测量
// 如果窗口不处于停止状态或者提交了下一次的绘制
if (!mStopped || mReportNextDraw) {
// Activity窗口的触摸模式发生了变化,并且由此引发了Activity窗口当前获得焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true
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);
// Ask host how big it wants to be
// 执行测量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
// 判断是否需要重新测量
boolean measureAgain = false;
// 如果需要在水平方向上分配额外的像素,需要重新测量
if (lp.horizontalWeight > 0.0f) {
// 测量宽度加上额外的宽度
width += (int) ((mWidth - width) * lp.horizontalWeight);
// 重新计算MeasureSpec
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;
}
// 如果需要重新测量了,就再调用一次performMeasure()
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
// 不是第一次performTraversals()
} else {
// 判断窗口有没有移动,如果移并且存在动画动就执行移动动画
maybeHandleWindowMove(frame);
}
3.调用layout和draw流程
// layout
// 需要layout并且窗口不是停止状态或提交了下一次draw
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 开始layout流程
performLayout(lp, mWidth, mHeight);
// 处理透明区域
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
...
}
...
}
...
// 处理insets
if (computesInternalInsets) {
// Clear the original insets.
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
// 重置insets树,清空之前的状态
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;
}
// 远程调用WMS去设置insets
try {
mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
}
}
}
// draw
// 第一次performTraversals()不会调用performDraw()因为newSurface为true,然后在scheduleTraversals()中会再次调用performTraversals()会执行draw
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// 没有取消draw并且没有创建新的surface
if (!cancelDraw && !newSurface) {
// 执行等待执行的动画
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 开始绘制
performDraw();
} else {
if (isViewVisible) {
// Try again
// 再次调用一次performTraversals()
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
// 执行结束动画
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;