Android中Activity是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图,这个内容视图就是用户看到的界面。

PhoneWindow是Android系统中最基本的窗口系统,每个Activity会创建一个。PhoneWindow是Activity和View系统交互的接口。一个PhoneWindow对应一个DecorView跟一个ViewRootImpl,DecorView是ViewTree里面的顶层布局,是继承于FrameLayout,是Activity中所有View的祖先。ViewRootImpl建立DecorView和Window之间的联系。



下面介绍一些相关的概念:

  • Window

    Window表示的是一个窗口,它是站在WindowManagerService角度上的一个抽象的概念,Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,只要有View的地方就一定有Window。

    这里需要注意的是,这个抽象的Window概念和PhoneWindow这个类并不是同一个东西,PhoneWindow表示的是手机屏幕的抽象,它充当Activity和DecorView之间的媒介,就算没有PhoneWindow也是可以展示View的。

    抛开一切,仅站在WindowManagerService的角度上,Android的界面就是由一个个Window层叠展现的,而Window又是一个抽象的概念,它并不是实际存在的,它是以View的形式存在,这个View就是DecorView。
  • DecorView

    DecorView是整个Window界面的最顶层View,View的测量、布局、绘制、事件分发都是由DecorView往下遍历这个View树。DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(具体情况和Android的版本及主题有关),上面是标题栏,下面是内容栏。在Activity中我们通过setContentView所设置的布局文件其实就是被加载到内容栏中的,而内容栏的id是content,因此指定布局的方法叫setContent()。
  • ViewRoot

    ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完之后,会将DecorView添加到Window中,同时会创建对应的ViewRootImpl,并将ViewRootImpl和DecorView建立关联,并保存到WindowManagerGlobal对象中。

绘制的流程


当一个应用启动时,会启动一个主Activity,Android系统会根据Activity的布局来对它进行绘制。绘制会从根视图ViewRootImpl的performTraversals()方法开始,从上到下遍历整个视图树,每个View控制负责绘制自己,而ViewGroup还需要负责通知自己的子View进行绘制操作。View的绘制流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽高,layout根据测量的宽高确定View在其父View中的四个顶点的位置,而draw则将View绘制到屏幕上。通过ViewGroup的递归遍历,一个View树就展现在屏幕上了。



performTraversals()方法在类ViewRootImpl内,其核心代码如下:

  int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// 测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
// 布局
performLayout(lp, mWidth, mHeight);
...
// 绘制
performDraw();
  • measure:根据父View传递的MeasureSpec进行计算大小,确定View的测量宽高。
  • layout:根据measure子View所得到的布局大小和布局参数,确定子View在其父View中的四个顶点的位置。
  • draw:把View绘制到屏幕上。

MeasureSpec


为了更好地理解View的测量过程,我们需要理解MeasureSpec,它是View的一个内部类,它表示对View的测量规格。

在Google官方文档中是这么定义MeasureSpec的:

A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.

MeasureSpec是一个32位二进制的整数,由SpecMode和SpecSize两部分组成。其中,高2位为SpecMode(测量模式),低30位为SpecSize(测量大小)。

SpecMode的取值可为以下三种:

  • UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
  • EXACTLY:精确测量模式,表示父视图已经决定了子视图的精确大小,这种模式下View的测量值就是SpecSize的值。对应View的LayoutParams的match_parent或者精确数值。
  • AT_MOST:最大值模式,父视图已经限制子视图的大小,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。对应View的LayoutParams的wrap_content。

Measure


ViewGroup的measure

ViewGroup在它的measureChild方法中传递给子View。ViewGroup通过遍历自身所有的子View,并逐个调用子View的measure方法实现测量操作。ViewGroup在遍历完子View后,需要根据子元素的测量结果来决定自己最终的测量大小,并调用setMeasuredDimension方法保存测量宽高值。

// 遍历测量 ViewGroup 中所有的 View
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);
}
}
} //测量某个指定的View
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

View的MeasureSpec是由父容器的MeasureSpec和自己的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有父类。在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小。

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

DecorView是FrameLyaout的子类,属于ViewGroup,对于ViewGroup来说,除了完成自己的measure过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,他没有重写View的onMeasure方法,这里很好理解,因为每个具体的ViewGroup实现类的功能是不同的,如何测量应该让它自己决定,比如LinearLayout和RelativeLayout。

因此在具体的ViewGroup中需要遍历去测量子View,这里我们看看ViewGroup中提供的测量子View的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);
}

