performTraversals方法会经过measure、layout和draw三个流程才能将一帧View需要显示的内容绘制到屏幕上,用最简化的方式看ViewRootImpl.performTraversals()方法,如下。

 private void performTraversals() {
...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
performLayout(lp, mWidth, mHeight);
...
performDraw();
. ..

首先来说这三个流程的意义:

performMeasure():从根节点向下遍历View树,完成所有ViewGroup和View的测量工作,计算出所有ViewGroup和View显示出来需要的高度和宽度;

performLayout():从根节点向下遍历View树,完成所有ViewGroup和View的布局计算工作,根据测量出来的宽高及自身属性,计算出所有ViewGroup和View显示在屏幕上的区域;

performDraw():从根节点向下遍历View树,完成所有ViewGroup和View的绘制工作,根据布局过程计算出的显示区域,将所有View的当前需显示的内容画到屏幕上。

再来具体分析这三个流程:

1. performMeasure()流程

  在ViewRootImpl.java中调用performMeasure()方法传入的参数为childWidthMeasureSpec和childHeightMeasureSpec。在performTraversals能找到它们初始化的地方如下。

  int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

  通过getRootMeasureSpec能够计算出一个MeasureSpec值,该值一般是父布局调用子布局的measure()是传入的参数,这个参数是由父布局的宽高和子布局的LayoutParams参数计算得到的,这两个参数用于子布局的measure过程,很大程度上决定着子View的宽高。上面代码出计算出来的childWidthMeasureSpec和childHeightMeasureSpec则是根据Window相关属性得到的MeasureSpec,因为DecorView本身没有父布局,所以传入给DecorView进行measure的MeasureSpec值是由Window的尺寸(mWidth、mHeigth)和DecorView的LayoutParams得到的;而对于普通的View,它的MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 来共同决定。

  方法中的lp.width和lp.heigth表示布局类型,这里指的是DecorView的布局类型。布局类型举例来说,写布局xml时android:layout_width="wrap_content"中设置的wrap_content值,共有三种类型MATCH_PARENT、WRAP_CONTENT以及直接写入了确定大小。进入getRootMeasureSpec()方法。

    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

  实际上getRootMeasureSpec()方法就是根据传入的窗口大小和类型,通过MeasureSpec.makeMeasureSpec()方法合并成一个int型的measureSpec值。该值代表一个32位 int 值,高2位代表 SpecMode(测量模式),由传入的布局类型转化而来,有三种类型,MeasureSpec.EXACTLY:确定大小,parent view为child view指定固定大小;MeasureSpec.AT_MOST:最大大小;child view在parent view中取值;MeasureSpec.UNSPECIFIED:无限制,parent view不约束child view的大小。该值低30位代表 SpecSize,指在某个测量模式下的规格大小。后面的方法中会通过MeasureSpec.getMode()和MeasureSpec.getSize()方法来解析出这两个值。

  从这里就可以看到childWidthMeasureSpec, childHeightMeasureSpec实际上表示的就是DecorView的宽高和布局类型。进入到performMeasure()方法。

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

  该方法设置完TraceView的起始点和结束点后直接便是进入到了mView.measure()方法,进入对应View.measure()方法代码。

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

//是否有重新Layout的标志
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; // Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
     //与上一次的MeasureSpec进行对比确定是否需要重新绘制
     final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded();
       //key的值是由widthMeasureSpec和heightMeasureSpece
//在MeasureCache中查看在当前传入的宽高的MeasureSpec是否已经执行过onMeasure计算
        //如果已经执行过,则直接取出结果通过setMeasuredDimemsionRaw()设置测量出的相关参数
//如果没有执行过,才会调用onMeasure()方法进行测量工作
//mPrivateFlags3的设置,在后面介绍的View.layout()方法中会用到
//在后面layout()方法中会判断该flag,如果此时没有调用,则那layout会再调用measure()
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} // flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { (2)
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()"
);
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}      ...
}

  实际的measure过程是在onMeasure()方法中完成的,这里进入到调用到View.onMeasure()方法。

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

  大家注意这里要注意performMeasure()方法是final类型的,即是不能被父类重载的,所以无论是对于任何一种Layout(父类为ViewGroup.java,ViewGroup.java父类为View.java)的measure()方法调用,或是任何一个控件比如TextView的measure()方法调用,执行代码都是View.java中的measure()方法;而不同的控件measure过程的区别是通过重写onMeasure()方法来实现的。所以在measure()方法里调用的onMeasure()方法并未直接走到View.onMeasure()方法,而是走到了View的父类中重写的onMeasure()方法。但是,这里我们要注意到上一段代码的(2)处,这段代码是在调用后onMeasure()方法之后的一个判断,上面的注释的意思是如果到现在还没有调用过setMeasuredDimension()方法,就会抛出下面的异常,所以在View的父类重写onMeasure()方法时,一定要执行一次setMeasuredDimension()方法。那这个方法做了什么事呢?进入View.setMeasuredDimension()方法代码。

    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
     //根据自己与父布局的android:layoutMode的值调整传入参数
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

  直接进入setMeasuredDimensionRaw()方法。

    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

  这里我们可以看到setMeasuredDimension()实际上就是将传入的参数设置到View的变量mMeasuredWidth和mMeasuredHeight中。这也是实际上measure流程所要完成的任务,即是调用到布局树上的所有ViewGroup和View的measure(),让所有的ViewGroup和View计算出对应的宽、高的值保存到自己的mMeasuredWidth、mMeasuredHeight变量中。

  我们继续来分析DecorView的measure流程,进入DecorView.onMeasure()方法。

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
final boolean isPortrait =
getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT; final int widthMode = getMode(widthMeasureSpec);
final int heightMode = getMode(heightMeasureSpec); boolean fixedWidth = false;
mApplyFloatingHorizontalInsets = false;      //如果SpecMode不是EXACTLY的,则需要在这里调整为EXACTLY
if (widthMode == AT_MOST) {
final TypedValue tvw = isPortrait ? mWindow.mFixedWidthMinor : mWindow.mFixedWidthMajor;
if (tvw != null && tvw.type != TypedValue.TYPE_NULL) {
final int w;
//根据DecorView属性,计算出DecorView需要的宽度
if (tvw.type == TypedValue.TYPE_DIMENSION) {
w = (int) tvw.getDimension(metrics);
} else if (tvw.type == TypedValue.TYPE_FRACTION) {
w = (int) tvw.getFraction(metrics.widthPixels, metrics.widthPixels);
} else {
w = 0;
}
if (DEBUG_MEASURE) Log.d(mLogTag, "Fixed width: " + w);
final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//根据上面计算出来的需要的宽度生成新的MeasureSpec用于DecorView的测量流程
if (w > 0) {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.min(w, widthSize), EXACTLY);
fixedWidth = true;
} else {
widthMeasureSpec = MeasureSpec.makeMeasureSpec(
widthSize - mFloatingInsets.left - mFloatingInsets.right,
AT_MOST);
mApplyFloatingHorizontalInsets = true;
}
}
} mApplyFloatingVerticalInsets = false;
if (heightMode == AT_MOST) {
  ... //逻辑同上
} ...super.onMeasure(widthMeasureSpec, heightMeasureSpec); ...
}

  由于DecorView.java父类是FrameLayout.java,所以调用super.onMeasure()时进入FrameworkLayout.onMeasure()方法。

   @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();

     //如果宽、高的MeasureSpec的Mode有一个不是EXACTLY,这里就是true
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
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
          //子View的测量,方法内会调用到child.measure(),后文详解
