View的工作流程--onMeasure

View何时开始绘制?-requestLayout()

  • Window是View的载体,在ViewRootImpl的setView方法中添加Window到WMS之前。会调用requestLayout绘制整颗View Hierarchy的绘制。
  • Activity在onResume执行结束后才会进行view的绘制和显现。在handleResumeAcitvity()方法执行完Activity的onResume()方法后,会把DecorView加载到Window中,并同时创建ViewRootImpl对象。
    ActivityThread::handleResumeActivity()
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
	//...
	if (a.mVisibleFromClient) {
	                if (!a.mWindowAdded) {
	                    a.mWindowAdded = true;
	                    //调用WindowManger的addView()方法
	                    wm.addView(decor, l);
	                } else {
	                    // The activity will get a callback for this {@link LayoutParams} change
	                    // earlier. However, at that time the decor will not be set (this is set
	                    // in this method), so no action will be taken. This call ensures the
	                    // callback occurs with the decor set.
	                    a.onWindowAttributesChanged(l);
	                
	               }
}

经过一系列的调用之后
WinWindowManagerImpl ::addView() --> WindowManagerGlobal::addView() -->ViewRootImpl::setView()

看看ViewRootImp::setView()

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
  	//...	
    //进行View的绘制流程
    requestLayout();
     //...
    //通过session与WMS建立通信
     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
    //...
}

接下来看看ViewRootImpl::requestLayout():

public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
        //检查是否在主线程,在子线程会抛出异常
            checkThread();
            //是否measure和layout布局的开关
            mLayoutRequested = true;
            //开始遍历View Hierarchy绘制
            scheduleTraversals();
        }
    }

为什么不能在子线程更新UI?

Android中的UI访问时没有加锁,多个线程可以同时访问更新操作同一个UI控件,是线程不安全的。在多线程模式下,当多个线程共同访问更新操作同一个UI控件时,容易产生不可控的错误,这是致命的。

接下来继续看看ViewRootImpl::scheduleTraversals() 方法

void scheduleTraversals() {

        if (!mTraversalScheduled) {//防止同一帧绘制多次
            mTraversalScheduled = true;
            //同步屏障
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//向主线程消息队列post一个Runnable.这个Runnable最终会调用performTraversals()
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            //...
        }
    }
  • 向消息队列添加同步屏障,它的作用是非异步的消息都不能执行,保障刷新的Message能够第一时间得到调用。

View树绘制的起点 - performTraversals()

performTraversals()它是整个View Hierarchy绘制的起点,它里面会执行View绘制的三大工作流程,进入ViewRootImpl::performTraversals()瞅瞅:

private void performTraversals() {
    //代表着View Hierarchy的根节点,即根视图
    final View host = mView;
    //...
    WindowManager.LayoutParams lp = mWindowAttributes;
    //desiredWindowWidth和desiredWindowHeight分别代表着屏幕的宽度和高度
    int desiredWindowWidth;
    int desiredWindowHeight;
    //...
    if (mLayoutRequested) {
        final Resources res = mView.getContext().getResources();
        //...
        //这里调用了measureHierarchy方法,里面会调用performMeasure方法,执行View Hierarchy的measure流程
        windowSizeMayChange |= measureHierarchy(host, lp, res,
     	                                         desiredWindowWidth, desiredWindowHeight);
        //...
    }
    //...
    if(didLayout){
          //这里调用了performLayout方法,执行View Hierarchy的layout流程
         performLayout(lp, mWidth, mHeight);
        //...
    }
    //...
    if (!cancelDraw && !newSurface) {
        //...
        //这里调用了performDraw方法,执行View Hierarchy的draw流程
   		performDraw();
    }
    //...
}

看看ViewRootImpl::measureHierarchy():

 private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,  final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
     int childWidthMeasureSpec;
     int childHeightMeasureSpec;
     //...
     //顶级View在调用performMeasure方法之前,会先调用getRootMeasureSpec方法来生成自身宽和高的MeasureSpec
     childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
     childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
     //这里调用performMeasure方法,执行View Hierarchy的measure流程
     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
 }

