View

绘制View有三个组件:

  1. Measure:测量视图的高低

  2. Layout:先通过measure测量出ViewGroup宽高,ViewGroup再通过layout方法根据自身宽高来确定自身位置

  3. Draw:绘制视图

1、View绘制前的流程

入门一段时间的同学都会知道,View绘制流程就是Measure() -> Laytou() -> Draw(),这三大方法。没错,不过在进行这三个方法之前,系统又做了什么准备工作呢?这就是本小节解决的问题。以下的图,是贯穿全文的总结图,放在文章开头,我们一步一步分析。

alt

以下内容与本系列《Activity、Window、DecorView以及ViewRoot层级关系》(以下简称为《参考文档1》)一文内容重复较多,特别是源码分析,故具体源码请看参考文档1。从Activity创建到View的绘制,经过以下步骤:

  • Step1:初始化PhoneWindow和WindowManager

之前一节说过,Activity 是在 ActivityThread 的 performLaunchActivity 中进行创建的,并在attach()方法中创建了PhoneWindow,并初始化WindowManager。attach()方法详情见参考文档1​的第2小节。

  • Step2:初始化DecorView

系统执行完attach()方法后,会执行onCreate()方法,在onCreate()方法中执行setContentView()方法将布局xml文件解析为View并设置到DecorView里面的ContentViews控件。这里的源码也对应参考文档1​的第2小节。

  • Step3:ViewRootImpl创建和关联DecorView

接着在handleResumeActivity()方法中,通过WindowManager的addView()方法将DecorView添加到WindoManager里面。通过源码分析,WindowManager的addView()方法最终通过WindowManagerGlobal的实例去addView(),在 WindowManagerGlobal()方法中,会创建一个ViewRootImpl,也就是最后会把DecorView传给了ViewRootImpl中的setView()方法。ViewRootImpl是DecorView的管理者,是WindowManager与DecorView之间的连接器。源码详情见参考文档1​的第4小节。

  • Step4:建立 PhoneWindow 和 WindowManagerService 之间的连接

WindowManagerService是所有Window窗口的管理者,负责Window的添加删除、事件派发等。每一个Activity都有一个PhoneWindow对象,操作PhoneWindow对象进行显示等操作,都需要和WindowManagerService进行交互。在上面第三步中的ViewRootImpl的setView()方法中,会执行setView()方法,会调用 requestLayout()方法,并且通过 WindowSession 的 addToDisplay()方法 与 WindowManagerService 进行交互。WindowManagerService 会为每一个 Window 关联一个 WindowStatus。到此,我们已经把DecorView加载到Window中了。

  • Step5:建立与SurfaceFlinger的链接

SurfaceFlinger主要对图层数据进行合成,然后发送到屏幕渲染。在第4步中,WindowStatus会创建一个SurfaceSession,SurfaceSession 会在 Native 层构造一个 SurfaceComposerClient 对象,它是应用程序与 SurfaceFlinger 沟通的桥梁。

  • Step6:申请Surface

View绘制会从ViewRoot的performTraversals(),按照Measure() -> Layout() -> Draw()经典流程完成View绘制。不过在此之前还会执行一个重要的函数relayoutWindow()。代码如下:

//frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals() {
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); //1
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    //执行测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //2
    ...
    //执行布局流程
    performLayout(lp, desiredWindowWidth, desiredWindowHeight); //3
    ...
    //执行绘制流程
    performDraw(); //4
}

调用[注释1]​的relayoutWindow()方法,通过 WindowSession与WindowManagerService交互,即把Java层的Surface和Native层的Surface关联在一起。

  • Step7:正式进入View绘制

接下来就是正式绘制View的整体流程,即[注释2-4]​三步走。绘制会根视图ViewRoot的performTraversals()方法开始,从上到下遍历整个视图树,每个ViewGroup会负责通知自己的子View进行绘制,而每个子View控件则负责绘制自己。大致的工作流程图如下:

alt

从上面的图,可以看到有onMeasure(),onLayout(),onDraw()这三个函数,是我们在实现自定义View最常接触的三个函数,接下来以这三个函数为思路进行讲解。

2、View绘制 - Measure(测量)

2.1 Measure源码流程