measureChildWithMargins(child, widthMeasureSpec,
0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
          //计算出所有子布局中宽度和高度最大的值
          //由于子布局占用的尺寸除了自身宽高之外,还包含了其距离父布局的边界的值,所以需要加上左右Margin值
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());
          //当前的FrameLayout的MeasureSpec不都是EXACTLY,且其子View为MATCH_PARENT,
          //则子View保存到mMatchParentChildren中,后面重新测量
         //DecorView不会走这个逻辑,因为进过了DecorView的onMeasure()流程,MeasureSpec一定都为EXACTLY
          //会走到下面流程的情况举例:用户自布局一个FrameLayout属性为WRAP_CONTENT是,但子布局为MATCH_PARENT
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}

     //最后计算得到的maxWidth和maxHeight的值需要保证能够容纳下当前Layout下所有子View,所以需要对各类情况进行处理
     //所以有以下的加上Padding值,用户设置的Mini尺寸值的对比,设置了背景图片情况的图片大小对比
     // 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()); // Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}

     //设置测量结果,相当于完成自己View的measure
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
     //会走到这里的情况见前面注释
count = mMatchParentChildren.size();
if (count > 1) {
for (int i = 0; i < count; i++) {
final View child = mMatchParentChildren.get(i);
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); final int childWidthMeasureSpec;
if (lp.width == LayoutParams.MATCH_PARENT) {
            //根据当前FrameLayout已经测量出来的mMeasureWidth,计算出MATCH_PARENT的子View的宽度值
            final int width = Math.max(0, getMeasuredWidth()
- getPaddingLeftWithForeground() - getPaddingRightWithForeground()
- lp.leftMargin - lp.rightMargin);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
width, MeasureSpec.EXACTLY);
} else {
childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
lp.leftMargin + lp.rightMargin,
lp.width);
} ... //childHeigthMeasureSpe的设置,逻辑同上一段 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}

  首先是对于maxHeight和maxWith的宽度的计算,为何能通过所有子布局中最大的子布局的尺寸来决定自己的尺寸呢?首先要回顾下FrameLayout的布局模式,简单来说,就是所有放在布局里的控件,都按照层次堆叠在屏幕的左上角,后加进来的控件覆盖前面的控件。正是因为要实现这样的布局模式,才决定了FrameLayout的measure算法,所有的子View都是堆叠在屏幕左上角,自然只需要根据最大的子View的尺寸来设置自己(还需要加一些特殊情况的处理),即可能够放下所有的子View。如果是LinearLayout的onMeasure()方法,则又会实现不同的测量逻辑,在测量时则会考虑到多个View依次摆放的问题。

  再来关注代码中第一个加粗的方法measureChildWithMargins(),这里逻辑是依次调用该方法完成FrameLayout所有子View的measure工作。该方法在父类ViewGroup.java类中实现的,进入ViewGroup.measureChildWithMargins()方法代码。

   protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 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); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

  这个方法比较简单,首先关注下widthUsed和heithUsed的意义,表示的是父布局已经占用的大小,上面的FrameLayout.onMeasure()代码中调用时传入的是0。为何是0?因为FrameLayout的逻辑是全部堆叠在右上角,所以这个子View放到FrameLayout中时,不会被其它子View提前使用了FrameLayout的空间。

  重点关注下getChildMeasureSpec()方法,通过该方法能够得到子View的MeasureSpec,所以才能调用子View的measure()方法,完成子View的测量。注意,这里传入的lp.width表示的是布局中的android:layout_width对应的值,可能是确定的值,可能是LayoutParams.MATCH_PARENT或LayoutParams.WRAP_CONTENT。进入ViewGroup.getChildMeasureSpec()方法。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
     //父布局的SpecMode和Size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

     //父布局中剩下的能够提供给子View使用的尺寸,通过后面计算得到子View需要多少
