Android应用层View绘制流程与源码分析(1),Android热修复原理

还记得前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》文章3-3小节探讨的inflate方法加载一些布局显示时指定的大小失效问题吗?当时只给出了结论,现在给出了详细原因分析,我想不需要再做过多解释了吧。

至此整个View绘制流程的第一步就分析完成了,可以看见,相对来说还是比较复杂的,接下来进行小结。

2-2 measure原理总结

通过上面分析可以看出measure过程主要就是从顶层父View向子View递归调用view.measure方法(measure中又回调onMeasure方法)的过程。具体measure核心主要有如下几点:

  • MeasureSpec(View的内部类)测量规格为int型,值由高2位规格模式specMode和低30位具体尺寸specSize组成。其中specMode只有三种值:

MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;

MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;

MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;

  • View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。

  • 最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。

  • ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。

  • 只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。

  • View的布局大小由父View和子View共同决定。

  • 使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

3 View绘制流程第二步:递归layout源码分析


在上面的背景介绍就说过,当ViewRootImpl的performTraversals中measure执行完成以后会接着执行mView.layout,具体如下:

private void performTraversals() {

mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

}

可以看见layout方法接收四个参数,这四个参数分别代表相对Parent的左、上、右、下坐标。而且还可以看见左上都为0,右下分别为上面刚刚测量的width和height。

至此又回归到View的layout(int l, int t, int r, int b)方法中去实现具体逻辑了,所以接下来我们开始分析View的layout过程。

整个View树的layout递归流程图如下:

这里写图片描述

3-1 layout源码分析

layout既然也是递归结构,那我们先看下ViewGroup的layout方法,如下:

@Override

public final void layout(int l, int t, int r, int b) {

super.layout(l, t, r, b);

}

看着没有?ViewGroup的layout方法实质还是调运了View父类的layout方法,所以我们看下View的layout源码,如下:

public void layout(int l, int t, int r, int b) {

//实质都是调用setFrame方法把参数分别赋值给mLeft、mTop、mRight和mBottom这几个变量

//判断View的位置是否发生过变化,以确定有没有必要对当前的View进行重新layout

boolean changed = isLayoutModeOptical(mParent) ?

setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

//需要重新layout

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {

//回调onLayout

onLayout(changed, l, t, r, b);

}

}

看见没有,类似measure过程,lauout调运了onLayout方法。

对比上面View的layout和ViewGroup的layout方法可以发现,View的layout方法是可以在子类重写的,而ViewGroup的layout是不能在子类重写的,言外之意就是说ViewGroup中只能通过重写onLayout方法。那我们接下来看下ViewGroup的onLayout方法,如下:

@Override

protected abstract void onLayout(boolean changed,

int l, int t, int r, int b);

看见没有?ViewGroup的onLayout()方法竟然是一个抽象方法,这就是说所有ViewGroup的子类都必须重写这个方法。所以在自定义ViewGroup控件中,onLayout配合onMeasure方法一起使用可以实现自定义View的复杂布局。自定义View首先调用onMeasure进行测量,然后调用onLayout方法动态获取子View和子View的测量大小,然后进行layout布局。重载onLayout的目的就是安排其children在父View的具体位置,重载onLayout通常做法就是写一个for循环调用每一个子视图的layout(l, t, r, b)函数,传入不同的参数l, t, r, b来确定每个子视图在父视图中的显示位置。

再看下View的onLayout方法源码,如下:

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

}

我勒个去!是一个空方法,没啥可看的。

既然这样那我们只能分析一个现有的继承ViewGroup的控件了,就拿LinearLayout来说吧,如下是LinearLayout中onLayout的一些代码:

public class LinearLayout extends ViewGroup {

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

if (mOrientation == VERTICAL) {

layoutVertical(l, t, r, b);

} else {

layoutHorizontal(l, t, r, b);

}

}

}

看见没有,LinearLayout的layout过程是分Vertical和Horizontal的,这个就是xml布局的orientation属性设置的,我们为例说明ViewGroup的onLayout重写一般步骤就拿这里的VERTICAL模式来解释吧,如下是layoutVertical方法源码:

void layoutVertical(int left, int top, int right, int bottom) {

final int paddingLeft = mPaddingLeft;

int childTop;

int childLeft;

// Where right end of child should go

//计算父窗口推荐的子View宽度

final int width = right - left;

//计算父窗口推荐的子View右侧位置

int childRight = width - mPaddingRight;

// Space available for child

//child可使用空间大小

int childSpace = width - paddingLeft - mPaddingRight;

//通过ViewGroup的getChildCount方法获取ViewGroup的子View个数

final int count = getVirtualChildCount();

//获取Gravity属性设置

final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;

final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;

//依据majorGravity计算childTop的位置值

switch (majorGravity) {

case Gravity.BOTTOM:

// mTotalLength contains the padding already

childTop = mPaddingTop + bottom - top - mTotalLength;

break;

// mTotalLength contains the padding already

case Gravity.CENTER_VERTICAL:

childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;

break;

case Gravity.TOP:

default:

childTop = mPaddingTop;

break;

}

//重点!!!开始遍历

for (int i = 0; i < count; i++) {

final View child = getVirtualChildAt(i);

if (child == null) {

childTop += measureNullChild(i);

} else if (child.getVisibility() != GONE) {

//LinearLayout中其子视图显示的宽和高由measure过程来决定的,因此measure过程的意义就是为layout过程提供视图显示范围的参考值

final int childWidth = child.getMeasuredWidth();

final int childHeight = child.getMeasuredHeight();

//获取子View的LayoutParams

final LinearLayout.LayoutParams lp =

(LinearLayout.LayoutParams) child.getLayoutParams();

int gravity = lp.gravity;

if (gravity < 0) {

gravity = minorGravity;

}

final int layoutDirection = getLayoutDirection();

final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);

//依据不同的absoluteGravity计算childLeft位置

switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {

case Gravity.CENTER_HORIZONTAL:

childLeft = paddingLeft + ((childSpace - childWidth) / 2)

  • lp.leftMargin - lp.rightMargin;

break;

case Gravity.RIGHT:

childLeft = childRight - childWidth - lp.rightMargin;

break;

case Gravity.LEFT:

default:

childLeft = paddingLeft + lp.leftMargin;

break;

}

if (hasDividerBeforeChildAt(i)) {

childTop += mDividerHeight;

}

childTop += lp.topMargin;

//通过垂直排列计算调运child的layout设置child的位置

setChildFrame(child, childLeft, childTop + getLocationOffset(child),

childWidth, childHeight);

childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

i += getChildrenSkipCount(child, i);

}

}

}

从上面分析的ViewGroup子类LinearLayout的onLayout实现代码可以看出,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子View的具体位置。

到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:

public final int getMeasuredWidth() {

return mMeasuredWidth & MEASURED_SIZE_MASK;

}

public final int getMeasuredHeight() {

return mMeasuredHeight & MEASURED_SIZE_MASK;

}

public final int getWidth() {

return mRight - mLeft;

}

public final int getHeight() {

return mBottom - mTop;

}

public final int getLeft() {

return mLeft;

}

public final int getRight() {

return mRight;

}

public final int getTop() {

return mTop;

}

public final int getBottom() {

return mBottom;

}

这也解释了为什么有些情况下getWidth()和getMeasuredWidth()以及getHeight()和getMeasuredHeight()会得到不同的值,所以这里不做过多解释。

到此整个View的layout过程分析就算结束了,接下来进行一些总结工作。

3-2 layout原理总结

整个layout过程比较容易理解,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点:

  • View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。

  • measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。

  • 凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的(前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》也有提到过)。

  • 使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

4 View绘制流程第三步:递归draw源码分析


在上面的背景介绍就说过,当ViewRootImpl的performTraversals中measure和layout执行完成以后会接着执行mView.layout,具体如下:

private void performTraversals() {

final Rect dirty = mDirty;

canvas = mSurface.lockCanvas(dirty);

mView.draw(canvas);

}

draw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工。所以又回归到了ViewGroup与View的树状递归draw过程。

先来看下View树的递归draw流程图,如下:

这里写图片描述

如下我们详细分析这一过程。

4-1 draw源码分析

由于ViewGroup没有重写View的draw方法,所以如下直接从View的draw方法开始分析:

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

if (!dirtyOpaque) {

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

}

// 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) {

matrix.setScale(1, fadeHeight * topFadeStrength);

matrix.postTranslate(left, top);

fade.setLocalMatrix(matrix);

p.setShader(fade);

canvas.drawRect(left, top, right, top + length, p);

}

// Step 6, draw decorations (scrollbars)

onDrawScrollBars(canvas);

}

看见整个View的draw方法很复杂,但是源码注释也很明显。从注释可以看出整个draw过程分为了6步。源码注释说(”skip step 2 & 5 if possible (common case)”)第2和5步可以跳过,所以我们接下来重点剩余四步。如下:

第一步,对View的背景进行绘制。

可以看见,draw方法通过调运drawBackground(canvas);方法实现了背景绘制。我们来看下这个方法源码,如下:

private void drawBackground(Canvas canvas) {

//获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable

final Drawable background = mBackground;

//根据layout过程确定的View位置来设置背景的绘制区域

if (mBackgroundSizeChanged) {

background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

mBackgroundSizeChanged = false;

rebuildOutline();

}

//调用Drawable的draw()方法来完成背景的绘制工作

background.draw(canvas);

}

第三步,对View的内容进行绘制。

可以看到,这里去调用了一下View的onDraw()方法,所以我们看下View的onDraw方法(ViewGroup也没有重写该方法),如下:

/**

  • Implement this to do your drawing.

  • @param canvas the canvas on which the background will be drawn

*/

protected void onDraw(Canvas canvas) {

}

可以看见,这是一个空方法。因为每个View的内容部分是各不相同的,所以需要由子类去实现具体逻辑。

第四步,对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。

我们来看下View的draw方法中的dispatchDraw(canvas);方法源码,可以看见如下:

/**

  • Called by draw to draw the child views. This may be overridden

  • by derived classes to gain control just before its children are drawn

  • (but after its own view has been drawn).

  • @param canvas the canvas on which to draw the view

*/

protected void dispatchDraw(Canvas canvas) {

}

看见没有,View的dispatchDraw()方法是一个空方法,而且注释说明了如果View包含子类需要重写他,所以我们有必要看下ViewGroup的dispatchDraw方法源码(这也就是刚刚说的对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制的原因,因为如果是View调运该方法是空的,而ViewGroup才有实现),如下:

@Override

protected void dispatchDraw(Canvas canvas) {

final int childrenCount = mChildrenCount;

final View[] children = mChildren;

for (int i = 0; i < childrenCount; i++) {

if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {

more |= drawChild(canvas, child, drawingTime);

}

}

// Draw any disappearing views that have animations

if (mDisappearingChildren != null) {

for (int i = disappearingCount; i >= 0; i–) {

more |= drawChild(canvas, child, drawingTime);

}

}

}

可以看见,ViewGroup确实重写了View的dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法,我们可以看下ViewGroup的drawChild方法,如下:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {

return child.draw(canvas, this, drawingTime);

}

可以看见drawChild()方法调运了子View的draw()方法。所以说ViewGroup类已经为我们重写了dispatchDraw()的功能实现,我们一般不需要重写该方法,但可以重载父类函数实现具体的功能。

第六步,对View的滚动条进行绘制。

可以看到,这里去调用了一下View的onDrawScrollBars()方法,所以我们看下View的onDrawScrollBars(canvas);方法,如下:

/**

  • Request the drawing of the horizontal and the vertical scrollbar. The

  • scrollbars are painted only if they have been awakened first.

  • @param canvas the canvas on which to draw the scrollbars

  • @see #awakenScrollBars(int)

*/

protected final void onDrawScrollBars(Canvas canvas) {

//绘制ScrollBars分析不是我们这篇的重点,所以暂时不做分析

}

可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。

到此,View的draw绘制部分源码分析完毕,我们接下来进行一些总结。

4-2 draw原理总结

可以看见,绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节:

  • 如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。

  • View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。

  • View的绘制是借助onDraw方法传入的Canvas类来进行的。

  • 区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。

  • 在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。

  • 默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。

【工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果】

5 View的invalidate和postInvalidate方法源码分析


你可能已经看见了,在上面分析View的三步绘制流程中最后都有调运一个叫invalidate的方法,这个方法是啥玩意?为何出现频率这么高?很简单,我们拿出来分析分析不就得了。

5-1 invalidate方法源码分析

来看一下View类中的一些invalidate方法(ViewGroup没有重写这些方法),如下:

/**

  • Mark the area defined by dirty as needing to be drawn. If the view is

  • visible, {@link #onDraw(android.graphics.Canvas)} will be called at some

  • point in the future.

  • This must be called from a UI thread. To call from a non-UI thread, call

  • {@link #postInvalidate()}.

  • WARNING: In API 19 and below, this method may be destructive to

  • {@code dirty}.

  • @param dirty the rectangle representing the bounds of the dirty region

*/

//看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View

public void invalidate(Rect dirty) {

final int scrollX = mScrollX;

final int scrollY = mScrollY;

//实质还是调运invalidateInternal方法

invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,

dirty.right - scrollX, dirty.bottom - scrollY, true, false);

}

