View工作原理之MeasureSpec、ViewImpl、DecorView

初识DecorView和ViewRootImpl/ViewRoot

很多书上都提过这两个类,我一度以为他们俩谁是谁的实现类,查了一下发现ViewRoot是Android2.2以前的,2.2之后就被ViewRootImpl替代了。所以叫ViewRootImpl就好了

DecorView和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

DecorView加入Window时序图

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();
    }

MeasureSpec

LayoutParams

你可能感兴趣的:(View工作原理之MeasureSpec、ViewImpl、DecorView)