int size = Math.max(0, specSize - padding);

     //用于保存子布局的进行measure的MeasureSpec的两个参数
int resultSize = 0;
int resultMode = 0; switch (specMode) {
// Parent has imposed an exact size on us
     //如果父布局是Mode是EXACTY
     case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
          //子View赋值了固定大小
          //则子View的SpecSize就是自己想要的大小
//则子View的SpecMode是EXACTY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
          //子View是MATCH_PARENT
          //子View想要父布局所有大小,则把父布局剩余的大小都给子View
          //子View的SpecMode是EXACTLY
          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.
          //子View是WRAP_CONTENT
          //子View想要在后面自己计算自己需要多少大小
          //则把父布局剩余的大小存入SpecSize,但SpecMode为AT_MOST
//表示子布局在后面measure自己大小的同时不能超过SpecSize的值
          resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break; // Parent has imposed a maximum size on us
     //父布局Mode为AT_MOST
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.
//子View是MATCH_PARENT
//由于父布局没有确定大小,所以子布局在确定自己需要多少大小前不能给出确定大小
//则把父布局剩余的大小存入SpecSize,但SpecMode为AT_MOST
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
//如果子View是MATFH_PARENT
          //由于父布局没有限定子布局大小,则设置SpecSize值为0 (需要View支持这种模式)
          //设置SpecMode类型还是为UNSPECIFIED,这样最后计算出有多大就给多大,没有父布局的限制(下同)
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
//生成MeasureSpec
     return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

  在计算出用于子View进行measure的高和宽的MeasureSpec后,便会进入到子View的measure()方法。如果子View是ViewGroup类型,则会继续调用到其子View的measure()方法,并调用setMeasuredDimension()方法设置mMeasureHeight和mMeasureWidth完成自己的measure;如果是子View是一个控件(例如TextView),则会根据自己onMeasure()的实现完成自身的测量,也是调用setMeasuredDimension()方法设置变量。通过这样的方式便个遍历完View树上所有的ViewGroup中间节点和View叶节点,完成measure流程。

2. performLayout()流程

  进入ViewRootImpl.performLayout()源码。

   private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {

     ... Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  
       ...           } finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}

  这里调用的到DecorView的layout方法,该方法是实际上是调用到到了ViewGroup的layout()方法。上面介绍measure流程时我们知道了两个相关方法measure()和onMeasure(),其中measure()方法在所有视图到基类View中实现的,且不能被重写;而onMeasure()方法实现了实际的测量过程,需要由继承了View的视图子类重写该方法从而实现了自己的测量逻辑。而对于Layout流程,也有着两个相关方法layout()和onLayout(),进入View.java代码看这连个方法。View.layout()方法后面详讲。

 public void layout(int l, int t, int r, int b) {
...
onLayout(changed, l, t, r, b);
     ...
}

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

  首先这里的layout()方法没有final修饰符,表示layout()方法可以被重写,另外onLayout()方法是一个空方法,所以需要View的子类根据自己需求来重写该方法来完成layout流程。再来看下继承自View的ViewGroup类的layout()和onLayout()方法。

   @Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {//过渡动画相关
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
} @Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);

  ViewGroup的layout()方法加上了final修饰符,而onLayout()方法则是抽象类型的,所以所有继承自ViewGroup类的布局类(例如FrameLayout.java)都需要实现onLayout()方法,而不能重写layout()方法。并且ViewGroup中的layout()的方法只是加上一些对于过渡动画逻辑的处理,本质上是调回了View的layout()方法。

  在ViewRootImpl.performLayout()方法中调用到了DecorView的layout()的方法,根据上方代码super.layout()实际调用到了View的layout()方法。注意这里传入的四个参数,分别为0,0, host.getMeasuredWidth(), host.getMeasuredHeight(),后两个值就是在measure流程中计算出来的mMeasuredWidth和MeasuredHeigth,用于表示需要的宽度和高度。进入View.layout()方法代码。

   public void layout(int l, int t, int r, int b) {
     //mPrivateFlag3记录了measure过程是否被跳过,如果被跳过则这时候再调用一次measure(),前文有提
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;

     //layoutoutMode为Optical则会调到setOpticalFrame()
//setOpticalFrame()会对传入的参数进行调整,但还是调用到setFrame()方法
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

     //如果View位置发生了变化或已经设置了重新Layout的标志
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);        ...
} mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

  首先会调用setFrame()方法,方法的返回值标志了布局与上一次是否发生了变化。传入的四个参数的分别代表了,布局左、顶部、右、底部的值,这四个值指示了一个矩形区域。

    protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false; if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
} if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true; // Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position
invalidate(sizeChanged); mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS;        //会回调onSizeChanged()方法
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
} ...
}
return changed;
}

  这个方法比较简单,主要是将父类传入的区域保存到View的mLeft、mTop、mRight、mBottom。在执行完setFrame()之后便会执行到onLayout()方法。这里我们通过插入一段TextView.java类的onLayout()方法,来探究下layout这个流程究竟是要完成什么工作?TextView的layout也是从其父类传入四个参数调用到View.layout()方法(上面代码),然后setFrame(),然后就会进入到进入TextView.onLayout()代码。

    @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (mDeferScroll >= 0) {
int curs = mDeferScroll;
mDeferScroll = -1;
bringPointIntoView(Math.min(curs, mText.length()));
}
}

  可以看到这里只是在调用了View.onLayout()后添加了一个DeferScroll的逻辑处理。而回想下View.onLayout()方法其实什么都没有做,但这样TextView的layout流程就结束了。那对于TextView来说,layout流程到底做了什么事呢?其实主要就是设置了mLeft、mTop、mRight、mBottom这四个变量,这四个变量指示了这个TextView在屏幕上应该出现的坐标位置区域;而这四个变量,是由TextView的父布局View计算好了,再调用到TextView.layout()方法时传入的。所以整个layout过程,实际遍历整个View树,根据measure过程计算出的View需要的宽度和高度值结合自己的LayoutParam属性,计算出所有View在相对于自己父布局View的边界的位置,并保存到mLeft、mTop、mRight、mBottom变量中,用于后面的绘制操作。

  上面可以知道TextView的layout计算出来的现实区域直接时父布局传入的,所以较为复杂的计算逻辑是处于Layout类onLayout()源码中的。DecorView继承自FrameLayout,DecorView.onLayout()方法会调用到其父类FrameLayout类的onLayout()方法,进入FrameLayout.onLayout()方法。

    @Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
} void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();

     //计算当前Layout的边界padding值
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();

