谈谈Android中View的绘制流程及performTraversals方法

谈谈Activity的setContentView是怎么加载XML视图的
谈谈Activity的View怎么与View绘制工具ViewRootImpl关联的
在前面两篇文章中分析了View是如何跟绘制工具ViewRootImpl关联的,setContentView是如何把我们编写的xml视图添加到Activity中的,那今天就要分析下绘制工具ViewRootImpl是如何绘制我们Activity当中的View的。

首先要了解View是什么:

View代表了用户界面组件的基本构建块。一个View在屏幕上占据一个矩形区域,并负责绘图和事件处理。View是窗口小部件的基类,它是用于创建交互式的用户界面组件(按钮,文本等)

从前面两篇文章可知

  1. Activity的attach方法会构造一个PhoneWindow实例
  2. 我们在onCreate里通过setContentView将我们的xml添加到了DecorView
  3. ActivityThread在后续过程中会将DecorView添加到Activity的窗口中,也就是添加到PhoneWindow
  4. WindowManagerGlobal通过ViewRootImpl的setView方法将DecorView传递到ViewRootImpl进行绘制

我们来看下setView方法

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
             mView = view;
             ……
             requestLayout();;
         }
     }
 }
 @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

可以看到这里post了一个mTraversalRunnable,我们看看这个runnable做了啥事

 final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }

            performTraversals();

            if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }

终于找到View绘制的最终入口了,也就是这个方法performTraversals()

performTraversals()

首先我们看这个方法名,perform是执行的意思,而Traversals是遍历循环的意思;所以这个方法看方法名就知道他是在遍历Activity的根布局DecorView里(或者其它窗口比如Dialog)的每一个View。

如果把Android中的View框架比作一个人,那我觉得这个方法就是人的心脏,非常重要。WMS的窗口属性变化,来自控件树的尺寸变化、重绘请求等都引发performTraversals()的调用,并在其中完成处理。View类及其子类中的onMeasure()、onLayout()以及onDraw()等回调也都是在performTraversals()的执行过程中直接或间接地引发。也正是如此,一次次的performTraversals()调用驱动着控件树有条不紊地工作着,一旦此方法无法正常执行,整个控件树都将处于僵死状态。

performTraversals方法可以说是我目前见到的Android源码中的几个最庞大的方法之一了,将近800行,如果你是第一次看这个肯定会迷糊的,没事,像我这样有时间看它个十几二十遍,你就会对它有个差不多的认识了。鉴于此方法太长,只能分段来看:

第一段:

//这个mView是通过setView方法传进来的,也就是Activity的根布局DecorView,使用final修饰,以防在遍历过程中被修改
        final View host = mView;

        if (DBG) {
            System.out.println("======================================");
            System.out.println("performTraversals");
            host.debug();
        }
        //mAdded在setView方法修改为true,表明Decorview已经添加到了PhoneWindow
        if (host == null || !mAdded)
            return;
        //是否正在遍历
        mIsInTraversal = true;
        //是否需要马上绘制
        mWillDrawSoon = true;
        //视图大小改变
        boolean windowSizeMayChange = false;
        //新视图
        boolean newSurface = false;
        //视图改变
        boolean surfaceChanged = false;
        WindowManager.LayoutParams lp = mWindowAttributes;

        //Activity窗口的宽度和高度
        int desiredWindowWidth;
        int desiredWindowHeight;
        //DecorView是否可见
        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded);

        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;
        //用来保存窗口宽度和高度,来自于全局变量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);
            }

            /**
             * 因为第一次遍历,View树第一次显示到窗口
             * 然后对mAttachinfo进行一些赋值
             * AttachInfo是View类中的静态内部类AttachInfo类的对象
             * 它主要储存一组当View attach到它的父Window的时候视图信息
             */
            mAttachInfo.mUse32BitDrawingCache = true;//使用32位绘图缓存
            mAttachInfo.mHasWindowFocus = false;//视图窗口当前不具有焦点。
            mAttachInfo.mWindowVisibility = viewVisibility;//当前窗口不可见
            mAttachInfo.mRecomputeGlobalAttributes = false;//ViewAncestor应在下次执行遍历时触发全局布局更改
            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
            // 如果之前未设置布局方向,则设置布局方向(View.LAYOUT_DIRECTION_INHERIT 是默认值)
            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                host.setLayoutDirection(mLastConfiguration.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
            //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

        } else {
                //如果不是第一次进来这个方法,它的当前宽度和高度就从之前的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 (viewVisibilityChanged) {
            mAttachInfo.mWindowVisibility = viewVisibility;
            host.dispatchWindowVisibilityChanged(viewVisibility);
            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;
            }
        }

        // 如果窗口不可见了,去掉可访问性焦点
        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;
             }
           }
         */
        getRunQueue().executeActions(mAttachInfo.mHandler);

        boolean insetsChanged = false;

        boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
        //进行预测量
        if (layoutRequested) {

            final Resources res = mView.getContext().getResources();

            if (mFirst) {
                // 视图窗口当前是否处于触摸模式。
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                //确保这个Window的触摸模式已经被设置
                ensureTouchModeLocally(mAddedTouchMode);
            } else {
                    /**
                 * 判断一下几个insects的值和上一次相比有没有什么变化,不同的话就改变insetsChanged
                 * mOverscanInsets 记录屏幕中的 overscan 区域               见贴图中相应描述区域
                 * mContentInsets  记录了屏幕中的控件在布局时必须预留的空间   见贴图中相应描述区域
                 * mStableInsets   记录了Stable区域,比mContentInsets区域大  见贴图中相应描述区域
                 * mVisibleInsets  记录了被遮挡的区域,如正在进行输入的TextView等不被遮挡,这样VisibleInsets的变化并不会导致重新布局,
                 *                 所以这里仅仅是将VisibleInsets保存到mAttachInfo中,以便绘制时使用
                 */
                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;
                }
                 /**
                 * 如果当前窗口的根布局的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);
        }

这里写图片描述

到这里主要计算出控件树为显示其内容所需的尺寸,即期望的窗口尺寸。

注意:

通过上面的代码可以看到desiredWindowWidth和desiredWindowHeight是经过一系列代码考核得到的值,讲道理整个View树是可以按照这个值去测量的,但是我们不仅只是测量和布局,还要尽可能舒适的一个UI去展示给用户

比如在大屏幕上,Dialog的width修饰为WRAP_CONTENT,按照上面的代码,其实desiredWindowWidth还是给了尽量大的值,就是屏幕宽度;但是Dialog可能就是为了显示几个字,那结果就是整个dialog的布局就被拉伸铺满屏幕;显然这种UI是不美丽的,那就需要通过measureHierarchy方法去优化,尝试下更小的宽度是否合适。就这样来到了measureHierarchy方法。

measureHierarchy

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
        //用于描述宽度的MeasureSpec
        int childWidthMeasureSpec;
        //用于描述高度的MeasureSpec
        int childHeightMeasureSpec;
        //表示测量结果是否可能导致窗口的尺寸发生变化
        boolean windowSizeMayChange = false;

        if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
                "Measuring " + host + " in display " + desiredWindowWidth
                + "x" + desiredWindowHeight + "...");
        //表示测量是否能满足控件树充分显示内容的要求
        boolean goodMeasure = false;
        //其实测量协商仅仅发生在width被指定为WRAP_CONTENT情况
        if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
            // 进入到这里就让窗口的大小不以屏幕大小去布局,而是给一个固定的值看看是否合适
            final DisplayMetrics packageMetrics = res.getDisplayMetrics();
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                //取出一个固定值来用,来自于config_prefDialogWidth
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                    + ", desiredWindowWidth=" + desiredWindowWidth);

            //假设上面config_prefDialogWidth值是320dp
            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 {
                    /**
                     * 走到这里说明上面的尺寸不适合View树
                     * 需要对宽度再进行放松,使用上面的预期值与最大值的平均值作为新的宽度
                     */
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                            + baseSize);
                    //重新获取MeasureSpec
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    //第二次测量
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                            + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                    //再次判断View树对新的结果是否满意
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
        }

        /**
         * 如果上面两次尝试测量结果,View树都不满意
         * 那老哥也没办法了,做不了优化了,只能以屏幕宽高去给View树测量了
         * 这是第三次也是最后一次测量了
         */
        if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            //如果测量得出的结果与当前窗口值不一样,就需要调整窗口大小了
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                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方法,根据给定的大小和模式返回一个度量规范

getRootMeasureSpec

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:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

