比较一下requestLayout和invalidate方法

·  阅读 3690

概述

注:本文基于Android 10源码,为了文章的简洁性,引用源码的地方可能有所删减。

博客链接

在更新 View 时我们常用到 requestLayout 和 invalidate 这两个方法,本文会根据源码分析一下这两个方法的区别和工作逻辑。在开始阅读之前可以先看看 Android-View绘制原理Android-Choreographer工作原理 对 Android View 绘制流程有一个大概的理解。另外如果对Android 图形系统感兴趣的话可以阅读一下 Android图形系统综述(干货篇) 系列的文章,个人觉得思路还是比较清晰的哈。

VRImpl.performTraversals

无论是 requestLayout 还是 invalidate 方法最后都会调用到 ViewRootImpl.performTraversals 方法开始 View 的更新,因此先看一下这个方法的部分代码:

private void performTraversals() {
    // DecorView 实例
    final View host = mView;
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    if (layoutRequested) {
        // 此处可能会调用 performMeasure 方法
        windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
    }
    if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        // ...
        if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            // 如果 lp.horizontalWeight 或 lp.verticalWeight 大于 0 则重新调用 performMeasure 测量
            // ...
            layoutRequested = true;
        }
    }
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);
    }
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
    if (!cancelDraw && !newSurface) {
        performDraw();
    }
}
复制代码

这个方法贼长,抽取关键部分可以大致明白 performTraversals 里会根据一些判断条件来执行 View 的 Measure, Layout, Draw 三大流程。

invalidate

View.invalidate

先看一下 invalidate 这个方法:

public void invalidate() {
    invalidate(true);
}

public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    if (skipInvalidate()) { // 判断是否需要跳过 invalidate
        return;
    }

    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
            || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
            || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
            || (fullInvalidate && isOpaque() != mLastIsOpaque)) { // 判断是否重绘
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque(); // 重新设置 Opaque
            mPrivateFlags &= ~PFLAG_DRAWN; // 移除 PFLAG_DRAWN 标志位
        }
        mPrivateFlags |= PFLAG_DIRTY; // 设置 PFLAG_DIRTY 脏区标志位

        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED; // 设置 PFLAG_INVALIDATED 标志位
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // 移除 PFLAG_DRAWING_CACHE_VALID 标志位
        }

        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            // damage 表示要重绘的脏区
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }
        // ...
    }
}

private boolean skipInvalidate() {
    return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
            (!(mParent instanceof ViewGroup) || !((ViewGroup) mParent).isViewTransitioning(this));
}
复制代码

首先会通过 skipInvalidate 方法判断是否要跳过 invalidate 过程,如果同时满足以下条件则跳过:

  • View 不可见
  • 当前没有运行动画
  • 父 View 不是 ViewGroup 类型或者父 ViewGoup 不处于过渡态

接下来再判断是否需要重绘,如果满足以下任意一个条件则进行重绘:

  • View 已经绘制完成且具有边界
  • invalidateCache == true 且设置了 PFLAG_DRAWING_CACHE_VALID 标志位,即绘制缓存可用
  • 没有设置 PFLAG_INVALIDATED 标志位,即没有被重绘过
  • fullInvalidate == true 且在 透明 和 不透明 之间发生了变化

在处理了一些标志位的逻辑后调用了父 View 的 invalidateChild 方法并将要重绘的区域 damage 传给父 View。于是接着看 ViewGroup.invalidateChild 方法:

public final void invalidateChild(View child, final Rect dirty) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null && attachInfo.mHardwareAccelerated) {
        // 开启了硬件加速
        onDescendantInvalidated(child, child);
        return;
    }
    // 未开启硬件加速
    ViewParent parent = this;
    if (attachInfo != null) {
        // ...
        do {
            // ...
            parent = parent.invalidateChildInParent(location, dirty);
            // 重新设置脏区
            // ...
        } while (parent != null);
    }
}
复制代码

可以看到这里会根据是否开启了硬件加速而走不同的逻辑。

软件绘制

关闭硬件加速时会循环调用 parent.invalidateChildInParent 方法并将返回值赋给 parent 直到其为 null 时退出循环,于是可以看看 ViewGroup.invalidateChildInParent 方法:

public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        // 处理运算 dirty 区域
        // ...
        // 移除 PFLAG_DRAWING_CACHE_VALID 标志位
        mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        return mParent;
    }
    return null;
}
复制代码