//遍历该Layout的所有子View
//结合子View的measure值,即自己的属性,计算出子View的layout区域,并调用子View的layout()方法
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();

          //获得该子布局measure出来的宽、高值
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight(); int childLeft;
int childTop; int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
} final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;                     //该Layout水平方向的gravity属性各种情况下的处理
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
            //水平方向居中
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
//水平方向居右
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
//水平方向居左
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}

          //该Layout垂直方法的gravity属性各种情况的处理
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
          //调用子View的layout方法,这里传入的参数就是父布局计算好的子View的区域
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}

  这样随着Layout的onLayout()调用到所有子View的layout()方法,而子View的layout()又会继续往下遍历直至遍历到View树到所有节电,这样就完成了整个layout的流程。

3. perfromDraw()流程

  执行完performLayout()方法,便会调用到performDraw()方法,进入到ViewRootImpl.performDraw()方法。

    private void performDraw() {
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
return;
}

     //是否需要全部重绘的标志
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false; mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
} ... }

  这里主要关注调用到了draw()方法,进入ViewRootImpl.draw()代码。  

   private void draw(boolean fullRedrawNeeded) {
...

     //如果是第一次绘制,则会回调到sFirstDrawHandlers中的事件
//在ActivityThread.attch()方法中有将回调事件加入该队列
//回调时会执行ActivityThread.ensureJitEnable来确保即时编译相关功能
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
     
     //滚动相关处理,如果scroll发生改变,则回调dispatchOnScrollChanged()方法
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}

