美文网首页Android面试view
Android - View 绘制流程

Android - View 绘制流程

作者: Whyn | 来源:发表于2022-01-23 19:48 被阅读0次
    View 绘制流程

    简介

    我们知道,在 Android 中,View 绘制主要包含 3 大流程:

    • measure(测量):主要用于确定 View 的测量宽/高。

    • layout(布局):主要用于确定 View 在父容器中的放置位置。

    • draw(绘制):结合前面两步结果,将 View 真正绘制到屏幕上。

    Android 中,主要有两种视图:ViewViewGroup,其中:

    • View:就是一个独立的视图
    • ViewGroup:一个容器组件,该容器可容纳多个子视图,即ViewGroup可容纳多个ViewViewGroup,且支持嵌套。

    虽然ViewGroup继承于View,但是在 View 绘制三大流程中,某些流程需要区分ViewViewGroup,它们之间的操作并不完全相同,比如:

    • ViewViewGroup都需要进行 measure,确定各自的测量宽/高。View只需直接测量自身即可,而ViewGroup通常都必须先测量所有子View,最后才能测量自己
    • 通常ViewGroup先定位自己的位置(layout),然后再定位其子View 位置(onLayout
    • View需要进行 draw 过程,而ViewGroup通常不需要(当然也可以进行绘制),因为ViewGroup更多作为容器存在,起存储放置功能

    measure 流程

    对 View 进行测量,主要包含两个步骤:

    1. 求取 View 的测量规格MeasureSpec
    2. 依据上一步求得的MeasureSpec,对 View 进行测量,求取得到 View 的最终测量宽/高。

    MeasureSpec

    对于第一个步骤,即求取 View 的MeasureSpec,首先我们来看下MeasureSpec的源码定义:

    // frameworks/base/core/java/android/view/View.java
    public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    
        /**
         * Measure specification mode: The parent has not imposed any constraint
         * on the child. It can be whatever size it wants.
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
        /**
         * Measure specification mode: The parent has determined an exact size
         * for the child. The child is going to be given those bounds regardless
         * of how big it wants to be.
         */
        public static final int EXACTLY     = 1 << MODE_SHIFT;
    
        /**
         * Measure specification mode: The child can be as large as it wants up
         * to the specified size.
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;
    
        // 生成测量规格
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    
        // 获取测量模式
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
    
        // 获取测量大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        ...
    }
    

    MeasureSpecView的一个公有静态内部类,它是一个 32 位的int值,高 2 位表示 SpecMode(测量模式),低 30 位表示 SpecSize(测量尺寸/测量大小)。
    MeasureSpec将两个数据打包到一个int值上,可以减少对象内存分配,并且其提供了相应的工具方法可以很方便地让我们从一个int值中抽取出 View 的 SpecMode 和 SpecSize。

    一个MeasureSpec表达的是:该 View 在该种测量模式(SpecMode)下对应的测量尺寸(SpecSize)。其中,SpecMode 有三种类型:

    • UNSPECIFIED:表示父容器对子View 未施加任何限制,子View 尺寸想多大就多大。

    • EXACTLY:如果子View 的模式为EXACTLY,则表示子View 已设置了确切的测量尺寸,或者父容器已检测出子View 所需要的确切大小。
      这种模式对应于LayoutParams.MATCH_PARENT和子View 设置具体数值两种情况。

    • AT_MOST:表示自适应内容,在该种模式下,View 的最大尺寸不能超过父容器的 SpecSize,因此也称这种模式为 最大值模式
      这种模式对应于LayoutParams.WRAP_CONTENT

    LayoutParams

    对 View 进行测量,最关键的一步就是计算得到 View 的MeasureSpec,子View 在创建时,可以指定不同的LayoutParams(布局参数),LayoutParams的源码主要内容如下所示:

    // frameworks/base/core/java/android/view/ViewGroup.java
    public static class LayoutParams {
        ...
        /**
         * Special value for the height or width requested by a View.
         * MATCH_PARENT means that the view wants to be as big as its parent,
         * minus the parent's padding, if any. Introduced in API Level 8.
         */
        public static final int MATCH_PARENT = -1;
    
        /**
         * Special value for the height or width requested by a View.
         * WRAP_CONTENT means that the view wants to be just large enough to fit
         * its own internal content, taking its own padding into account.
         */
        public static final int WRAP_CONTENT = -2;
    
        /**
         * Information about how wide the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT
         * in API Level 8) or WRAP_CONTENT, or an exact size.
         */
        public int width;
    
        /**
         * Information about how tall the view wants to be. Can be one of the
         * constants FILL_PARENT (replaced by MATCH_PARENT
         * in API Level 8) or WRAP_CONTENT, or an exact size.
         */
        public int height;
        ...
    }
    

    其中:

    • LayoutParams.MATCH_PARENT:表示子View 的尺寸与父容器一样大(注:需要减去父容器padding部分空间,让父容器padding生效)
    • LayoutParams.WRAP_CONTENT:表示子View 的尺寸自适应其内容大小(注:需要包含子View 本身的padding空间)
    • width/height:表示 View 的设置宽/高,即layout_widthlayout_height设置的值,其值有三种选择:LayoutParams.MATCH_PARENTLayoutParams.WRAP_CONTENT具体数值

    LayoutParams会受到父容器的MeasureSpec的影响,测量过程会依据两者之间的相互约束最终生成子View 的MeasureSpec,完成 View 的测量规格。

    简而言之,View 的MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同决定(DecorViewMeasureSpec是由自身的LayoutParams和屏幕尺寸共同决定,参考后文)。也因此,如果要求取子View 的MeasureSpec,那么首先就需要知道父容器的MeasureSpec,层层逆推而上,即最终就是需要知道顶层View(即DecorView)的MeasureSpec,这样才能一层层传递下来,这整个过程需要结合Activity的启动过程进行分析。

    Activity 视图基本结构

    我们知道,在 Android 中,Activity是作为视图组件存在,主要就是在手机上显示视图界面,可以供用户操作,Activity就是 Andorid 中与用户直接交互最多的系统组件。

    Activity的基本视图层次结构如下所示:

    Android - Acvitity 视图架构

    Activity中,实际承载视图的组件是Window(更具体来说为PhoneWindow),顶层View 是DecorView,它是一个FrameLayoutDecorView内部是一个LinearLayout,该LinearLayout由两部分组成(不同 Android 版本或主题稍有差异):TitleViewContentView,其中,TitleView就是标题栏,也就是我们常说的TitleBarActionBarContentView就是内容栏,它也是一个FrameLayout,主要用于承载我们的自定义根布局,即当我们调用setContentView(...)时,其实就是把我们自定义的布局设置到该ContentView中。

    Activity启动完成后,最终就会渲染出上述层次结构的视图。

    DecorView 测量规格

    因此,如果我们要求取得到子View 的MeasureSpec,那么第一步就是求取得到顶层View(即DecorView)的MeasureSpec。大致过程如下所示:

    1. Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:

      // frameworks/base/core/java/android/app/ActivityThread.java
      final void handleResumeActivity(IBinder token,
              boolean clearHide, boolean isForward, boolean reallyResume) {
          ...
          ActivityClientRecord r = performResumeActivity(token, clearHide);
          ...
          // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow
          r.window = r.activity.getWindow();
          // PhoneWindow 绑定的顶层视图:DecorView
          View decor = r.window.getDecorView();
          decor.setVisibility(View.INVISIBLE);
          // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager
          ViewManager wm = a.getWindowManager();
          WindowManager.LayoutParams l = r.window.getAttributes();
          ...
          // 添加 DecorView 到 PhoneWindow 上(相当于设置 Activity 根视图)
          wm.addView(decor, l);
          ...
      }
      

      其中,r.window.getDecorView()实际调用的是PhoneWindow.getDecorView(),其会返回顶层DecorView(不存在时会自动实例化):

      // frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
      public class PhoneWindow extends Window implements MenuBuilder.Callback {
          // This is the top-level view of the window, containing the window decor.
          private DecorView mDecor;
          ...
      
          @Override
          public final View getDecorView() {
              if (mDecor == null) {
                  installDecor();
              }
              return mDecor;
          }
      
          private void installDecor() {
              if (mDecor == null) {
                  mDecor = generateDecor();
                  ...
              }
              ...
          }
      
          protected DecorView generateDecor() {
              // 实例化 DecorView
              return new DecorView(getContext(), -1);
          }
          ...
      }
      

      然后,r.window.getAttributes()实际调用的是Window.getAttributes()

      // frameworks/base/core/java/android/view/Window.java
      public abstract class Window {
          private final WindowManager.LayoutParams mWindowAttributes =
              new WindowManager.LayoutParams();
          ...
      
          public final WindowManager.LayoutParams getAttributes() {
              return mWindowAttributes;
          }
      }
      // frameworks/base/core/java/android/view/WindowManager.java
      public interface WindowManager extends ViewManager {
          ...
          public static class LayoutParams extends ViewGroup.LayoutParams
                  implements Parcelable {
              public LayoutParams() {
                  // DecorView 的布局参数为 MATCH_PARENT
                  super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
                  ...
              }
          }
      }
      

      这里可以看到,此处r.window.getAttributes()返回的是一个WindowManager.LayoutParams实例,对应的最终宽/高布局参数为LayoutParams.MATCH_PARENT,最后通过wm.addView(decor,l)DecorView添加到WindowManager上(最终其实是设置到ViewRootImpl上),所以DecorView的布局参数为MATCH_PARENT

    2. View 的绘制流程真正开始的地方为ViewRootImpl.performTraversals(),在其中,有如下代码片段:

      // frameworks/base/core/java/android/view/ViewRootImpl.java
      private void performTraversals() {
          ...
          int desiredWindowWidth;
          int desiredWindowHeight;
          ...
          // Ask host how big it wants to be
          windowSizeMayChange |= measureHierarchy(host, lp, res,
                  desiredWindowWidth, desiredWindowHeight);
          ...
      }
      
      private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
          final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
          ...
          childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
          childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
          ...
      }
      

      此处的desiredWindowWidthdesiredWindowHeight是屏幕的尺寸,内部最终会调用到ViewRootImpl.getRootMeasureSpec(...),其源码如下所示:

      // frameworks/base/core/java/android/view/ViewRootImpl.java
      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;
      }
      

      ViewRootImpl.getRootMeasureSpec(...)见名知意,其实就是用来获取顶层View(即DecorView)的MeasureSpec,其逻辑如下:

      1. DecorViewLayoutParamsMATCH_PARENT时,说明DecorView的大小与屏幕一样大,而又由于屏幕大小是确定的,因此,其 SpecMode 为EXACTLY,SpecSize 为windowSize,;
      2. DecorViewLayoutParamsWRAP_CONTENT时,说明DecorView自适应内容大小,因此它的大小不确定,但是最大不能超过屏幕大小,故其 SpecMode 为AT_MOST,SpecSize 为windowSize
      3. 其余情况为DecorView设置了具体数值大小或UNSPECIFIED,故以DecorView为主,其 SpecMode 为EXACTLY,SpecSize 就是自己设置的值,即rootDimension

      结合我们上面的分析,由于DecorViewLayoutParamsMATCH_PARENT,因此,DecorViewMeasureSpec最终为:MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY),即DecorView的 SpecMode 为EXACTLY,SpecSize 为屏幕大小。

    默认测量(measure)

    经过上述步骤求取得到 View 的MeasureSpec后,接下来就可以真正对 View 进行测量,求取 View 的最终测量宽/高:

    Android 内部对视图进行测量的过程是由View#measure(int, int)方法负责的,但是对于ViewViewGroup,其具体测量过程有所差异。

    因此,对于测量过程,我们分别对ViewViewGroup进行分析:

    • View测量View的测量过程由View.measure(...)方法负责,其源码如下所示:

      // frameworks/base/core/java/android/view/View.java
      public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
          ...
          // measure ourselves, this should set the measured dimension flag back
          onMeasure(widthMeasureSpec, heightMeasureSpec);
          ...
      }
      

      View#measure(int, int)中参数widthMeasureSpecheightMeasureSpec是由父容器传递进来的,具体的测量过程请参考后文内容。

      需要注意的是,View#measure(int, int)是一个final方法,因此其不可被覆写,实际真正测量 View 自身使用的是View#onMeasure(int, int)方法,如下所示:

      // frameworks/base/core/java/android/view/View.java
      protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                  getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
      }
      

      onMeasure(...)主要做了三件事:

      1. 首先通过getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法获取得到 View 的推荐最小测量宽/高:

        // frameworks/base/core/java/android/view/View.java
        protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
        
        protected int getSuggestedMinimumHeight() {
            return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
        }
        

        这两个方法的实现原理是一致的,这里就只分析getSuggestedMinimumWidth()方法实现,该方法内部是一个三目运算符,可以很清晰看出,当 View 没有设置背景时,它的宽度就为mMinWidthmMinWidth就是android:minWidth这个属性对应设置的值(未设置android:minWidth时,其值默认为0),当 View 设置了背景时,它的宽度就是mMinWidthmBackground.getMinimumWidth()之中的较大值,其中,mBackground.getMinimumWidth()源码如下:

        // frameworks/base/graphics/java/android/graphics/drawable/Drawable.java
        /*
         * @return The minimum width suggested by this Drawable. If this Drawable
         *         doesn't have a suggested minimum width, 0 is returned.
         */
        public int getMinimumWidth() {
            final int intrinsicWidth = getIntrinsicWidth();
            return intrinsicWidth > 0 ? intrinsicWidth : 0;
        }
        
        // 不同子类可实现具体大小
        public int getIntrinsicWidth() {
            return -1;
        }
        

        Drawable.getMinimumWidth()就是返回 Drawable 的原始宽度,如果该 Drawable 未设置宽度,则返回0

        综上,getSuggestedMinimumWidth()/getSuggestedMinimumHeight()其实就是用于获取 View 的最小测量宽/高,其具体逻辑为:当 View 没有设置背景时,其最小宽/高为android:minWidth/android:mMinHeight所指定的值,当 View 设置了背景时,其最小测量宽/高为android:minWidth/android:minHeight与其背景图片宽/高的较大值。

        简而言之,View 的最小测量宽/高为android:minWidth/android:minHeight和其背景宽/高之间的较大值。

      2. 通过getDefaultSize(...)获取到 View 的默认测量宽/高,具体获取过程如下所示:

        // frameworks/base/core/java/android/view/View.java
        public static int getDefaultSize(int size, int measureSpec) {
            int result = size;
            // 测量模式
            int specMode = MeasureSpec.getMode(measureSpec);
            // 测量大小
            int specSize = MeasureSpec.getSize(measureSpec);
        
            switch (specMode) {
            case MeasureSpec.UNSPECIFIED:
                result = size;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            }
            return result;
        }
        

        此处的size是通过getSuggestedMinimumWidth()/getSuggestedMinimumHeight()方法获取得到系统建议 View 的最小测量宽/高。

        参数measureSpec是经由View.measure(...)->View.onMeasure(...)->View.getDefaultSize(...)调用链传递进来的,表示的是当前 View 的MeasureSpec

        getDefaultSize(...)内部首先会获取 View 的测量模式和测量大小,然后当 View 的测量模式为UNSPECIFIED时,也即未限制 View 的大小,因此此时 View 的大小就是其原生大小(也即android:minWidth或背景图片大小),当 View 的测量模式为AT_MOSTEXACTLY时,此时不对这两种模式进行区分,一律将 View 的大小设置为测量大小(即 SpecSize)。
        :实际上,这里可以看到,默认情况下,View 不区分AT_MOSTEXACTLY,也即,当自定义 View 时,LayoutParams.WRAP_CONTENTLayoutParams.MATCH_PARENT效果是一样的,均为MATCH_PARENT的效果,原因是 子View 的MeasureSpec是由父容器传递进来的,父容器是通过ViewGroup#getChildMeasureSpec(...)方法获取得到 子View 的MeasureSpec,在该方法内部,子View 的测量模式无论是AT_MOST或是EXACTLY,其测量大小都为父容器大小(确定的说,是父容器剩余空间大小),因此其效果就等同于MATCH_PARENT,具体源码详情分析请参考后文。

        总之,一般自定义 View 时,都需要覆写onMeasure(...),并为其LayoutParams.WRAP_CONTENT设置一个默认大小,如下所示:

         @Override
         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        
             // 先进行默认测量
             super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        
             // 默认大小依据自己灵活配置,这里为 400px
             int defaultSize = 400;
        
             // 获取默认测量宽/高
             int width = this.getMeasuredWidth();
             int height = this.getMeasuredHeight();
        
             // 获取 View 的布局参数
             ViewGroup.LayoutParams lp = this.getLayoutParams();
        
             // 宽度为自适应,则设置一个默认大小
             if(lp.width == ViewGroup.LayoutParams.WARP_CONTENT) {
                 width = defaultSize;
             }
        
             // 高度为自适应,则设置一个默认大小
             if(lp.height == ViewGroup.LayoutParams.WARP_CONTENT) {
                 height = defaultSize;
             }
        
             this.setMeasuredDimension(width, height);
         }
        
      3. 获取到 View 的测量宽/高后,通过setMeasuredDimension(...)记录 View 的测量宽/高:

        // frameworks/base/core/java/android/view/View.java
        protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
            ...
            setMeasuredDimensionRaw(measuredWidth, measuredHeight);
        }
        
        // 记录测量宽/高
        private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;
        
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
        }
        

        setMeasuredDimension(...)其实就是将 View 的最终测量宽/高设置到View.mMeasuredWidth/View.mMeasuredHeight属性中,完成测量过程。

    • ViewGroup测量ViewGroup是一个抽象类,其继承于View

      public abstract class ViewGroup extends View implements ViewParent, ViewManager {...}
      

      ViewGroup的测量过程也是由View.measure(...)负责,因此实际负责测量的是ViewGroup.onMeasure(...)方法,但是由于ViewGroup的作用是用于容纳子View,如果想测量ViewGroup,则必须先测量其子View,而又由于不同的ViewGroup有不同的布局特性,因此无法抽象出一套标准的测量流程,所以ViewGroup本身没有覆写onMeasure(...)方法(交由具体自定义ViewGroup覆写),但是它提供了一些测量子View 的辅助方法,比如:measureChildren(...)measureChildrenWithMargins(...)measureChild(...)getChildMeasureSpec(...)等等,自定义ViewGroup可借助这些辅助方法,在onMeasure(...)中完成子View 的测量,然后最终才能完成自己的测量。

      我们随便选择一个辅助方法,比如ViewGroup#measureChildWithMargins(...),查看其源码:

      // android/view/ViewGroup.java
      protected void measureChildWithMargins(View child,
              int parentWidthMeasureSpec, int widthUsed,
              int parentHeightMeasureSpec, int heightUsed) {
          // 获取 子View 的 LayoutParams
          final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
      
          // 获取 子View 的 MeasureSpec
          // 父容器已使用的空间为:自身已使用空间 + 自身的 padding + 子View的 margin
          final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                  mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                          + widthUsed, lp.width);
          final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                  mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                          + heightUsed, lp.height);
      
          // 测量子View
          child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
      

      代码非常简洁易懂,其核心就是先获取得到 子View 的MeasureSpecgetChildMeasureSpec(...)),然后就可以对 子View 进行测量(child.measure(...))。

      View#measure(...)的测量详情上述我们已经介绍过了,这里我们主要来看下ViewGroup#getChildMeasureSpec(...)获取 子View 测量规格的具体过程:

      // android/view/ViewGroup.java
      /**
       *
       * @param spec 父容器的 MeasureSpec
       * @param padding 父容器已使用的空间(比如:父View自身的 padding + 子View的 margin)
       * @param childDimension 子View的 LayoutParams
       * @return 子View 的 MeasureSpec
       */
      public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
          // 当前View(即父容器)的测量模式
          int specMode = MeasureSpec.getMode(spec);
          // 父容器的测量大小
          int specSize = MeasureSpec.getSize(spec);
      
          // 父容器剩余可用空间
          int size = Math.max(0, specSize - padding);
      
          // 子View 最终测量大小
          int resultSize = 0;
          // 子View 最终测量模式
          int resultMode = 0;
      
          switch (specMode) {
          // Parent has imposed an exact size on us
          // 父容器大小已确定
          case MeasureSpec.EXACTLY:
              if (childDimension >= 0) { 
              // 子View 设置了具体大小(精确数值)
                  resultSize = childDimension;
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.MATCH_PARENT) { 
              // 子View 大小撑满父容器
                  // Child wants to be our size. So be it.
                  resultSize = size;
                  resultMode = MeasureSpec.EXACTLY;
              } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              // 子View 自适应内容大小
                  // Child wants to determine its own size. It can't be
                  // bigger than us.
                  resultSize = size;
                  resultMode = MeasureSpec.AT_MOST;
              }
              break;
      
          // Parent has imposed a maximum size on us
          // 父容器自适应内容大小
          case MeasureSpec.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:
              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;
          }
          // 子View 的最终测量规格
          return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
      }
      

      getChildMeasureSpec(...)其实就是ViewGroup对其内部 子View 的默认测量过程,其核心逻辑为:

      1. 如果父容器的测量模式为EXACTLY:即父容器测量大小是确切的,且其剩余空间精确为size,此时:

        • 如果 子View 的LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY
        • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器是EXACTLY,即大小已知,因此,子View 也是大小已知,故其测量模式为EXACTLY,且其测量大小就是父容器剩余空间大小,具体为size
        • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST,测量大小为父容器剩余空间size
      2. 如果父容器的测量模式为AT_MOST:即父容器自适应其内容大小,也即父容器大小不确定,此时:

        • 如果 子View 的LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY
        • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器是AT_MOST,即大小未知,因此,子View 也是大小未知,即其测量模式为AT_MOST,且其测量大小不超过父容器剩余空间大小size
        • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自适应内容大小,但是其尺寸最大不能超过父容器剩余空间,因此其测量模式为AT_MOST,测量大小为父容器剩余空间size
      3. 如果父容器的测量模式为UNSPECIFIED:即父容器大小无限制,此时:

        • 如果 子View 的LayoutParams为具体数值:则表示 子View 已明确设置了具体大小,因此,此时 子View 的测量大小即为自己设置的值,即childDimension,测量模式为EXACTLY
        • 如果 子View 的LayoutParamsMATCH_PARENT:表示 子View 的大小撑满父容器,由于父容器大小无限制,因此,子View 的大小也是无限制的,所以,子View 的测量模式为UNSPECIFIED,测量大小未知,通常设置为0,表示无限。
        • 如果 子View 的LayoutParamsWRAP_CONTENT:表示 子View 自适应内容大小,由于父容器大小无限制,因此,子View 的测量大小也是无限制的,所以其模式为UNSPECIFIED,测量大小无限,通常使用0进行表示。

      上述的逻辑总结如下图所示:(:图片来源于互联网,侵删)

      ViewGroup#getChildMeasureSpec

      :前面我们一直强调:子View 的MeasureSpec是由其LayoutParams和父容器的MeasureSpec共同约束构造而成,其实这部分逻辑就是ViewGroup#getChildMeasureSpec(...)方法负责的,可以很清晰看到,子View 的MeasureSpec就是在父容器MeasureSpec约束下,与其自身LayoutParams共同协商决定的。

    综上,无论是对View的测量还是ViewGroup的测量,都是由View#measure(int widthMeasureSpec, int heightMeasureSpec)方法负责,然后真正执行 View 测量的是 View 的onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法。

    具体来说,View直接在onMeasure(...)中测量并设置自己的最终测量宽/高。在默认测量情况下,View的测量宽/高由其父容器的MeasureSpec和自身的LayoutParams共同决定,当View自身的测量模式为LayoutParams.UNSPECIFIED时,其测量宽/高为android:minWidth/android:minHeight和其背景宽/高之间的较大值,其余情况皆为自身MeasureSpec指定的测量尺寸。

    而对于ViewGroup来说,由于布局特性的丰富性,只能自己手动覆写onMeasure(...)方法,实现自定义测量过程,但是总的思想都是先测量 子View 大小,最终才能确定自己的测量大小。

    layout 流程

    当确定了 View 的测量大小后,接下来就可以来确定 View 的布局位置了,也即将 View 放置到屏幕具体哪个位置。

    View layout

    View 的布局过程由View#layout(...)负责,其源码如下:

    // android/view/View.java
    /**
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        ...
        setFrame(l, t, r, b);
        ...
        onLayout(changed, l, t, r, b);
        ...
    }
    

    View#layout(...)主要就做了两件事:

    1. setFrame(...):首先通过View#setFrame(...)来确定自己的布局位置,其源码如下:

      // android/view/View.java
      protected boolean setFrame(int left, int top, int right, int bottom) {
          ...
          // Invalidate our old position
          invalidate(sizeChanged);
      
          mLeft = left;
          mTop = top;
          mRight = right;
          mBottom = bottom;
      }
      

      setFrame(...)其实就是更新记录 View 的四个顶点位置,这样 View 在父容器中的坐标位置就确定了。

    2. onLayout(...)setFrame(...)是用于确定 View 自身的布局位置,而onLayout(...)主要用于确定 子View 的布局位置:

      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      }
      

      由于 View 不包含子组件,因此其onLayout是一个空实现。

    ViewGroup layout

    ViewGroup 的布局流程由ViewGroup#layout(...)负责,其源码如下:

    // android/view/ViewGroup.java
    @UiThread
    public abstract class ViewGroup extends View implements ViewParent, ViewManager {
        ...
        @Override
        public final void layout(int l, int t, int r, int b) {
            ...
            super.layout(l, t, r, b);
            ...
        }
    

    可以看到,ViewGroup#layout(...)最终也是通过View#layout(...)完成自身的布局过程,一个注意的点是,ViewGroup#layout(...)是一个final方法,因此子类无法覆写该方法,主要是ViewGroup#layout(...)方法内部对子视图动画效果进行了相关设置。

    由于ViewGroup#layout(...)内部最终调用的还是View#layout(...),因此,ViewGroup#onLayout(...)就会得到回调,用于处理 子View 的布局放置,其源码如下:

    // android/view/ViewGroup.java
    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
    

    由于不同的ViewGroup,其布局特性不同,因此ViewGroup#onLayout(...)是一个抽象方法,交由ViewGroup子类依据自己的布局特性,摆放其 子View 的位置。

    draw 流程

    当 View 的测量大小,布局位置都确定后,就可以最终将该 View 绘制到屏幕上了。

    View 的绘制过程由View#draw(...)方法负责,其源码如下:

    // android/view/View.java
    public void draw(Canvas canvas) {
        ...
        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */
    
        // Step 1, draw the background, if needed
        drawBackground(canvas);
    
        // skip step 2 & 5 if possible (common case)
        ...
        // Step 2, save the canvas' layers
        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }
    
        if (drawBottom) {
            canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
        }
    
        if (drawLeft) {
            canvas.saveLayer(left, top, left + length, bottom, null, flags);
        }
    
        if (drawRight) {
            canvas.saveLayer(right - length, top, right, bottom, null, flags);
        }
        ...
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
    
        // Step 4, draw the children
        dispatchDraw(canvas);
    
        // Step 5, draw the fade effect and restore layers
        ...
        if (drawTop) {
            ...
            canvas.drawRect(left, top, right, top + length, p);
        }
    
        if (drawBottom) {
            ...
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }
    
        if (drawLeft) {
            ...
            canvas.drawRect(left, top, left + length, bottom, p);
        }
    
        if (drawRight) {
            ...
            canvas.drawRect(right - length, top, right, bottom, p);
        }
        ...
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    }
    

    其实注释已经写的很清楚了,View#draw(...)主要做了以下 6 件事:

    1. 绘制背景:drawBackground(...)

    2. 如果有必要的话,保存画布图层:Canvas.saveLayer(...)

    3. 绘制自己onDraw(...),其源码如下:

      // android/view/View.java
      protected void onDraw(Canvas canvas) {
      }
      

      View#onDraw(...)是一个空方法,因为每个 View 的绘制都是不同的,自定义 View 时,通常会覆写该方法,手动绘制该 View 内容。

    4. 绘制子ViewdispatchDraw(...),其源码如下:

      // android/view/View.java
      protected void dispatchDraw(Canvas canvas) {
      }
      

      由于 View 没有子元素,因此其dispatchDraw是一个空实现。

      查看下ViewGroup#dispatchDraw(...),其源码如下:

      // android/view/ViewGroup.java
      @Override
      protected void dispatchDraw(Canvas canvas) {
          ...
          final int childrenCount = mChildrenCount;
          final View[] children = mChildren;
          ...
          for (int i = 0; i < childrenCount; i++) {
              ...
              more |= drawChild(canvas, child, drawingTime);
              ...
          }
          ...
      }
      
      protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
          return child.draw(canvas, this, drawingTime);
      }
      

      可以看到,其内部主要就是遍历子View,最后通过child.draw(...)让子View自己进行绘制。

    5. 如果有必要的话,绘制淡化效果并恢复图层:Canvas.drawRect(...)

    6. 绘制装饰:onDrawForeground(...),其源码如下:

      // android/view/View.java
      public void onDrawForeground(Canvas canvas) {
          onDrawScrollIndicators(canvas);
          onDrawScrollBars(canvas);
      
          final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
          ...
          foreground.draw(canvas);
          }
      }
      

      其实主要就是绘制滚动条,前景图片等视图相关的装饰。

    绘制起始流程

    我们知道,在Activity启动过程中,会调用到ActivityThread.handleResumeActivity(...),该方法就是 View 视图绘制的起始之处:

    // frameworks/base/core/java/android/app/ActivityThread.java
    final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        ...
        // 回调 Activity.onResume() 方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        ...
        // 获取当前 Activity 实例
        final Activity a = r.activity;
        ...
        // 此处的 window 为与 Activity 绑定的 PhoneWindow,即 Activity.mWindow
        r.window = r.activity.getWindow();
        // PhoneWindow 绑定的顶层视图:DecorView
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        // 获取与 Activity 绑定的 WindowManager,实际上是 PhoneWindow 的 WindowManager
        ViewManager wm = a.getWindowManager();
        WindowManager.LayoutParams l = r.window.getAttributes();
        ...
        // 添加 DecorView 到 PhoneWindow 上(相当于设置 Activity 根视图)
        wm.addView(decor, l);
        ...
    }
    

    可以看到,ActivityThread.handleResumeActivity(...)主要就是获取到当前Activity绑定的ViewManager,最后调用ViewManager.addView(...)方法将DecorView设置到PhoneWindow上,也即设置到当前Activity上。ViewManager是一个接口,WindowManager继承ViewManager,而WindowManagerImpl实现了接口WindowManager,此处的ViewManager.addView(...)实际上调用的是WindowManagerImpl.addView(...),源码如下所示:

    // frameworks/base/core/java/android/view/WindowManagerImpl.java
    public final class WindowManagerImpl implements WindowManager {
        private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
        ...
        @Override
        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mDisplay, mParentWindow);
        }
        ...
    }
    

    WindowManagerImpl.addView(...)内部转发到WindowManagerGlobal.addView(...)

    // frameworks/base/core/java/android/view/WindowManagerGlobal.java
    public final class WindowManagerGlobal {
        ...
        public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            ...
            ViewRootImpl root;
            ...
            // 实例化一个 ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);
            ...
            // 将 ViewRootImpl 与 DecorView 关联到一起
            root.setView(view, wparams, panelParentView);
            ...
        }
        ...
    }
    

    WindowManagerGlobal.addView(...)内部,会创建一个ViewRootImpl实例,然后调用ViewRootImpl.setView(...)ViewRootImplDecorView关联到一起:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
        ...
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            ...
            // 将 DecorView 绑定到 ViewRootImpl.mView 属性上
            mView = view;
            ...
            mWindowAttributes.copyFrom(attrs);
            ...
            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            requestLayout();
            ...
        }
        ...
        @Override
        public void requestLayout() {
            if (!mHandlingLayoutInLayoutRequest) {
                // 检查是否处于主线程
                checkThread();
                ...
                scheduleTraversals();
            }
        }
        ...
    }
    

    ViewRootImpl.setView(...)内部首先关联了传递过来的DecorView(通过属性mView指向DecorView即可建立关联),然后最终调用requestLayout(),而requestLayout()内部又会调用方法scheduleTraversals()

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
        ...
        Choreographer mChoreographer;
        ...
        final class TraversalRunnable implements Runnable {
            @Override
            public void run() {
                // 开始执行绘制
                doTraversal();
            }
        }
        final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
        ...
        void scheduleTraversals() {
            if (!mTraversalScheduled) { // 同一帧内不会多次调用遍历
                mTraversalScheduled = true;
                // 发送一个同步屏障
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                // 将 UI 绘制任务发送到 Choreographer,回调触发 mTraversalRunnable,执行绘制操作
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                ...
            }
        }
        ...
        void doTraversal() {
            ...
            performTraversals();
            ...
        }
        ...
    }
    

    ViewRootImpl.scheduleTraversals()内部主要做了两件事:

    1. 调用MessageQueue.postSyncBarrier()方法发送一个同步屏障,同步屏障可以拦截Looper对同步消息的获取与分发,即加入同步屏障后,此时Looper只会获取和处理异步消息,如果没有异步消息,则进入阻塞状态。
    2. 通过Choreographer.postCallback(...)发送一个Choreographer.CALLBACK_TRAVERSAL的异步视图渲染消息。因为前面已经发送了一个同步屏障,因此此处的视图绘制渲染消息会优先被处理。

    Choreographer.postCallback(...)会申请一次 VSYNC 中断信号,当 VSYNC 信号到达时,便会回调Choreographer.doFrame(...)方法,内部会触发已经添加的回调任务,Choreographer的回调任务有以下四种类型:

    // 回调 INPUT 任务
    doCallbacks(Choreographer.CALLBACK_INPUT, mframeTimeNanos);
    // 回调 ANIMATION
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    // 回调 View 绘制任务 TRAVERSAL
    doCallbacks(Choreographer,CALLBACK_TRAVERSAL, frameTimeNanos);
    // API Level 23 新增,COMMIT 
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    

    因此,ViewRootImpl.scheduleTraversals(...)内部通过mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)发送的异步视图渲染消息就会得到回调,即回调mTraversalRunnable.run()方法,最终会执行doTraversal()方法,而doTraversal()内部又会调用performTraversals()方法,该方法才是真正开始执行 View 绘制流程的地方,其源码如下所示:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    public final class ViewRootImpl implements ViewParent,
            View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
        ...
        private void performTraversals() {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            ...
            // Ask host how big it wants to be
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            ...
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
            performDraw();
            ...
        }
        ...
    }
    

    综上,performTraversals()会依次调用performMeasure(...)performLayout(...)performDraw()三个方法,这三个方法会依次完成顶层View(即DecorView)的测量(measure)、布局(layout)和绘制(draw)流程,具体详情请参考后文。

    到此,我们才真正进入 View 绘制流程,总结一下上述流程,如下图所示:

    View 绘制起始流程

    performMeasure

    书接前文,我们知道,真正开始 View 绘制流程是ViewRootImpl.performTraversals(),该方法内部首先进行的是performMeasure(...)流程:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            // 调用 DecorView.measure(...) 
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
    

    此处的mView其实就是DecorView,其赋值指向在ViewRootImpl.setView(...)中进行,可以看到,performMeasure(...)实际调用的是DecorView.measure(...),所以最终会回调DecorView#onMeasure(...)方法,其源码如下:

    // frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        ...
        private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
            ...
            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                ...
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                ...
            }
        ...
    }
    

    可以看到,DecorView#onMeasure(...)内部将测量过程交由其父类,即FrameLayout进行处理,那我们看下FrameLayout#onMeasure(...)源码:

    // frameworks/base/core/java/android/widget/FrameLayout.java
    public class FrameLayout extends ViewGroup {
        ...
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 获取 子View 数量 
            int count = getChildCount();
            ...
            // 最大高度
            int maxHeight = 0;
            // 最大宽度
            int maxWidth = 0;
            int childState = 0;
    
            for (int i = 0; i < count; i++) {
                // 获取 子View
                final View child = getChildAt(i);
                // 只对可见的 子View 进行测量
                if (mMeasureAllChildren || child.getVisibility() != GONE) {
                    // 测量子View
                    measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                    // 获取 子View 的布局参数
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    // 获取当前子View的宽度,包含其外边距,记录子View的最大宽度
                    maxWidth = Math.max(maxWidth,
                            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                    // 记录子View的最大高度
                    maxHeight = Math.max(maxHeight,
                            child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
                    ...
                }
            }
    
            // Account for padding too
            // 最大宽度包含前景偏移量:padding
            maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
            // 最大高度包含前景偏移量:padding
            maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
            // Check against our minimum height and width
            // 比较子View 和 系统建议的 子View 最小高度,获取两者中的较大值
            maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
            // 比较子View 和 系统建议的 子View 最小宽度,获取两者中的较大值
            maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
            // Check against our foreground's minimum height and width
            final Drawable drawable = getForeground();
            if (drawable != null) {
                // 子View 高度和 前景图片高度比较,记录其中较大值
                maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
                // 子View 高度和 前景图片宽度比较,记录其中较大值
                maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
            }
    
            // 记录测量结果
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT));
    
            ...
        }
        ...
    }
    

    FrameLayout的布局特性为:所有 子View 层叠在一起,所以FrameLayout的测量宽/高就是其所有 子View 中最大的宽和高,因此FrameLayout#onMeasure(...)的核心逻辑就是遍历其所有子View,然后通过measureChildWithMargins(...)(该方法前面内容已详细介绍)测量子View,然后就可以获取 子View 的宽/高,记录其中最大的宽/高值,作为自己的测量宽/高。

    经过以上步骤,DecorView的测量就已经完成了。

    综上,ViewRootImpl#performMeasure(...)其实就是对DecorView的测量过程(DecorView#measure(...)),DecorView是一个FrameLayout,其测量过程主要由FrameLayout#onMeasure(...)负责,内部主要测量逻辑是先遍历所有子View,让 子View 先自己进行测量(child.measure(...)),然后就可以获取 子View 的测量大小,记录所有 子View 中占比最大的测量宽/高,作为自己的最终测量大小。

    performLayout

    ViewRootImpl#performMeasure(...)完成对DecorView的测量后,接下来执行的是ViewRootImpl#performLayout(...),其源码如下:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ...
        // cache mView since it is used so much below...
        final View host = mView;
        ...
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        ...
    }
    

    其中,参数lpwidthheight均为MATCH_PARENTdesiredWindowWidthdesiredWindowHeight为屏幕宽/高,mViewDecorView

    所以,performLayout(...)内部其实就是调用DecorView#layout(...),前面 layout 流程中介绍过,ViewGroup#layout(...)内部最终会通过View#layout(...)进行布局,而View#layout(...)内部最终通过View#setFrame(...)方法记录四个顶点位置,这样DecorView自己的布局位置就已确定了,即host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight())

    确定了DecorView自身的布局位置后,接下来就是要布局其 子View 了,因此,这里最终回调的是DecorView#onLayout(...)方法,其源码如下所示:

    // frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        ...
        private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
            ...
            @Override
            protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
                super.onLayout(changed, left, top, right, bottom);
                ...
            }
        ...
    }
    

    DecorView#onLayout(...)内部转交给FrameLayout#onLayout(...)进行 子View 布局操作,其源码如下:

    // frameworks/base/core/java/android/widget/FrameLayout.java
    public class FrameLayout extends ViewGroup {
        ...
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            // 布局子View
            layoutChildren(left, top, right, bottom, false /* no force left gravity */);
        }
    
        void layoutChildren(int left, int top, int right, int bottom,
                                      boolean forceLeftGravity) {
            // 获取 子View 数量
            final int count = getChildCount();
    
            // 左边可放置起始点坐标
            final int parentLeft = getPaddingLeftWithForeground();
            // 右边可放置终点坐标
            final int parentRight = right - left - getPaddingRightWithForeground();
    
            // 顶部可放置起始点坐标
            final int parentTop = getPaddingTopWithForeground();
            // 底部可放置终点坐标
            final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    
            // 遍历 子View
            for (int i = 0; i < count; i++) {
                // 获取 子View
                final View child = getChildAt(i);
                // 不放置状态为 GONE 的子View
                if (child.getVisibility() != GONE) {
                    // 获取 子View 布局参数
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                    // 获取 子View 测量宽/高
                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();
    
                    // 当前 子View 的布局左边界
                    int childLeft;
                    // 当前 子View 的布局右边界
                    int childTop;
                    ...
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
        }
        ...
    }
    

    FrameLayout#onLayout(...)内部是通过FrameLayout#layoutChildren(...)进行 子View 的布局操作,其主要逻辑就是遍历所有 子View,计算得到 子View 的四个顶点位置坐标,最后将结果传递给child.layout(...),让 子View 记录自己在父容器中的布局位置,完成 子View 的布局过程。

    综上,ViewRootImpl#performLayout(...)就是对DecorView的布局过程,此过程会递归计算各个 子View 的布局位置,调用 子View 的布局方法,完成各个 子View 的布局。

    performDraw

    完成了performMeasure(...)performLayout(...)后,最后一步就是performDraw(...)过程,其源码如下:

    // frameworks/base/core/java/android/view/ViewRootImpl.java
    private void performDraw() {
        ...
        draw(fullRedrawNeeded);
        ...
    }
    
    private void draw(boolean fullRedrawNeeded) {
        ...
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
            return;
        }
        ...
    }
    
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        ...
        mView.draw(canvas);
        ...
    }
    

    可以看到,ViewRootImpl#performDraw()内部会经由ViewRootImpl#draw(...)ViewRootImpl#drawSoftware(...),最终执行的还是DecorView#draw(...)过程,其源码如下:

    // frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java
    public class PhoneWindow extends Window implements MenuBuilder.Callback {
        ...
        private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
            @Override
            public void draw(Canvas canvas) {
                super.draw(canvas);
    
                if (mMenuBackground != null) {
                    mMenuBackground.draw(canvas);
                }
            }
        ...
    }
    

    由于FrameLayout没有覆写draw(...)方法,因此,super.draw(...)最终调用的是View#draw(...)方法,所以DecorView默认采用的就是 View 的绘制方法,具体绘制详情上文已介绍过了,主要就是对DecorView的背景、内容、子View、滚动条等装饰视图进行绘制。

    至此,View 绘制的整个流程已基本介绍完毕。

    总结

    View 的绘制主要有以下一些核心内容:

    1. 三大流程:View 绘制主要包含如下三大流程:

      • measure:测量流程,主要负责对 View 进行测量,其核心逻辑位于View#measure(...),真正的测量处理由View#onMeasure(...)负责。默认的测量规则为:如果 View 的布局参数为LayoutParams.WRAP_CONTENTLayoutParams.MATCH_PARENT,那么其测量大小为 SpecSize;如果其布局参数为LayoutParams.UNSPECIFIED,那么其测量大小为android:minWidth/android:minHeight和其背景之间的较大值。

      自定义View 通常覆写onMeasure(...)方法,在其内一般会对WRAP_CONTENT预设一个默认值,区分WARP_CONTENTMATCH_PARENT效果,最终完成自己的测量宽/高。而ViewGrouponMeasure(...)方法中,通常都是先测量子View,收集到相应数据后,才能最终测量自己。

      • layout:布局流程,主要完成对 View 的位置放置,其核心逻辑位于View#layout(...),该方法内部主要通过View#setFrame(...)记录自己的四个顶点坐标(记录与对应成员变量中即可),完成自己的位置放置,最后会回调View#onLayout(...)方法,在其内完成对 子View 的布局放置。

        :不同于 measure 流程首先对 子View 进行测量,最后才测量自己,layout 流程首先是先定位自己的布局位置,然后才处理放置 子View 的布局位置。

      • draw:绘制流程,就是将 View 绘制到屏幕上,其核心逻辑位于View#draw(...),主要就是对 背景自身内容(onDraw(...)子View(dispatchDraw(...)装饰(滚动条、前景等) 进行绘制。

        :通常自定义View 覆写onDraw(...)方法,完成自己的绘制即可,ViewGroup 一般充当容器使用,因此通常无需覆写onDraw(...)

    2. Activity 的根视图(即DecorView)最终是绑定到ViewRootImpl,具体是由ViewRootImpl#setView(...)进行绑定关联的,后续 View 绘制的三大流程都是均有ViewRootImpl负责执行的。

    3. 对 View 的测量流程中,最关键的一步是求取 View 的MeasureSpec,View 的MeasureSpec是在其父容器MeasureSpec的约束下,结合自己的LayoutParams共同测量得到的,具体的测量逻辑由ViewGroup#getChildMeasureSpec(...)负责。
      DecorViewMeasureSpec取决于自己的LayoutParams和屏幕尺寸,具体的测量逻辑位于ViewRootImpl#getRootMeasureSpec(...)

    最后,稍微总结一下 View 绘制的整个流程:

    1. 首先,当 Activity 启动时,会触发调用到ActivityThread#handleResumeActivity(..),其内部会经历一系列过程,生成DecorViewViewRootImpl等实例,最后通过ViewRootImpl#setView(decor,MATCH_PARENT)设置 Activity 根View。

      ViewRootImpl#setView(...)内容通过将其成员属性ViewRootImpl#mView指向DecorView,完成两者之间的关联。

    2. ViewRootImpl成功关联DecorView后,其内部会设置同步屏障并发送一个CALLBACK_TRAVERSAL异步渲染消息,在下一次 VSYNC 信号到来时,CALLBACK_TRAVERSAL就会得到响应,从而最终触发执行ViewRootImpl#performTraversals(...),真正开始执行 View 绘制流程。

    3. ViewRootImpl#performTraversals(...)内部会依次调用ViewRootImpl#performMeasure(...)ViewRootImpl#performLayout(...)ViewRootImpl#performDraw(...)三大绘制流程,其中:

      • performMeasure(..):内部主要就是对DecorView执行测量流程:DecorView#measure(...)DecorView是一个FrameLayout,其布局特性是层叠布局,所占的空间就是其 子View 占比最大的宽/高,因此其测量逻辑(onMeasure(...))是先对所有 子View 进行测量,具体是通过ViewGroup#measureChildWithMargins(...)方法对 子View 进行测量,子View 测量完成后,记录最大的宽/高,设置为自己的测量大小(通过View#setMeasuredDimension(...)),如此便完成了DecorView的测量流程。

      • performLayout(...):内部其实就是调用DecorView#layout(...),如此便完成了DecorView的布局位置,最后会回调DecorView#onLayout(...),负责 子View 的布局放置,核心逻辑就是计算出各个 子View 的坐标位置,最后通过child.layout(...)完成 子View 布局。

      • performDraw():内部最终调用到的是DecorView#draw(...),该方法内部并未对绘制流程做任何修改,因此最终执行的是View#draw(...),所以主要就是依次完成对DecorView背景子View(dispatchDraw(...)视图装饰(滚动条、前景等) 的绘制。

    参考

    相关文章

      网友评论

        本文标题:Android - View 绘制流程

        本文链接:https://www.haomeiwen.com/subject/jsicdltx.html