invalidateChildInParent 方法主要是对子 View 传递的 dirty 区域进行处理与运算并返回 mParent 对象。因此在 invalidateChild 方法中会循环逐层调用父 View 的 invalidateChildInParent 方法,最终来到顶层的 DecorView.invalidateChild 方法,其 mParent 是 ViewRootImpl, 根据 Android-Window机制源码解读 可以重新看看 ViewRootImpl.setView 方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) { // 这里的 view 即是 DecorView 对象
        if (mView == null) {
            mView = view;
            // ...
            requestLayout();
            // ...
            view.assignParent(this);
        }
    }
}

// View
void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but" + " it already has a parent");
    }
}
复制代码

因此可以看出 DecorView 的 mParent 就是 ViewRootImpl 对象,于是再看看 ViewRootImpl.invalidateChildInParent 方法:

public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    checkThread(); // 检查当前线程是否是主线程
    // ...
    invalidateRectOnScreen(dirty);
    return null;
}

private void invalidateRectOnScreen(Rect dirty) {
    // ...
    scheduleTraversals();
}
复制代码

可以看到 ViewRootImpl.invalidateChildInParent 方法返回 null,因此 ViewGroup.invalidateChild 会退出循环。至于 scheduleTraversals 方法应该很熟悉了,在 Vsync 信号到来后便会执行 performTraversals 方法(参考 Android-Choreographer工作原理),由于 mLayoutRequested 默认值是 false 且 invalidate 过程并没有给它赋值,于是不会调用 measureHierarchy 方法,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 和 performLayout,否则只会调用 performDraw 去重新 draw,并在其中给 View 设置 PFLAG_DRAWN 标志位。

硬件绘制

开启了硬件绘制后会走 ViewGroup.onDescendantInvalidated 方法:

public void onDescendantInvalidated(@NonNull View child, @NonNull View target) {
    // ...
    if (mParent != null) {
        mParent.onDescendantInvalidated(this, target);
    }
}
复制代码

和软件绘制类似,接下来会逐级调用父 View 的 onDescendantInvalidated 方法,最后走到 ViewRootImpl.onDescendantInvalidated 方法:

public void onDescendantInvalidated(@NonNull View child, @NonNull View descendant) {
    invalidate();
}

void invalidate() {
    mDirty.set(0, 0, mWidth, mHeight);
    if (!mWillDrawSoon) {
        scheduleTraversals();
    }
}
复制代码

可以看到跟软件绘制类似调用了 scheduleTraversals 方法,在 Vsync 信号到来后执行 performTraversals 方法,由于 mLayoutRequested 默认值是 false 且 invalidate 过程并没有给它赋值,于是不会调用 measureHierarchy 方法,而只有满足上面的条件 —— mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 才会调用 performMeasure 和 performLayout,否则只会调用 performDraw 去重新 draw。

根据之前 Android-Surface之创建流程及软硬件绘制 的解析,开启硬件加速后 performDraw 方法会通过 mAttachInfo.mThreadedRenderer.draw 来绘制,接着调用到 ThreadedRenderer.updateRootDisplayList 方法:

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    updateViewTreeDisplayList(view);
    // ...
}

private void updateViewTreeDisplayList(View view) {
    view.mPrivateFlags |= View.PFLAG_DRAWN;
    view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED;
    view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
    view.updateDisplayListIfDirty();
    view.mRecreateDisplayList = false;
}
复制代码

注意到在调用 View.invalidate 方法时对该 View 设置了 PFLAG_INVALIDATED 标志位,因此调用 invalidate 方法的 View 的 mRecreateDisplayList 属性为 true,其它 View 为 false。接下来会调用到 view.updateDisplayListIfDirty 方法:

public RenderNode updateDisplayListIfDirty() {
    // 调用 invalidate 时 PFLAG_DRAWING_CACHE_VALID 标志位已经被清除,因此该条件为 true
    if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || !renderNode.isValid() || (mRecreateDisplayList)) {
        if (renderNode.isValid() && !mRecreateDisplayList) {
            // 不需要重绘
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchGetDisplayList();
            return renderNode; // no work needed
        }

        // 需要重绘
        mRecreateDisplayList = true;
        // 重绘...
    }
    return renderNode;
}
复制代码

这里根据 mRecreateDisplayList 的值会判断是走 重绘 还是 dispatchGetDisplayList 的逻辑:

  • 对于调用 View.invalidate 方法的 View 来说其 mRecreateDisplayList 值为 true,因此走重绘逻辑。
  • 其余 View 如 DecorView 等会走 dispatchGetDisplayList 逻辑。

最开始的根 View 是 DecorView 类型,来看看 dispatchGetDisplayList 方法:

protected void dispatchGetDisplayList() {
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
            recreateChildDisplayList(child);
        }
    }
    // ...
}