既然宽高的度量规范出来了,那就开始测量了,调用performMeasure方法

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

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

        //判断当前View是否是以可见边界布局的ViewGroup
        boolean optical = isLayoutModeOptical(this);
        //对度量规范进行校正
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;

        // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

        if (forceLayout || needsLayout) {
            // 先清除测量尺寸标记
            // PFLAG_MEASURED_DIMENSION_SET标记用于检查控件在onMeasure()方法中是否通过
            //调用setMeasuredDimension()将测量结果存储下来
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            //解析所有与RTL相关的属性,比如背景,字体,padding等属性设置
            resolveRtlPropertiesIfNeeded();

            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //对自己进行测量, 每个View子类都需要重写这个方法以便正确地对自身进行测量
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            /**
             * 检查View子类的onMeasure()是否调用了setMeasuredDimension()
             * setMeasuredDimension()会将PFLAG_MEASURED_DIMENSION_SET标记重新加入mPrivateFlags中。
             * 之所以做这样的检查,是由于onMeasure()的实现可能由开发者完成,
             * 而在Android看来,开发者是不可信的
             */
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }
        //记录父控件给予的MeasureSpec,用以检查之后的测量操作是否有必要进行
        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }

可以看到其实这个方法并没有做任何的测量工作,真正的测量过程交给了onMeasure方法去做,它的作用在于引发onMeasure()的调用,并对onMeasure()行为的正确性进行检查。另外,在控件系统看来,一旦控件执行了测量操作,那么随后必须进行布局操作,因此在完成测量之后,将PFLAG_LAYOUT_REQUIRED标记加入mPrivateFlags,以便View.layout()方法可以顺利进行

onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

可以看到这个方法实现很简单,只调用了setMeasuredDimension()方法保存测量结果,具体的实现由子类去重写,提供更加合理、高效的实现

setMeasuredDimension

必须通过onMeasure调用此方法来存储测量的宽度和测量的高度。
如果不这样做,将在测量时触发异常

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int opticalWidth  = insets.left + insets.right;
            int opticalHeight = insets.top  + insets.bottom;

            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
            measuredHeight += optical ? opticalHeight : -opticalHeight;
        }
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
        //保存测量的宽度
        mMeasuredWidth = measuredWidth;
        //保存测量的高度
        mMeasuredHeight = measuredHeight;
        //向mPrivateFlags中添加PFALG_MEASURED_DIMENSION_SET,以此证明onMeasure()保存了测量结果
        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

其实这里的逻辑就是我们自定义View的时候,重写onMeasure方法,我们自己实现测量过程,测量结束后然后调用setMeasuredDimension方法将测量结果传入进行保存。
两个测量结果可以通过getMeasuredWidthAndState()与getMeasuredHeightAndState()两个方法获得。测量结果不仅仅是一个尺寸,而是一个测量状态与尺寸的复合整型值。其低30位表示了测量结果的尺寸,而高两位则表示了控件对测量结果是否满意,即父控件给予的MeasureSpec是否可以使得子控件完整地显示其内容。当控件对测量结果满意时,直接将尺寸传递给setMeasuredDimension()即可,注意要保证高两位为0。倘若对测量结果不满意,则使用View.MEASURED_STATE_TOO_SMALL | measuredSize 作为参数传递给setMeasuredDimension()以告知父控件对MeasureSpec进行可能的调整。回看measureHierarchy方法能看到调用getMeasuredWidthAndState,来判断是否对测量结果是否满意。