上述方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码上看,子元素的MeasureSpec的创建与父容器的MeasureSpec和本身的LayoutParams有关,此外和View的margin和父类的padding有关,现在看看getChildMeasureSpec的具体实现:

 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:
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;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
  • 当父View的mode是EXACTLY的时候:说明父View的size是确定的。

    子View的宽或高是具体数值:子view的size已经固定了,子View的size就是固定这个数值,mode=EXACTLY。

    子View的宽或高是MATCH_PARENT:子View的size=父View的size,mode=EXACTLY。

    子View的宽或高是WRAP_CONTENT:子View是包裹布局,说明子View的size还不确定,所以子View的size最大不能超过父View的size,mode=AT_MOST。

  • 当父View的mode是AT_MOST的时候:说明父View的size是不确定的。

    子View的宽或高是具体数值:子view的size已经固定了,子View的size就是固定这个数值,mode=EXACTLY。

    子View的宽或高是MATCH_PARENT:父View的size是不确定的,子View是填充布局情况,也不能确定size,所以子View的size不能超过父View的size,mode=AT_MOST。

    子View的宽或高是WRAP_CONTENT:子View是包裹布局,size不能超过父View的size,mode=AT_MOST。

  • 当父View的mode是UNSPECIFIED的时候:说明父View不指定测量模式,父View没有限制子视图的大小,子View可以是想要的任何尺寸。

    子View的宽或高是具体数值:子view的size已经固定了,子View的size就是固定这个数值,mode=EXACTLY。

    子View的宽或高是MATCH_PARENT:子视图可以是想要的任何尺寸,mode=UNSPECIFIED。

    子View的宽或高是WRAP_CONTENT:子视图可以是想要的任何尺寸,mode=UNSPECIFIED。



    需要注意一点就是,此时的MeasureSpec并不是View真正的大小,只有setMeasuredDimension之后才能真正确定View的大小。

    关于具体ViewGroup的onMeasure过程这里不做分析,由于每种布局的测量方式不一样,不可能逐个分析,但在它们的onMeasure里面的步骤是有一定规律的:

    1.根据各自的测量规则遍历Children元素,调用getChildMeasureSpec方法得到Child的measureSpec;

    2.调用Child的measure方法;

    3.调用setMeasuredDimension确定最终的大小。

View的measure

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法里面会去调用onMeasure方法,我们可以通过复写onMeasure()方法去测量设置View的大小。如下:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
....
} //如果需要自定义测量,子类需重写这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
} //将测量好的宽跟高进行存储
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
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);
} //如果View没有重写onMeasure方法,默认会直接调用getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//获取父View传递过来的模式
int specMode = MeasureSpec.getMode(measureSpec);
//获取父View传递过来的大小
int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) {
case MeasureSpec.UNSPECIFIED:
//View的大小父View未定,设置为建议最小值
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}

从上述代码可以得出,View的宽/高由specSize决定,直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。

上述就是View的measure大致过程,在measure完成之后,通过getMeasuredWidth/Height方法就可以获得测量后的宽高,这个宽高一般情况下就等于View的最终宽高,因为View的layout布局的时候就是根据measureWidth/Height来设置宽高的,除非在layout中修改了measure值。

Layout


measure()方法中我们已经测量出View的大小,根据这些大小,我们接下来就要确定View在父View的布局位置。Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法。简单的来说就是,layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

  • View的layout方法代码:

    public void layout(int l, int t, int r, int b) {
    ...
    onLayout(changed, l, t, r, b);
    ...
    } //空方法,子类如果是ViewGroup类型,则重写这个方法,实现ViewGroup中所有View控件布局
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

可以看到这是一个空实现,和onMeasure方法类似,onLayout的实现和具体的布局有关,具体ViewGroup的子类需要重写onLayout方法,并根据具体布局规则遍历调用Children的layout方法。

通过上面的分析,可以得到两个结论:

View通过layout方法来确认自己在父容器中的位置。

ViewGroup通过onLayout方法来确定View在容器中的位置。

  • FrameLayout的onLayout方法代码:

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false);
    } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) {
    final View child = getChildAt(i);
    if (child.getVisibility() != GONE) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams(); 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; 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;
    } 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;
    } child.layout(childLeft, childTop, childLeft + width, childTop + height);
    }
    }
    }

1、获取父View的内边距padding的值。

2、遍历子View,处理子View的layout_gravity属性、根据View测量后的宽和高、父View的padding值、来确定子View的布局参数。

3、调用child.layout方法,对子View进行布局。

Draw


经过前面的测量和布局之后,接下来就是绘制了,也就是真正把View绘制在屏幕可见视图上。Draw操作用来将控件绘制出来,绘制的流程从performDraw()方法开始。performDraw()方法在类ViewRootImpl内,其核心代码如下:

private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
...
} private boolean draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
...
} private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
...
mView.draw(canvas);
...
}

最终调用到View的draw方法绘制每个具体的View,绘制基本上可以分为七个步骤。

public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// Step 2, If necessary, save the canvas' layers to prepare for fading
saveCount = canvas.getSaveCount();
canvas.saveUnclippedLayer(left, top, right, top + length);
...
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
...
// Step 4, draw the children
dispatchDraw(canvas);
...
// Step 5, If necessary, draw the fading edges and restore layers
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
...
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
...
}

第一步:drawBackground(canvas):作用就是绘制View的背景。

第二步:saveUnclippedLayer:保存画布的图层。