private void recreateChildDisplayList(View child) {
    child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;
    child.mPrivateFlags &= ~PFLAG_INVALIDATED;
    child.updateDisplayListIfDirty();
    child.mRecreateDisplayList = false;
}
复制代码

可以看到 recreateChildDisplayList 和 updateViewTreeDisplayList 方法有些类似,都会设置 mRecreateDisplayList 的值,于是从 DecorView 开始会遍历其子 View 依次调用 updateDisplayListIfDirty 方法,对于没有设置 PFLAG_INVALIDATED 标志位的 View 会调用 dispatchGetDisplayList 接着往下分发,而设置了 PFLAG_INVALIDATED 标志位的 View 则会执行重绘逻辑,根据 Android-View绘制原理 可知当该 View 执行 View.draw 开始重绘时,如果它是 ViewGroup 类型,则会调用 ViewGroup.dispatchDraw 方法分发 draw 事件,dispatchDraw 内部遍历子 View 然后调用 drawChild 方法:

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

// View
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    // 是否是硬件加速
    boolean drawingWithRenderNode = mAttachInfo != null && mAttachInfo.mHardwareAccelerated && hardwareAcceleratedCanvas;
    if (drawingWithRenderNode) {
        // 又会重复上面的逻辑,对于没有设置 PFLAG_INVALIDATED 标志位的 View 不会重绘
        renderNode = updateDisplayListIfDirty();
    }
    // ...
}
复制代码

小结

调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。由于 mLayoutRequested == false,因此只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:

  • 关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。
  • 开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

View 在绘制后会设置 PFLAG_DRAWN 标志位。

requestLayout

View.requestLayout

public void requestLayout() {
    if (mMeasureCache != null) mMeasureCache.clear();

    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
        // 如果处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中
        ViewRootImpl viewRoot = getViewRootImpl();
        if (viewRoot != null && viewRoot.isInLayout()) {
            if (!viewRoot.requestLayoutDuringLayout(this)) {
                return;
            }
        }
        mAttachInfo.mViewRequestingLayout = this;
    }
    // 添加标志位
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
        mAttachInfo.mViewRequestingLayout = null;
    }
}

// ViewRootImpl
boolean requestLayoutDuringLayout(final View view) {
    if (!mLayoutRequesters.contains(view)) {
        mLayoutRequesters.add(view);
    }
    // ...
}
复制代码

如果此时处于 Layout 则将该请求加入 ViewRootImpl 中的任务队列中,否则向上调用父 View 的 requestLayout 方法,直到 ViewRootImpl 中:

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
复制代码

ViewRootImpl.requestLayout 方法在 check 了线程后将 mLayoutRequested 置为 true 且调用 scheduleTraversals 方法,于是在 Vsync 信号到来后会调用 performTraversals 方法。由于 mLayoutRequested == true,因此会依次执行 performMeasure, performLayout 以及 performDraw 方法开始 View 的绘制流程。

绘制过程

measure

接下来看看 View.requestLayout 方法对整个 View 树的影响。首先看一下 View.measure 方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    // ...
    final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
    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) {
        // ...
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 设置 PFLAG_LAYOUT_REQUIRED 标志位
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
}
复制代码

在 View.requestLayout 方法中已经看到给当前 View 及其父 View 都添加了 PFLAG_FORCE_LAYOUT 标志位,因此其 forceLayout == ture,即会执行 onMeasure 方法测量。而对于未设置 PFLAG_FORCE_LAYOUT 标志位的 View 则需要判断其尺寸是否发生改变才会决定调用 onMeasure 与否。我们看到调用 onMeasure 后又设置了 PFLAG_LAYOUT_REQUIRED 标志位。

layout

接着看 View.layout 方法:

public void layout(int l, int t, int r, int b) {
    boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        // ...
    }
    // ...
}
复制代码

由于调用 onMeasure 后设置了 PFLAG_LAYOUT_REQUIRED 标志位,因此也会跟着执行 onLayout 方法。另外看一下 setOpticalFrame 和 setFrame 方法,其中 setOpticalFrame 方法中最终也会调用到 setFrame 方法:

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
        invalidate(sizeChanged);
        // ...
    }
}
复制代码

因此可以看到当 View 四个顶点发生变化时也会调用 onLayout 方法,且会调用 View.invalidate 方法,并将 View 的宽高是否发生变化传给 invalidateCache 参数。

draw

ViewRootImpl.performDraw 会调用到 ViewRootImpl.draw 方法:

private boolean draw(boolean fullRedrawNeeded) {
    final Rect dirty = mDirty;
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        // 硬件绘制/软件绘制
    }
}
复制代码