执行完measureHierarchy方法后,ViewRootImpl就知道了View树需要的大小,并同时修改windowSizeMayChange的值,它的表示有可能需要改变窗口大小以适应View树的大小要求,接下来的一段代码就正式确定是否需要改变窗口大小

        /**
         * 用于确定是否需要更改窗口大小用来适用View树的空间要求
         * layoutRequested 为true,说明view正在调用自身的requestLayout,自身内容变化要求重新布局,
         * 随后此方法会沿着控件树向根部回溯,最终调用到ViewRootImp.requestLayout()
         * windowSizeMayChange 为true ,它有多处赋值,说明View树所需大小与窗口大小不一致
         * (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() 判断上面测量后View树的大小与窗口大小值是否相等
         * 最后的条件是如果窗口width或height被设置成WRAP_CONTENT,计算出来的窗口大小desiredWindowWidth/desiredWindowHeight 与上一次测量保存的frame.width()/frame.height()
         * 同时与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;

        // 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.
        //如果Activity重新启动,需要强制通过wms获取最新的值
        windowShouldResize |= mActivityRelaunched;

第二阶段

/**
         * 开始进入三大阶段的第一个阶段了 6个条件只要满足一个就进入
         * mFirst true 说明是第一次执行测量布局绘制操作
         * windowShouldResize true 即Activity窗口大小需要改变
         * insetsChanged true 说明此次窗口overscan等一些边衬区域发生了改变,与上次不一样
         * viewVisibilityChanged 说明View的可见性发生了变化
         * params 即窗口属性发生了变化,指向了mWindowAttributes
         * mForceNextWindowRelayout true 即ViewRootHandler接收到消息MSG_RESIZED_REPORT,即size改变了
         */
        final boolean isViewVisible = viewVisibility == View.VISIBLE;
        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;

            ......
            ......

            try {

                //请求WMS计算Activity窗口大小及边衬区域大小
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

                ......
                    ......

                /**
                 * mPendingOverscanInsets等rect在relayoutWindow方法里保存了最新窗口大小值
                 * 再与上一次测量的保存在mAttachInfo中的值进行比较
                 */
                final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
                        mAttachInfo.mOverscanInsets);
                boolean contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
                        mAttachInfo.mVisibleInsets);
                final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                        mAttachInfo.mStableInsets);
                final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
                final boolean surfaceSizeChanged = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                final boolean alwaysConsumeNavBarChanged =
                        mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
                //如果改变了,就重新将最新值保存在mAttachInfo中
                if (contentInsetsChanged) {
                    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 (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);
                }

                ......
                    ......

            // !!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.
            //从window session获取最大size作为当前窗口大小
            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }

            ......
            ......

            /**
             * mStopped true 说明当前窗口的所有者 比如activity处于暂停状态
             * mReportNextDraw true 即ViewRootHandler接收到消息MSG_RESIZED_REPORT ,需要绘制
             */
            if (!mStopped || mReportNextDraw) {

                //确定当前窗口处于触摸模式,也就是获取焦点
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                        (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);

                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || framesChanged ||
                        updatedConfiguration) {

                    //获取宽高的度量规范
                    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()
                            + " framesChanged=" + framesChanged);

                     // 执行测量操作
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // Implementation of weights from WindowManager.LayoutParams
                    // We just grow the dimensions as needed and re-measure if
                    // needs be
                    //获取测量后的View的真是宽度和高度
                    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 {
            // 判断窗口有没有移动,如果移动就执行移动动画
            maybeHandleWindowMove(frame);
        }

第二阶段也就是我们View绘制三大流程第一步了,具体测量代码分析在第一阶段已经分析了,就不在赘述

第三阶段

 //通过这几个值来确定是不是要重新布局
        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
            //计算透明区域
            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) {
                    }
                }
            }  
}

performLayout方法会调用View的layout方法,在layout方法中回调onLayout方法,开发者可以重写这个方法,这个具体分析与measure类似

第四阶段

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

在View的Draw方法里绘制一个View分一下几步

  • drawBackground 绘制背景
  • 保存画布图层
  • onDraw(canvas) 回调 绘制内容 由子类去实现
  • dispatchDraw(canvas) 有子view 就绘制子view
  • 绘制淡入淡出效果并恢复图层
  • onDrawForeground(canvas) 画装饰 比如前景色 滚动条

这里就谈下我们的Activity,Activity启动后,它的DecorView会被ViewRootImpl类的performTraversals方法去操作;

  1. 首先就是performMeasure的过程,这个方法会调用到View类的measure方法,这里并没有具体的测量实现,而是调用到了onMeasure,需要子类去实现;要知道DecorView其实就是个FrameLayout,是一个ViewGroup,更是一个View,自己重写onMeasure方法,去循环调用子View的measure过程;子类也会重写onMeasure方法,自己实现测量自己的大小

  2. 这样所有的View测量结束,第二步就是performLayout了,默认是调用到了View类layout方法;如果是ViewGroup,就调用ViewGroup的layout方法,但是这个方法没有具体实现,还是调用到了View类的layout方法;View类的layout方法回调onLayout方法,要怎么确定位置由子类重写onlayout方法实现;这里DecorView去重写,自己实现,循环调用每个子View的layout方法,将上下左右坐标值传递给子View,确定子View的位置

  3. 接下来就是最后的绘制过程了,也就是第三步performDraw,继续走到ViewTreeObserver的dispatchOnDraw方法,通知所有注册了OnDrawListener接口的View去调用自己的onDraw()方法,绘制自己;DecorView因为是个ViewGroup,重写了dispatchDraw方法,循环调用每个子View的draw方法;最后就是每个子View重写onDraw方法绘制自身

猜你喜欢

转载自blog.csdn.net/qq_30993595/article/details/80931556