第三步:onDraw(canvas):绘制View的内容。View的内容是根据自己需求自己绘制的,所以方法是一个空方法,View的继承类自己复写实现绘制内容。

第四步:dispatchDraw(canvas):遍历子View进行绘制内容。在View里面是一个空实现,ViewGroup里面才会有实现。View的绘制过程的传递通过dispatchDraw来实现的,dispatchDraw会遍历调用所有子元素的draw方法,如此draw事件就一层层地传递了下去。在自定义ViewGroup一般不用复写这个方法,因为它在里面的实现帮我们实现了子View的绘制过程,基本满足需求。

第五步:drawRect:绘制边缘和恢复画布的图层。

第六步:onDrawForeground(canvas):对前景色跟滚动条进行绘制。

第七步:drawDefaultFocusHighlight(canvas):绘制默认焦点高亮。

总结


如果是自定义ViewGroup的话,需要重写onMeasure方法,在onMeasure方法里面遍历测量子元素,同理onLayout方法也是一样,最后实现onDraw方法绘制自己;

如果自定义View的话,则需要从写onMeasure方法,处理wrap_content的情况,不需要处理onLayout,最后实现onDraw方法绘制自己。

Android探究之View的绘制流程的更多相关文章

  1. 深入理解 Android 之 View 的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  2. Android的自定义View及View的绘制流程

    目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...

  3. 【转】深入理解Android之View的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  4. Android之View的绘制流程

    本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...

  5. 自定义控件(视图)1期笔记02:View的绘制流程

    1. 引言: 来自源码的3个方法: (1)public final void measure():测量,用来控制控件的大小,final不建议覆写 (2)public void layout():布局, ...

  6. 自定义view:view的绘制流程

    1.view的绘制流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw. ...

  7. 深入了解View的绘制流程

    1.  ViewRoot ViewRoot是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure.layout.draw)都是通过ViewRoot完 ...

  8. 每日一问:简述 View 的绘制流程

    Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...

  9. Android View的绘制流程

    写得太好了,本来还想自己写的,奈何肚里墨水有限,直接转吧.正所谓前人种树,后人乘凉.. View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发 ...

随机推荐

  1. Netty源码—六、tiny、small内存分配

    tiny内存分配 tiny内存分配流程: 如果申请的是tiny类型,会先从tiny缓存中尝试分配,如果缓存分配成功则返回 否则从tinySubpagePools中尝试分配 如果上面没有分配成功则使用a ...

  2. 深入理解令牌认证机制(token)

    以前的开发模式是以MVC为主,但是随着互联网行业快速的发展逐渐的演变成了前后端分离,若项目中需要做登录的话,那么token成为前后端唯一的一个凭证. token即标志.记号的意思,在IT领域也叫作令牌 ...

  3. 对于一个WEB前端初学者,学前端应该注意,有什么技巧

    web前端经验总结需要注意的地方和技巧如下: 1.编程思维 学习web前端开发核心在于一个“编程思维”,因为每段代码都不一样,都需要分别去看,所以只要你掌握了学习web前端的编程思维,那么写程序对于你 ...

  4. 实例分析Vue.js中 computed和methods不同机制

    在vue.js中,有methods和computed两种方式来动态当作方法来用的 1.首先最明显的不同 就是调用的时候,methods要加上() 2.我们可以使用 methods 来替代 comput ...

  5. Github排序(转载)

    目录 1. 冒泡排序 2. 选择排序 3. 插入排序 4. 希尔排序 5. 归并排序 6. 快速排序 7. 堆排序 8. 计数排序 9. 桶排序 10. 基数排序 参考:https://mp.weix ...

  6. Spring的历史及哲学

    Spring的历史和哲学 1.Spring 历史 时间回到2002年,当时正是 Java EE 和 EJB 大行其道的时候,很多知名公司都是采用此技术方案进行项目开发.这时候有一个美国的小伙子认为 E ...

  7. PHP获取远程http或ftp文件的md5值

    PHP获取本地文件的md5值: md5_file("/path/to/file.png"); PHP获取远程http文件的md5值: md5_file("https:// ...

  8. 微信小程序报错,不在以下 request 合法域名列表中(引起的探索)

       最近因为突然对小程序有兴趣,然后开始了自学之旅.     在学习的过程当中遇到了一个问题,控制台报错,提示:不在以下 request 合法域名列表中,如下图所示 然后我就开始了搜索之旅,相对觉得 ...

  9. ansible基础-变量

    一 变量的命名规范 变量的命名应该符如下合两个规范: 变量应该由字母.数字.下划线组成 变量应该以字母开头 例如:host_port.HOST_PORT.var5是符合命名规范的,foo-port. ...

  10. OAuth2实现单点登录SSO

    1.  前言 技术这东西吧,看别人写的好像很简单似的,到自己去写的时候就各种问题,“一看就会,一做就错”.网上关于实现SSO的文章一大堆,但是当你真的照着写的时候就会发现根本不是那么回事儿,简直让人抓 ...