//窗口当前是否有动画需要执行
boolean animating = mScroller != null && mScroller.computeScrollOffset();

     ... //scroll相关处理
     
     
final Rect dirty = mDirty;

     ...
     
     
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
} ...      if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//mAttachInfo.mHardwareRenderer不为null,则表示该Window使用硬件加速进行绘制
//执行ViewRootImpl.set()方法会判断是否使用硬件加速

//若判断使用会调用ViewRootImpl.enableHardwareAcceleration()来初始化mHardwareRenderer
       //该View设置为使用硬件加速,且当前硬件加速处于可用状态
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...

//使用硬件加速绘制方式
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo,
this);
} else {// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
//如果当前View要求使用硬件加速,但硬件加速处于disable状态
//可能是由于硬件加速在销毁之前的surface实例时会发出无效的宣告导致的
          if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) { try {
//尝试重新初始化当前window的硬件加速
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
} mFullRedrawNeeded = true;
scheduleTraversals();
return;
}

          //使用软件渲染绘制方式
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
} ...
}

  mDirty表示的是当前需要更新的区域,即脏区域。经过一些scroll相关的处理后,如果脏区域不为空或者有动画需要执行时,便会执行重绘窗口的工作。有两种绘制方式,硬件加速绘制方式和软件渲染绘制方式,在创建窗口流程的ViewRootImpl.setView()中,会根据不同情况,来选择是否创mAttachInfo.mHardwareRenderer对象。如果该对象不为空,则会进入硬件加速绘制方式,即调用到ThreadedRenderer.draw();否则则会进入软件渲染的绘制方式,调用到ViewRootImpl.drawSoftware()方法。但是无论哪种方式,都会走到mView.draw()方法,即DecorView.draw()方法。该方法是实际调用到到是View.draw(Canvas canvas)方法。

  在调用View.draw()方式时调用该方法时会传入canvas参数,硬件加速和软件渲染这两种方式会创建出不同的Canvas用以执行View的draw流程。Canvas顾名思义就是画布的意思,这个类一方面代表了当前用于绘制的区域及区域属性相关信息,同时也提供了各类接口用于在这片区域上画出给类图形,比如调用canvas.drawCircle(...)方法就根据传入参数在屏幕上画出一个特定的圆圈。硬件加速和软件渲染这两种方式对于Canvas有着不同的底层实现,带来的效果和对系统性能及内存这些的影响也不一样,这里我们不再展开介绍。直接看来View.draw()方法。

    @CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /*
* 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)
*/
//根据上面注释可以知道draw的过程分为5步
//1.画出背景background
//2.判断是否需要画边缘的渐变效果

