初识DecorView和ViewRootImpl/ViewRoot
很多书上都提过这两个类,我一度以为他们俩谁是谁的实现类,查了一下发现ViewRoot是Android2.2以前的,2.2之后就被ViewRootImpl替代了。所以叫ViewRootImpl就好了
DecorView
- Activity#setContentView
- Activity#attach
mWindow = new PhoneWindow(this); - PhoneWindow#setContentView()
public void setContentView(int layoutResID) {
//mContentParent是mDecor本身或者是mDecor的一个子元素
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
......
- PhoneWindow#installDecor()
- PhoneWindow#generateDecor()
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
- PhoneWindow#generateLayout
设置DecorView,是否有titlebar,为DecorView添加子view,即上面提到的mContentParent
ViewRootImpl
ViewRootImpl开启测量、布局和绘制流程
ViewRootImpl是连接WindowManager和DecorView的纽带,它负责开启了视图树的测量 布局 绘制流程,这一过程在它的performTaversals()方法中。
首先看一下performTraversals()方法是从哪里开始被调用的,在ViewRootImpl中有一个setView()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
……
requestLayout();;
}
}
}
requsetLayout方法中调用了scheduleTraversals()方法
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
看到下面post了mTraversalRunnable
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
TraversalRunnable是一个线程
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
开启线程执行doTraversals()方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在doTraversal()找到了唯一调用perform的入口。
由performTraversals()出现的大致流程可以知道,ViewRootImpl的setView()方法,将DecorView和将要加进去的view绑定,通过:
- requestLayout()--->
- scheduleTraversals()--->
- 开启TraversalRunnable线程调用doTraversal()--->
- performTraversals()
performTraversals()
过程可以大致分为三部分
1.计算出控件树为显示其内容所需的尺寸,即期望的窗口尺寸
开始执行的条件:
//这个mView是通过setView方法传进来的,也就是Activity的根布局DecorView,使用final修饰,以防在遍历过程中被修改
final View host = mView;
//mAdded在setView方法修改为true,表明Decorview已经添加到了PhoneWindow
if (host == null || !mAdded)
return;
在往下就是计算当前窗口的宽度和高度,大致可以分为两个部分,第一次执行遍历和不是第一次
//Activity窗口的宽度和高度
int desiredWindowWidth;
int desiredWindowHeight;
...
//用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸
Rect frame = mWinFrame;
//构造方法里mFirst赋值为true,意思是第一次执行遍历吗
if (mFirst) {
//是否需要重绘
mFullRedrawNeeded = true;
//是否需要重新确定Layout
mLayoutRequested = true;
这里又包含两种情况:是否包括状态栏
//判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的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 {
//宽度和高度为整个屏幕的值
Configuration config = mContext.getResources().getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
...
else{
这是window的长和宽改变了的情况,需要对改变的进行数据记录
//如果不是第一次进来这个方法,它的当前宽度和高度就从之前的mWinFrame获取
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;
}
}
...
//进行预测量
if (layoutRequested){
...
if (mFirst) {
// 视图窗口当前是否处于触摸模式。
mAttachInfo.mInTouchMode = !mAddedTouchMode;
//确保这个Window的触摸模式已经被设置
ensureTouchModeLocally(mAddedTouchMode);
} else {
//六个if语句,判断insects值和上一次比有什么变化,不同的话就改变insetsChanged
//insects值包括了一些屏幕需要预留的区域、记录一些被遮挡的区域等信息
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
...
这里有一种情况,我们在写dialog时,会手动添加布局,当设定宽高为Wrap_content时,会把屏幕的宽高进行赋值,给出尽量长的宽度
/**
* 如果当前窗口的根布局的width或height被指定为WRAP_CONTENT时,
* 比如Dialog,那我们还是给它尽量大的长宽,这里是将屏幕长宽赋值给它
*/
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
//判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的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 {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
}
}
// 进行预测量窗口大小,以达到更好的显示大小
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
但是当我们的内容很少时,不需要那么多的宽高,measureHierarchy()方法就会缩小宽高来找一个匹配当前内容的样式。很人性化,这就是源码中的亮点地方吧
performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
调用了view.measure(),measure()方法并没有做什么,而是将测量交给了它内部的onMeasure()方法
2.第二阶段
* 开始进入三大阶段的第一个阶段了 6个条件只要满足一个就进入
* mFirst true 说明是第一次执行测量布局绘制操作
* windowShouldResize true 即Activity窗口大小需要改变
* insetsChanged true 说明此次窗口overscan等一些边衬区域发生了改变,与上次不一样
* viewVisibilityChanged 说明View的可见性发生了变化
* params 即窗口属性发生了变化,指向了mWindowAttributes
* mForceNextWindowRelayout true 即ViewRootHandler接收到消息MSG_RESIZED_REPORT,即size改变了
//请求WMS计算Activity窗口大小及边衬区域大小
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
这一部分还是在计算测量
3.layout和draw
//根据这三个值判断需不需要重新布局
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
...
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
//没有取消绘制 也没有重新创建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();
}