Measure翻译过来即是“测量”的意思,在此测量的是每个控件的宽和高。在代码层面,则是给每个View的mMeasuredWidth和mMeasuredHeight变量进行赋值。在测量时遵循:

  • 如果是ViewGroup,则遍历测量子View的宽高,再根据子View宽高算出自身的宽高;
  • 如果是子View,则直接测量出自身宽高;

现在从[注释2]​的performMeasure()方法开始:

//frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); //5
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

逻辑很清晰,可发现实际起作用的是[注释5]​mView.measure()方法,

//frameworks/base/core/java/android/view/View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    if (forceLayout || needsLayout) {
       if (cacheIndex < 0 || sIgnoreMeasureCache) {
            onMeasure(widthMeasureSpec, heightMeasureSpec); //6
        } else {
            setMeasuredDimensionRaw((int) (value >> 32), (int) value);
        }
    }
    ...
}

measure()方法使用final修饰,代表不可重写。在measure()方法中会进行一系列逻辑处理后,调用[注释6]​的onMeasure()方法,真正的测量都在onMeasure()方法中实现。

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

可以看到onMeasure()方法使用protected修饰,代表我们可以重写该方法。因此如果需要实现自己的测量逻辑,只能通过子View重写onMeasure()方法,而不能重写measure()方法。onMeasure()最后调用[注释7]​setMeasuredDimension()设置View的宽高信息,完成View的测量操作。

看看getDefaultSize()的源码:

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    //通过MeasureSpec解析获取mode与size
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST: //8
    case MeasureSpec.EXACTLY: //8
        result = specSize;
        break;
    }
    return result;
}

这是系统设置默认的尺寸,在[注释8]​可以看到如果specMode是AT_MOST或者EXACTLY,则返回的就是specSize。至于 UNSPECIFIED 的情况,则会返回一个建议的最小值,这个值和子元素设置的最小值它的背景大小有关(这一段话可先看看2.2小节再回来继续看)。

从一开始执行的performMeasure()到最后设置宽高的setMeasuredDimension()方法,流程都比较清晰。并且可以发现有两个贯穿整个流程的变量,widthMeasureSpec和heightMeasureSpec,理解这两个变量才是关键。

2.2 什么是MeasureSpec

MeasureSpec是一个32位的int型数据,由两部分组成,SpecMode(测量模式,高2位) + SpecSize(测量尺寸,低30位)。将这两者打包为一个int数据可以起到节省内存的作用。有打包当然也有解包的方法:

//获取测量模式
public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}
//获取测量尺寸
public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}

名词解析:控件的布局参数LayoutParams​是指控件设定为match_parent或者wrap_content或具体数值之中的一种。

2.2.1 测量模式

  • EXACTLY:确定大小,父View希望子View的大小是确定的。对应LayoutParams中的match_parent和具体数值这两种模式。检测到View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值;
  • AT_MOST :最大大小,父View希望子View的大小最多是specSize指定的值。对应LayoutParams中的wrap_content。View的大小不能大于父容器的大小。
  • UNSPECIFIED :不确定大小,父View完全依据子View的设计值来决定。系统不对View进行任何限制,要多大给多大,一般用于系统内部。

具体详见2.2.2小节的图。

2.2.2 MeasureSpec如何确定

  • DecorView:通过屏幕大小和自身布局参数LayoutParams,只要将自身大小和屏幕大小相比,设置一个不超过屏幕大小的宽高和对应测量模式即可;
  • ViewGroup和View:需要通过父布局的MeasureSpec和自身的布局参数LayoutParams确定,具体如下:

alt

2.3 ViewGroup的测量

上面说过ViewGroup需要测量其包含的子View的宽高后,根据子View宽高算出自身的宽高。所以在ViewGroup中定义了measureChildren(), measureChild(), measureChildWithMargins()方法来对子视图进行测量,measureChildren()内部实质只是循环调用measureChild()。

//frameworks/base/core/java/android/view/ViewGroup.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec); //调用measureChild()对子View进行测量
        }
    }
}

protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    //获取子视图的LayoutParams
    final LayoutParams lp = child.getLayoutParams();
  
    //通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    //继续调用子View的measure()方法,子View的measure()中会回调子View的onMeasure()方法,并最终调用到setMeasuredDimension()来设置宽高
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