//3.画出当前View需要显示的内容,调用onDraw()来实现
//4.调用dispatchDraw()方法,进入子视图的draw逻辑
//5.如果需要花边缘渐变效果,则在这里画
//6.绘制装饰(如滚动条)      // 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;
     //如果不需要绘制边缘渐变效果,跳过了step5
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
//绘制自己View的内容
       if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children
       //调起子View的Draw过程
dispatchDraw(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); // we're done...
return;
} /*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
... //有边缘渐变效果的处理// 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
... //画出边缘渐变效果// 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);
}

  注释已经很清楚,View的draw过程分为了五个步骤,如果没有边缘渐变效果则会跳过第五步。这里我们关注onDraw()方法实现了自己View内容的绘制和dispatchDraw()方法调起了自己的子View的绘制过程。首先,在调用View中调用onDraw()时,由于走的时DecorView的流程,所以会调用到DecorView.onDraw()方法,代码如下。

    @Override
public void onDraw(Canvas c) {
super.onDraw(c);
//DecorView设置了一个BackgroundFallback,该对象用于应用没有设置window背景时,会显示该对象指示的背景
mBackgroundFallback.draw(mContentRoot, c, mWindow.mContentParent);
}

  这里实际调用到的是父类的onDraw()方法,但是在DecorView的父类FrameLayout及更上一层的ViewGroup上都没有实现该方法,所以super.onDraw()实际调用到了View.onDraw()方法。

    protected void onDraw(Canvas canvas) {
}

  但是View.onDraw()方法是一个空实现,这里可以看出来,onDraw()方法是用于父类进行重写来实现画出自己内容的方法,而ViewGroup及其各种Layout子类都没有实现该方法,因为对于布局来说本身就是用来放置控件的,自己是没有什么内容好绘制的,所以在onDraw()是并未执行什么内容。自然作为FrameLayout子类的DecorView也没有什么好画出来的,除了画出一个FallbackBackground。但如果当前的View不是DecorView,也不是其它布局View,而是一个控件类型的View,那在onDraw()时就会根据自己的属性及自己需要显示的区,通过调用传入的Canvas对象的各种方法,来在屏幕上画出自己需要显示的内容。

  再来看View.draw()方法里的Step4调用到disptachDraw()方法,在View.dispatchDraw()方法还是一个空实现,说明是希望View的子类来实现的。而一般的非ViewGroup的控件子View类则不会实现该方法,因为这里View是没有子View的,所以当遍历到控件类型的View时,这一步实际上是什么都没有做的。我们继续来看DecorView的draw流程,这里调用到dispatchDraw()实际调用到到是ViewGroup.dispatchDraw()方法,进入该代码。

    @Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;

//如果当前的ViewGroup需要执行Layout级别的动画
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
            //将需要执行的动画设置到子View的对应属性上
attachLayoutAnimationParameters(child, params, i, childrenCount);
bindLayoutAnimation(child);
}
} final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}

       //启动mLayoutAnimationControlle中设置的动画
controller.start(); mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;

       //动画启动时的回调
if (mAnimationListener != null) {
mAnimationListener.onAnimationStart(controller.getAnimation());
}
} int clipSaveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
     //如果当前的ViewGroup设置了Padding的属性
if (clipToPadding) {
clipSaveCount = canvas.save();
//将父视图传入的canvas裁剪去padding的区域
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
} ...
for (int i = 0; i < childrenCount; i++) {
       ... //TransientView的处理        final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}

     ...

     // Draw any disappearing views that have animations
     //绘制mDisappearingChildren列别中的子视图,指正在处于消失动画状态的子View
if (mDisappearingChildren != null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
final View child = disappearingChildren.get(i);
more |= drawChild(canvas, child, drawingTime);
}
}
...

     //检查动画是否完成,如果完成则发送一个异步消息,通知应用程序
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
mLayoutAnimationController.isDone() && !more) {
// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
@Override
public void run() {
notifyAnimationListener();
}
};
post(end);
}
}

  在dispatch中会遍历当前ViewGroup的子视图,然后调用drawChild()方法来依次调起子视图的绘制过程,进入ViewGroup.drawChild()代码。

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}

  这里直接就调用到了child.draw()方法,但是注意,这里的draw()方法与我们之前分析过的View.draw()方法参数不一样,不仅传入了父布局视图的画布,还把父布局视图视图自身作为参数传入了。进入有三个参数的View.draw()方法的代码。

   /**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
   //这个方法是专门用于ViewGroup来调其子View的绘制过程的方法
//方法会传入当前View的父布局View,用来进行父布局canvas画布的区域移动和裁剪工作
//该方法会根据绘制类型(硬件加速、软件渲染)结合View属性进行一些canvas和painter属性设置工作
   boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

     ...
  
     draw(canvas);

     ...
}

  这个方法很长,但都是逻辑算法类型的,不影响到后面的流程,方法的作用见上访注释。我们回想一下之前的流程,从canvas的创建,到视图使用canvas进行绘制,并未涉及到canvas的裁剪动作。那是是因为我们是从DecorView进行分析的,在我们略过的canvas初始化动作时就根据DecorView在layout布局时计算出来的显示区域,进行了canvas画布区域的实质;而对于其它的ViewGroup在调起其子View的绘制动作时,就会调用到上方有三个参数的View.draw()方法,在该方法中结合layout过程中的计算结果,完成canvas画布的裁剪,然后再调用View.draw(Canvas)方法(之前介绍过的包含五个步骤的方法)来完成当前View的绘制过程。通过这样一个流程,便能够调用到视图树上所有ViewGroup和控件View的draw()方法,画出了所有View需要显示的内容,完成了一次绘制过程。

  

源码分析篇 - Android绘制流程(二)measure、layout、draw流程的更多相关文章

  1. 源码分析篇 - Android绘制流程(三)requestLayout()与invalidate()流程及Choroegrapher类分析

    本文主要探讨能够触发performTraversals()执行的invalidate().postInvalidate()和requestLayout()方法的流程.在调用这三个方法到最后执行到per ...

  2. 源码分析篇 - Android绘制流程(一)窗口启动流程分析

    Activity.View.Window之间的关系可以用以下的简要UML关系图表示,在这里贴出来,比较能够帮组后面流程分析部分的阅读. 一.Activity的启动流程 在startActivity() ...

  3. 深入源码分析SpringMVC底层原理(二)

    原文链接:深入源码分析SpringMVC底层原理(二) 文章目录 深入分析SpringMVC请求处理过程 1. DispatcherServlet处理请求 1.1 寻找Handler 1.2 没有找到 ...

  4. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  5. ASP.NET Core[源码分析篇] - 认证

    追本溯源,从使用开始 首先看一下我们的通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解 public void ConfigureServ ...

  6. ASP.NET Core[源码分析篇] - WebHost

    _configureServicesDelegates的承接 在[ASP.NET Core[源码分析篇] - Startup]这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_ ...

  7. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  8. Solr4.8.0源码分析(19)之缓存机制(二)

    Solr4.8.0源码分析(19)之缓存机制(二) 前文<Solr4.8.0源码分析(18)之缓存机制(一)>介绍了Solr缓存的生命周期,重点介绍了Solr缓存的warn过程.本节将更深 ...

  9. Android应用层View绘制流程之measure,layout,draw三步曲

    概述 上一篇博文对DecorView和ViewRootImpl的关系进行了剖析,这篇文章主要是来剖析View绘制的三个基本流程:measure,layout,draw.仅仅有把这三个基本流程搞清楚了, ...

随机推荐

  1. PHP continue break 区别 用法

    <?php //continue 跳过当前循环,进行下一个 //break 终止当前循环 $db=new PDO("mysql:host=localhost;dbname=root&q ...

  2. bat语法集【转】

    源文链接:http://www.cnblogs.com/jiangzhichao/archive/2012/02/15/2353004.html 1 echo 和 @@                 ...

  3. Latex中图表位置的控制

    \begin{figure}[!htbp] 其中htbp是可选的,它们分别代表 !-忽略“美学”标准 h-here t-top b-bottom p-page-of-its-own

  4. How to transfer developer profile to one mac to another mac

    Export developer profile from old mac. In the Xcode Organizer, select your team in the Teams section ...

  5. SSM_CRUD新手练习(3)创建数据库

    在上一节我们已经完成了基本的SSM配置,现在需要创建我们数据库. 我们需要两张表分别为tbl_emp(员工表)和tbl_dedpt(部门表).同时d_id是部门表对应dept_id的外键. 需要注意的 ...

  6. 《计算机科学基础》学习笔记_Part 1 Computer and Data

    Technorati Tags: 计算机科学基础,读书笔记 Chapter 1. Introduction Ø  计算机:黑盒,Output Data=f(Input Data, Program) Ø ...

  7. input和raw_input

    Python2.X使用raw_input() Python3.X废弃了raw_input()函数,使用input()函数替代它 code: data=input("please input ...

  8. UNIGUI换版本注意事项

    比如UNIGUI换版本注意事项 许多人在更换UNIGUI版本时,会遇到各种问题,报各样错.比如下面的: 然后便不知所措,怀疑是UNIGUI新版本有问题——不能安装成功.其实不然. 下面是正确的解决方法 ...

  9. 数据库中表的位置,在sysdatabases中

    name dbid sid mode status status2 crdate reserved category cmptlevel filename version master :: :: C ...

  10. [leet code 198]House Robber

    1 题目 You are a professional robber planning to rob houses along a street. Each house has a certain a ...