View的绘制的三个方法:

  • onMeasure:测量,判断当前这个View需要多大
  • onLayout:布局,判断当前这个View要放置到整个布局的什么地方
  • onDraw:绘制

View的测量流程 – performMeasure()

首先先看看ViewRootImpl::measureHierarchy()方法中的ViewRootImpl::getRootMeasureSpec()方法:

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // 如果为MATCH_PARENT,MeasureSpecMode为MeasureSpec.EXACTLY
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // 如果为WRAP_CONTENT,MeasureSpecMode为MeasureSpec.EXACTLY
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // 如果有固定的值,MeasureSpecMode也是MeasureSpec.EXACTLY
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

顶级View和子View的MeasureSpec

接下来看看上面所示源码中出现最多的MeasureSpec,它是View的静态内部类。
进入源码View::MeasureSpec类(顶级View):

public static class MeasureSpec {
		//左移位数
        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 makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
           //...

     
        @UnsupportedAppUsage
        public static int makeSafeMeasureSpec(int size, int mode) {
            if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
                return 0;
            }
            return makeMeasureSpec(size, mode);
        }

     //从给定的MeasureSpec中取出SpecMode
        @MeasureSpecMode
        public static int getMode(int measureSpec) {
         
            return (measureSpec & MODE_MASK);
        }

       //从给定的MeasureSpec中取出SpecSize
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        //...
        }
    }

从上面可以看出,。MeasureSpec是一个32位的int值,高两位表示SpecMode测量模式,低30位表示当前测量模式下的规格大小。SpecMode可以有三种取值:

  • UNSPECIFIED:父控件没有给子view任何限制,子View可以设置为任意大小。在ScrollView这类滑动控件的情况下,子视图的高度是不相关的。 因此,它将向孩子提供一个UNSPECIFIED视图,告诉孩子他们可以像他们需要的一样高。ScrollView将处理它们的绘图和放置。
  • EXACTLY :精准模式,设置空间大小时如果是指定大小或者match-parent就都是这种模式。
  • AT_MOST:最大模式,wrapContent就是这种。
    MeasureSpec讲解完之后,又回到最初的ViewRootImpl::getRootMeasureSpec()方法,此方法为顶级View的MeasureSpec的创建,由于它没有父容器,所以MeasureSpec是由屏幕窗口和自身的LayoutParams来共同决定的,rootDimension就是传入的屏幕窗口的LayoutParams的大小模式。

接下来看看子View的MeasureSpec创建 --getChildMeasureSpec()
子View的MeasureSpec都是由父容器的MeasureSpec和自身的LayoutParams共同决定的。
ViewGroup::measureChild()

    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