以上代码注释已经写的比较清楚了,关键看看getChildMeasureSpec()方法:

//frameworks/base/core/java/android/view/ViewGroup.java
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    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:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } 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 has imposed a maximum size on us
    case MeasureSpec.AT_MOST: //a
        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; //b
        } 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; //c
        }
        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 = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = 0;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

代码逻辑也非常清楚,首先根据父控件的specMode模式,结合子控件的布局参数LayoutParams,最后得到子控件的MeasureSpec属性。有一点需要注意,如果父控件的MeasureSpec是AT_MOST,对应[注释a]​,无论子控件的布局参数是WRAP_CONTENT还是MATCH_PARENT,最终获得的子控件的specMode模式都是AT_MOST,即[注释b-c]​。因此,一般的解决方法是当布局参数是WRAP_CONTENT时,在onMeasure()方法里手动指定一下默认的宽和高:

@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
      int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
      int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
      int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
      if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(400,400);
      }else if(widthSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(400,heightSpecSize);
      }else if(heightSpecMode==MeasureSpec.AT_MOST){
          setMeasuredDimension(widthSpecSize,400);
      }
  }

2.4 Measure总结

  • measure过程主要就是从顶层父View向子View递归调用view.measure方法进行测量(measure()中又回调onMeasure()方法)的过程;
  • 如果是ViewGroup则需执行要measure()并重写onMeasure()方法,在该方法中定义自己的测量方式,接着调用maesureChildren()方法遍历测量子View的宽高,最终根据子View宽高确定自己的宽高;
  • ViewGroup类提供了measureChild(),measureChildren()和measureChildWithMargins()方法,简化了父子View的尺寸计算;
  • 如果是子View则调用measure() -》 onMeasure()方法完成自身的测量即可;
  • View的measure()方法是final修饰的,不能重写,只能重写onMeasure()方法完成自己的测量,且重写时不建议把宽高设置为死值;
  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

流程图(摘抄于参考文档2)如下: alt

3、View绘制 - Layout(布局)

3.1 Layout源码流程

将所有的View的宽高都计算好之后,就开始将所有的View进行布局了,即在Window摆放好所有View的位置,View的布局主要是通过确定上下左右四个关键点来确定其位置。值得一说的是,测量时,先测量子View的宽高,再测量父View的宽高。但是在布局时顺序则相反,是父View先确定自身的布局,再确认子View的布局。还是从[注释3]​的performLayout()方法开始看:

//frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
         int desiredWindowHeight) {
     mLayoutRequested = false;
     mScrollMayChange = true;
     mInLayout = true;

     final View host = mView;
     if (host == null) {
         return;
     }
         Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
     try {
         ...
         host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //9
         ...
     } finally {
         Trace.traceEnd(Trace.TRACE_TAG_VIEW);
     }
     mInLayout = false;
 }

[注释9]​的host其实是View类,因此会调用View.java里面的layout()方法。

//frameworks/base/core/java/android/view/View.java
public void layout(int l, int t, int r, int b) {  

    // 当前视图的四个顶点
    int oldL = mLeft;  
    int oldT = mTop;  
    int oldB = mBottom;  
    int oldR = mRight;  

    // setFrame() / setOpticalFrame():确定View自身的位置
    // 即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回  
 boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //10

    //如果视图的大小和位置发生变化,会调用onLayout()
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
        // onLayout():确定该View所有的子View在父容器的位置   
        onLayout(changed, l, t, r, b);      //11
        ...
    }
    ...
}

上面代码有几个函数需要了解:

  • setFrame():确定View自身位置;
  • setOpticalFrame():也是确定View自身位置,其内部也是通过调用setFrame()来实现;
  • onLayout():确认该View里面的子View在父容器的位置,用protected修饰,在View.java文件里的onLayout()只是个空函数,需要子类进行重写。

3.2 LinearLayout的onLayout()

如果当前的View是一个子View,则不需要重写onLayout()。但如果是一个ViewGroup,则先执行layout()方法 –〉setFrame()方法确定自己的位置,再通过重写onLayout(),其中的关键是循环调用child.layout()方法来确定子View在当前父容器的位置。我们看看LinearLayout的onLayout():