/**

  • Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The

  • coordinates of the dirty rect are relative to the view. If the view is

  • visible, {@link #onDraw(android.graphics.Canvas)} will be called at some

  • point in the future.

  • This must be called from a UI thread. To call from a non-UI thread, call

  • {@link #postInvalidate()}.

  • @param l the left position of the dirty region

  • @param t the top position of the dirty region

  • @param r the right position of the dirty region

  • @param b the bottom position of the dirty region

*/

//看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对局部View

public void invalidate(int l, int t, int r, int b) {

final int scrollX = mScrollX;

final int scrollY = mScrollY;

//实质还是调运invalidateInternal方法

invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);

}

/**

  • Invalidate the whole view. If the view is visible,

  • {@link #onDraw(android.graphics.Canvas)} will be called at some point in

  • the future.

  • This must be called from a UI thread. To call from a non-UI thread, call

  • {@link #postInvalidate()}.

*/

//看见上面注释没有?public,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View

public void invalidate() {

//invalidate的实质还是调运invalidateInternal方法

invalidate(true);

}

/**

  • This is where the invalidate() work actually happens. A full invalidate()

  • causes the drawing cache to be invalidated, but this function can be

  • called with invalidateCache set to false to skip that invalidation step

  • for cases that do not need it (for example, a component that remains at

  • the same dimensions with the same content).

  • @param invalidateCache Whether the drawing cache for this view should be

  •        invalidated as well. This is usually true for a full
    
  •        invalidate, but may be set to false if the View's contents or
    
  •        dimensions have not changed.
    

*/

//看见上面注释没有?default的权限,只能在UI Thread中使用,别的Thread用postInvalidate方法,View是可见的才有效,回调onDraw方法,针对整个View

void invalidate(boolean invalidateCache) {

//实质还是调运invalidateInternal方法

invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);

}

//!!!!!!看见没有,这是所有invalidate的终极调运方法!!!!!!

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,

boolean fullInvalidate) {

// Propagate the damage rectangle to the parent view.

final AttachInfo ai = mAttachInfo;

final ViewParent p = mParent;

if (p != null && ai != null && l < r && t < b) {

final Rect damage = ai.mTmpInvalRect;

//设置刷新区域

damage.set(l, t, r, b);

//传递调运Parent ViewGroup的invalidateChild方法

p.invalidateChild(this, damage);

}

}

看见没有,View的invalidate(invalidateInternal)方法实质是将要刷新区域直接传递给了父ViewGroup的invalidateChild方法,在invalidate中,调用父View的invalidateChild,这是一个从当前向上级父View回溯的过程,每一层的父View都将自己的显示区域与传入的刷新Rect做交集 。所以我们看下ViewGroup的invalidateChild方法,源码如下:

public final void invalidateChild(View child, final Rect dirty) {

ViewParent parent = this;

final AttachInfo attachInfo = mAttachInfo;

do {

//循环层层上级调运,直到ViewRootImpl会返回null

parent = parent.invalidateChildInParent(location, dirty);

} while (parent != null);

}

这个过程最后传递到ViewRootImpl的invalidateChildInParent方法结束,所以我们看下ViewRootImpl的invalidateChildInParent方法,如下:

@Override

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {

//View调运invalidate最终层层上传到ViewRootImpl后最终触发了该方法

scheduleTraversals();

return null;

}

看见没有?这个ViewRootImpl类的invalidateChildInParent方法直接返回了null,也就是上面ViewGroup中说的,层层上级传递到ViewRootImpl的invalidateChildInParent方法结束了那个do while循环。看见这里调运的scheduleTraversals这个方法吗?scheduleTraversals会通过Handler的Runnable发送一个异步消息,调运doTraversal方法,然后最终调用performTraversals()执行重绘。开头背景知识介绍说过的,performTraversals就是整个View数开始绘制的起始调运地方,所以说View调运invalidate方法的实质是层层上传到父级,直到传递到ViewRootImpl后触发了scheduleTraversals方法,然后整个View树开始重新按照上面分析的View绘制流程进行重绘任务。

到此View的invalidate方法原理就分析完成了。

5-2 postInvalidate方法源码分析

上面分析invalidate方法时注释中说该方法只能在UI Thread中执行,其他线程中需要使用postInvalidate方法,所以我们来分析分析postInvalidate这个方法源码。如下:

public void postInvalidate() {

postInvalidateDelayed(0);

}

继续看下他的调运方法postInvalidateDelayed,如下:

public void postInvalidateDelayed(long delayMilliseconds) {

// We try only with the AttachInfo because there’s no point in invalidating

// if we are not attached to our window

final AttachInfo attachInfo = mAttachInfo;

//核心,实质就是调运了ViewRootImpl.dispatchInvalidateDelayed方法

if (attachInfo != null) {

attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);

}

}

我们继续看他调运的ViewRootImpl类的dispatchInvalidateDelayed方法,如下源码:

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {

Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);

mHandler.sendMessageDelayed(msg, delayMilliseconds);

}

看见没有,通过ViewRootImpl类的Handler发送了一条MSG_INVALIDATE消息,继续追踪这条消息的处理可以发现:

public void handleMessage(Message msg) {

switch (msg.what) {

case MSG_INVALIDATE:

((View) msg.obj).invalidate();

break;

}

}

看见没有,实质就是又在UI Thread中调运了View的invalidate();方法,那接下来View的invalidate();方法我们就不说了,上名已经分析过了。

到此整个View的postInvalidate方法就分析完成了。

5-3 invalidate与postInvalidate方法总结

依据上面对View的invalidate分析我总结绘制如下流程图:

这里写图片描述

依据上面对View的postInvalidate分析我总结绘制如下流程图:

这里写图片描述

关于这两个方法的具体流程和原理上面也分析过了,流程图也给出了,相信已经很明确了,没啥需要解释的了。所以我们对其做一个整体总结,归纳出重点如下:

invalidate系列方法请求重绘View树(也就是draw方法),如果View大小没有发生变化就不会调用layout过程,并且只绘制那些“需要重绘的”View,也就是哪个View(View只绘制该View,ViewGroup绘制整个ViewGroup)请求invalidate系列方法,就绘制该View。

常见的引起invalidate方法操作的原因主要有:

  • 直接调用invalidate方法.请求重新draw,但只会绘制调用者本身。

  • 触发setSelection方法。请求重新draw,但只会绘制调用者本身。

  • 触发setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE 转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及draw过程,同样只绘制需要“重新绘制”的视图。

  • 触发setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。

  • 触发requestFocus方法。请求View树的draw过程,只绘制“需要重绘”的View。

5-4 通过invalidate方法分析结果回过头去解决一个背景介绍中的疑惑

分析完invalidate后需要你回过头去想一个问题。还记不记得这篇文章的开头背景介绍,我们说整个View绘制流程的最初代码是在ViewRootImpl类的performTraversals()方法中开始的。上面当时只是告诉你了这个结论,至于这个ViewRootImpl类的performTraversals()方法为何会被触发没有说明原因。现在我们就来分析一下这个触发的源头。

让我们先把大脑思考暂时挪回到《Android应用setContentView与LayoutInflater加载解析机制源码分析》这篇博文的setContentView机制分析中(不清楚的请点击先看这篇文章再回过头来继续看)。我们先来看下那篇博文分析的PhoneWindow的setContentView方法源码,如下:

@Override

public void setContentView(View view, ViewGroup.LayoutParams params) {

//如果mContentParent为空进行一些初始化,实质mContentParent是通过findViewById(ID_ANDROID_CONTENT);获取的id为content的FrameLayout的布局(不清楚的请先看《Android应用setContentView与LayoutInflater加载解析机制源码分析》文章)

if (mContentParent == null) {

installDecor();

}


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中…(img-p1XqnCm2-1712422096051)]

[外链图片转存中…(img-5Aos2Wjf-1712422096051)]

[外链图片转存中…(img-61qiZwFA-1712422096052)]

[外链图片转存中…(img-R21FKHVc-1712422096052)]

[外链图片转存中…(img-Maq6hRfi-1712422096052)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

今天关于面试的分享就到这里,还是那句话,有些东西你不仅要懂,而且要能够很好地表达出来,能够让面试官认可你的理解,例如Handler机制,这个是面试必问之题。有些晦涩的点,或许它只活在面试当中,实际工作当中你压根不会用到它,但是你要知道它是什么东西。

最后在这里小编分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司19年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

【Android核心高级技术PDF文档,BAT大厂面试真题解析】

[外链图片转存中…(img-5ZoXE5ac-1712422096052)]

【算法合集】

[外链图片转存中…(img-Np4Ivl3z-1712422096053)]

【延伸Android必备知识点】

[外链图片转存中…(img-sWVRE5ly-1712422096053)]

【Android部分高级架构视频学习资源】

**Android精讲视频领取学习后更加是如虎添翼!**进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 16
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值