//这里调用了getChildMeasureSpec方法,里面就是创建子View的MeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
//如果子View是一个ViewGroup,递归measure下去
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在子View进行measure()方法前会调用getChildMeasureSpec()获得子View的MeasureSpec,我们进入ViewGroup::getChildMeasureSpec()方法:

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //取出父容器的测量模式specMode
        int specMode = MeasureSpec.getMode(spec);
        //取出父容器的测量大小specSize
        int specSize = MeasureSpec.getSize(spec);
		//子View最大可用大小size == 父容器剩余大小 == 父容器的尺寸减去padding 
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY://父容器是EXACTLY
            if (childDimension >= 0) {//子View的LayoutParams是固定大小
            //子View的大小
                resultSize = childDimension;
                //子View的MeasureSpecMode
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {//子View的LayoutParams是MATCH_PARENT
                
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {如果子View的LayoutParams是WRAP_CONTENT
                
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST://父容器是AT_MOST
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED://父容器是UNSPECIFIED
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

看一下View.sUseZeroUnspecifiedMeasureSpec:

 /**
     * Always return a size of 0 for MeasureSpec values with a mode of UNSPECIFIED
     */
   
    static boolean sUseZeroUnspecifiedMeasureSpec = false;

看完顶级View和子View的MeasureSpec后,接下来就进入measure流程了

View和ViewGroup的measure流程

measure流程都是从ViewRootImpl的performMeasure()开始,并且都会先调用View的measure方法。
ViewRootImpl::performMeasure():

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

View::measure():

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
      //...
  
      onMeasure(widthMeasureSpec, heightMeasureSpec);
	 //...
  }

measure方法是一个final方法,方法不能被子类重写,measure的具体过程交给了onMeasure方法实现。

View的onMeasure流程

进入View::onMeasure():

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //如果View没有重写onMeasure方法,则会调用setMeasuredDimension方法设置宽高,在设置之前先调用getDefaultSize方法获取默认宽高
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

当调用setMeasuredDimension方法设置View的宽高后,就可以通过getMeasureWidth()或getMeasureHeight()获得View测量的宽高,看看 getDefaultSize()方法:

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //如果specMode是UNSPECIFIED,返回的大小就是传进来的size,而这个size就是通过getSuggestedMinimumWidth()或getSuggestedMinimumHeight()方法获得的
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
            //如果specMode是AT_MOST或EXACTLY,返回的大小就是MeasureSpec中的specSize
           //如果自定义控件直接继承自View,得重写onMeasure()方法,因为在AT_MOST
           //或者EXACTALY,它们的返回值一样,都是父容器剩下的大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

在MeasureSpec.UNSPECIFIED下,看看getSuggestedMinimumWidth()得到的大小:

protected int getSuggestedMinimumHeight() {
//在UNSPECIFIED模式下,如果View没有设置背景,
//那么View的高就等于android:minHeight,如果View设置了背景,
//那么View的宽就等于View的背景background的宽和android:minHeight的最大值.。
	return (mBackground == null) ?
	 mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }

View的onMeasure方法执行完后,就可以通过getMeasureWidth()或getMeasureHeight()获得View测量的宽高,但是有可能会不准确。

ViewGroup的Measure流程

ViewGroup继承自View,但是是一个抽象类,并没有重写View的onMeasure()方法,而是由ViewGroup的子类重写onMeasure()方法,实现不同的measure流程,以FrameLayout为例。
FrameLayout::onMeasure():

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //获取子View的个数
        int count = getChildCount();

        final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
        mMatchParentChildren.clear();

        int maxHeight = 0;
        int maxWidth = 0;
        int childState = 0;
//遍历所有子View,测量每个子View的大小
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {//如果子View可见
            //测量子View的大小
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //FrameLayout自身的测量宽高就是所有子View宽高中的最大值
                maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                childState = combineMeasuredStates(childState, child.getMeasuredState());
                if (measureMatchParentChildren) {
                    if (lp.width == LayoutParams.MATCH_PARENT ||
                            lp.height == LayoutParams.MATCH_PARENT) {
                        mMatchParentChildren.add(child);
                    }
                }
            }
        }

        // Account for padding too
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

        // Check against our minimum height and width
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

      //...
            }
        }
    }

FrameLayout的onMeasure()方法是遍历所有子View,然后逐个测量子View的大小

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
    //省略的这部分在上面已经讲过,主要是创建子View的MeasureSpec(childWidthMeasureSpec, childHeightMeasureSpec)
    //...
    //调用子View的measure方法,叫子View自己测量自己
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

小结

measure流程主要是测量View的宽高。首先会去计算MeasureSpec,如果是DecorView就会根据window尺寸和自身的参数来决定,如果是普通的View,就是根据父容器和自身的参数决定。在计算好MeasureSpec后,就会调用performMeasure,里面调用measure()进行测量工作,最后调用onMeasure()去设置测量宽高,如果是ViewGroup,因为它并没有定义其测量的具体过程,测量过程需要子类实现。所以会在具体的ViewGroup中还会去遍历子View,调用子View的measure进行测量工作;如果是子View,直接开始测量,设置View的宽高,这就是整个测量流程。

参考资料:
View的工作原理

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页