View的工作原理之Measure过程源码学习(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lin962792501/article/details/84859958

        上一篇文章从Android程序启动过程讲解了Activity、PhoneWindow以及ViewRoot与DecorView的联系。本篇文章详细讲述一下DecorView的measure过程。

        在了解measure过程过程之前需要先了解MeasureSpec这个类, MeasureSpec是一个32位的int值,高2位表示SpecMode(测量模式),低30位表示SpecSize(测量大小)。MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,为方便操作,提供了打包和解包方法。MeasureSpec很大程度上决定一个View的尺寸规格,之所以说是很大程度而不是完全决定,是因为View的measure过程还受到父容器的影响。在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec来测量View的宽高。MeasureSpec部分源代码如下所示。

//View.java  24515行
 private static final int MODE_SHIFT = 30;
 private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
 public static final int UNSPECIFIED = 0 << MODE_SHIFT;
 public static final int EXACTLY     = 1 << MODE_SHIFT;
 public static final int AT_MOST     = 2 << MODE_SHIFT;

 public static int getMode(int measureSpec) {
     //noinspection ResourceType
     return (measureSpec & MODE_MASK);
 }

 public static int getSize(int measureSpec) {
     return (measureSpec & ~MODE_MASK);
 }

        如上代码,SpecMode有三种不同的值,分别是UNSPECIFIED,EXACTLY ,AT_MOST 。这里特别指出EXACTLY的值是数字1左移30位结果1073741824,AT_MOST的值数字2左移30位结果是-2147483648。为啥特别指出这两个值,在后面代码会讲到。在UNSPECIFIED这种模式下,父容器不限制View的大小,要多大给多大,但这种情况一般用户系统内部,对于我们的开发工作几乎使用不到,所以不需深入了解。EXACTLY模式是精确模式,在这种模式下,父容器检测出View的精确大小,View的最终大小就是SpecSize的值。AT_MOST模式,在这种模式下,父容器给定一个SpecSize的值,View的大小不能大于这个值,具体多大需要看View的具体实现。

        了解完MeasureSpec之后,我们开始进入DecorView的measure过程。前一篇文章说到:在ViewRootImpl中有一个方法performTraversals,在这个方法中,DecorView进行了measure,layout,draw三个过程,分别对应performMeasure,performLayout和performDraw三个方法。performTraversals方法中调用方法performMeasure,这个方法的代码如下

//ViewRootImpl.java  2404行
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

        可以看到,它实际上是调用了mView的measure方法,前一篇文章也说明了,这个mView就是DecorView。因为measure方法是final类型,所以最终调用的是View中的measure方法,而View中的measure方法会调用实现类的onMeasure中的方法,也就是DecorView的onMeasure。所以DecorView的onMeasure方法获得的参数就是performMeasure方法的参数。这个参数是什么呢?来看下面代码。

//Build.java 576行
  public static final int JELLY_BEAN_MR1 = 17;
 //View.java 4545行
  sUseBrokenMakeMeasureSpec = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
   
 //View.java  24563行
  public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
      if (sUseBrokenMakeMeasureSpec) {//判断SDK版本是否17以下
           return size + mode;
       } else {
           return (size & ~MODE_MASK) | (mode & MODE_MASK);
       }
  }

          先来看makeMeasureSpec方法的实现,这里是将测量模式mode和测量大小size结合起来组成一个32位的int型数值。这里需要注意,测量模式mode都是左移了30位的,具体参考上文的MeasureSpec部分。方法对SDK版本是否在17上下做了区分,原因我看了一下注释,好像是因为SDK17以下使用两个值相加的话,会使RelativeLayout在滚动布局中(ScrollView或者HorizontalScrollView)出现错误。因为SDK17以上,使用了位运算来获得MeasureSpec。

//ViewRootImpl.java  375行
 final Rect mWinFrame; // frame given by window manager.

//ViewRootImpl.java  1628行
Rect frame = mWinFrame;

//ViewRootImpl.java  2070行
// !!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();
}

//ViewRootImpl.java  1594行
WindowManager.LayoutParams lp = mWindowAttributes;

//ViewRootImpl.java  2145行
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

//ViewRootImpl.java  2155行
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

       上方的代码,performMeasure方法的参数来自getRootMeasureSpec,getRootMeasureSpec方法的参数,我已经在上方代码中,展示了他们的来源,mWidth和mHeight是mWinFrame的宽高,结合注释可以知道,这也就是窗口的大小了。lp的话,没有翻到最终来源,但是也很容易猜出,这应该是给予DecorView的布局约束。由此,DecorView的测量规格值,是有窗口大小和系统给予它的布局约束WindowManager.LayoutParams来共同决定了。继续看getRootMeasureSpec方法的源代码。

//ViewRootImpl.java  2633行
 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;
 }

       在getRootMeasureSpec方法中,很直观的可以看到,它是根据系统给到DecorView的布局约束WindowManager.LayoutParams的值来确定DecorView的测量规格值MeasureSpec的。这里需要额外记住一点的是ViewGroup.LayoutParams.MATCH_PARENT的值是-1,ViewGroup.LayoutParams.WRAP_CONTENT的值是-2

  1. 如果布局约束LayoutParams中,width或者height的值是LayoutParams.MATCH_PARENT,那么测量模式就是EXACTLY,精确模式,大小是窗口的宽度或高度;
  2. 如果布局约束LayoutParams中,width或者height的值是LayoutParams.WRAP_CONTENT,那么测量模式就是AT_MOST,最大模式,大小是窗口的宽度或高度;
  3. 如果布局约束LayoutParams中,width或者height的值是具体的数值,那么测量模式就是EXACTLY,精确模式,宽度或高度大小给定的具体大小;

        至此,DecorView的onMeasure方法参数的来源就清楚了,其实就是DecorView的宽高的测量规格值。查看DecorView的onMeasure方法源代码得知,因为DecorView和子元素之间存在一些需要处理的间距,所以真正传递给子元素的测量规格值需要进行处理,这就是onMeasure方法的功能。DecorView的onMeasure方法还调用super.onMeasure(widthMeasureSpec, heightMeasureSpec);。因为DecorView继承自FrameLayout,所以这里将调用FrameLayout的onMeasure方法,在这个方法中,会获取子元素的个数,然后遍历子元素的measure过程,这样就把measure过程从DecorView传到了子元素去。而子元素的measure过程,如果这个子元素不是ViewGroup,而是普通的View,那么就执行自己的measure过程就行了,如果是ViewGroup的话,还需要像FrameLayout一样,循环调用子元素的measure过程,如此一级一级往下,所有View的measure过程都会被执行。这个在下篇文章中将会详细讲解。

       DecorView的Measure过程到这就完成了。

猜你喜欢

转载自blog.csdn.net/lin962792501/article/details/84859958