//frameworks/base/core/java/android/widget/LinearLayout.java
 @Override
 protected void onLayout(boolean changed, int l, int t, int r, int b) {
     if (mOrientation == VERTICAL) {
         layoutVertical(l, t, r, b);  //如果是垂直布局,则调用layoutVertical()
     } else {
         layoutHorizontal(l, t, r, b);
     }
 }

 void layoutVertical(int left, int top, int right, int bottom) {
     final int paddingLeft = mPaddingLeft;
     int childTop;
     int childLeft;
     //计算子View个数
     final int count = getVirtualChildCount();
     ...
     for (int i = 0; i < count; i++) {
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasureWidth();
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp = 
                    (LinearLayout.LayoutParams) child.getLayoutParams();
            ...
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            // 为子元素确定对应的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); //12
            // childTop会逐渐增大,意味着后面的子元素会被放置在靠下的位置
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child,i)
        }
    }
 }

 private void setChildFrame(View child, int left, int top, int width, int height) {
     child.layout(left, top, left + width, top + height); //13
 }

如果LinearLayout是垂直布局,则调用layoutVertical()。接着计算子View的个数,循环调用[注释12]​的setChildFrame()方法来确定子View在父容器的位置。从[注释13]​可以知道setChildFrame()方法其实就是调用子View的layout()方法。而子View会继续调用setFrame()方法确定自己的位置,在执行onLayout()方法,上面说过在View.java文件里的onLayout()只是个空函数,所以此时不会有具体实现。

3.3 Layout流程图

看看Layout的流程图加深理解(抄录于参考文档2): alt

4、View绘制 - Draw(绘制)

4.1 Draw源码流程

每个View的宽高和位置都确定好后,就开始最后的绘制了,从[注释4]​performDraw()开始看:

//frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw() {
    try {
        //执行draw()
        boolean canUseAsync = draw(fullRedrawNeeded);
    } finally {
        mIsDrawing = false;
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

private void draw(boolean fullRedrawNeeded) {
    ...
    //执行drawSoftware()
    if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset, 
    scalingRequired, dirty)) {
        return;
    }
    ...
}

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, 
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
    try {
        //最终执行了子View的draw()方法
        mView.draw(canvas);
    } finally {
        if (!attachInfo.mSetIgnoreDirtyState) {
            // Only clear the flag if it was not set during the mView.draw() call
            attachInfo.mIgnoreDirtyState = false;
        }
    }
}

4.2 Draw的步骤

通过源码可以追溯到,最终执行到了View.java里面的draw()方法。

    //frameworks/base/core/java/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
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges =(viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges =(viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            drawAutofilledHighlight(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // Step 7, draw the default focus highlight
            drawDefaultFocusHighlight(canvas);

            if (debugDraw()) {
                debugDrawFocus(canvas);
            }

            // we're done...
            return;
        }
    }

以上的源码里的官方注释,draw()方法有以下步骤:

  1. 绘制View的背景;
  2. 如果有必要的话,保存画布的图层以准备fading;
  3. 绘制View的内容,即执行关键函数onDraw()​;
  4. 绘制子View;
  5. 如果有必要的话,绘制View的fading边缘并恢复图层;
  6. 绘制View的装饰(比如滚动条等等)
  7. 绘制默认焦点高亮

无论是View还是ViewGroup,绘制的流程都是如此,还有两点需要了解:

  • 在ViewGroup中,实现了dispatchDraw()方法,而子View是不需要实现该方法的;
  • 自定义View时,一般需要重写onDraw()方法,以绘制自己想要的样式,自定义View可见本系列《自定义View》一文。

4.3 Draw的流程图

看看Draw()的流程图加深理解(抄录于参考文档2): alt

5、总结

  • View的绘制有三大经典步骤:测量-布局-绘制,如果需要自定义View,可能需要重写onMeasure()方法,onLayout()方法,onDraw()方法;
  • 测量时,先测量子View再根据子View大小,计算出父View的大小;
  • 布局时,先布局好父View的位置,再布局子View的位置;
  • 绘制时,先绘制背景,再绘制父View,最后绘制子View;

6 为什么onCreate获取不到View的宽高

Activity在执行完oncreate,onResume之后才创建ViewRootImpl,ViewRootImpl进行View的绘制工作