dirty 是脏区,在 ViewRootImpl.invalidate 方法中会调用 mDirty.set() 方法为其设置边界值,如果上面 View 的顶点没有发生变化则不会调用 invalidate 方法,则 dirty.isEmpty() 返回 true,因此整个 View 树都不会重绘。

小结

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会从上往下重新进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

示例

代码

接下来看一个示例,首先自定义两个 ViewGroup:

class MyViewGroup1(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : LinearLayout(context, attrs, defStyleAttr) {
    constructor(context: Context?) : this(context, null, 0)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d("LLL", "MyViewGroup1 -- onMeasure")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        Log.d("LLL", "MyViewGroup1 -- onLayout")
        super.onLayout(changed, l, t, r, b)
    }

    override fun onDraw(canvas: Canvas?) {
        Log.d("LLL", "MyViewGroup1 -- onDraw")
        super.onDraw(canvas)
    }
}
复制代码

MyViewGroup2 跟 MyViewGroup1 的代码类似。然后自定义一个 View:

class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {

    constructor(context: Context?) : this(context, null, 0)
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        Log.d("LLL", "MyView -- onMeasure")
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        Log.d("LLL", "MyView -- onLayout")
        super.onLayout(changed, left, top, right, bottom)
    }

    override fun onDraw(canvas: Canvas?) {
        Log.d("LLL", "MyView -- onDraw")
        super.onDraw(canvas)
    }
}
复制代码

接下来看一下布局文件:

<com.hearing.demo.MyViewGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FF0000"
    android:onClick="group1">

    <com.hearing.demo.MyViewGroup2
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#00FF00"
        android:onClick="group2">

        <com.hearing.demo.MyView
            android:layout_width="150dp"
            android:layout_height="150dp"
            android:background="#0000FF"
            android:onClick="view1"/>
    </com.hearing.demo.MyViewGroup2>
</com.hearing.demo.MyViewGroup1>
复制代码

给这三个控件添加点击事件,分别调用其 invalidate 和 requestLayout 方法。

invalidate

开启硬件加速

// 点击 MyViewGroup1 
D/LLL: MyViewGroup1 -- onDraw

// 点击 MyViewGroup2
D/LLL: MyViewGroup2 -- onDraw

// 点击 MyView
D/LLL: MyView -- onDraw
复制代码

关闭硬件加速

// 点击 MyViewGroup1 
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw

// 点击 MyView
D/LLL: MyViewGroup1 -- onDraw
D/LLL: MyViewGroup2 -- onDraw
D/LLL: MyView -- onDraw
复制代码

requestLayout

开启硬件加速

// 点击 MyViewGroup1
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout

// 点击 MyView
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyView -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout
D/LLL: MyView -- onLayout
复制代码

关闭硬件加速

// 点击 MyViewGroup1
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout

// 点击 MyViewGroup2
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout

// 点击 MyView
D/LLL: MyViewGroup1 -- onMeasure
D/LLL: MyViewGroup2 -- onMeasure
D/LLL: MyView -- onMeasure
D/LLL: MyViewGroup1 -- onLayout
D/LLL: MyViewGroup2 -- onLayout
D/LLL: MyView -- onLayout
复制代码

总结

invalidate

调用 View.invalidate() 方法后会逐级往上调用父 View 的相关方法,最终在 Choreographer 的控制下调用 ViewRootImpl.performTraversals() 方法。只有满足 mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null ... 等条件才会执行 measure 和 layout 流程,否则只执行 draw 流程,draw 流程的执行过程与是否开启硬件加速有关:

  • 关闭硬件加速则从 DecorView 开始往下的所有子 View 都会被重新绘制。
  • 开启硬件加速则只有调用 invalidate 方法的 View 才会重新绘制。

requestLayout

调用 View.requestLayout 方法后会依次调用 performMeasure, performLayout 和 performDraw 方法,调用者 View 及其父 View 会重新从上往下进行 measure, layout 流程,一般情况下不会执行 draw 流程(子 View 会通过判断其尺寸/顶点是否发生改变而决定是否重新 measure/layout/draw 流程)。

因此,当只需要进行重绘时可以使用 invalidate 方法,如果需要重新测量和布局则可以使用 requestLayout 方法,而 requestLayout 方法不一定会重绘,因此如果要进行重绘可以再手动调用 invalidate 方法。

题外话:昨天发现上周的文章 Android-WebView还会存在内存泄漏吗? 居然上了周榜第一,同时也上了十一月份月榜小尾巴,开心之余又不胜惶恐,本科毕业快一年半了,最近两个月开始在掘金写文章,总担心自己的文章质量和内容出问题,掘金是一个开放的平台,大家要是觉得流程解析中有疏漏或者不清晰的欢迎指出哈,新人一枚,共同进步!觉得不错的留个再走哈~

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改