invalidate三部曲之始于invalidate

已开通新的博客,后续文字都会发到新博客

https://www.0xforee.top


invalidate - to make invalid

invalidate() 方法可谓是自定义View的常客,不管是自己覆写onDraw()方法还是触发动画效果,以及其他种种需要更新界面元素的情况,都只需要调用一句invalidate(),剩下的交给系统即可,可谓省时又省力
如果说我们在一些简单的View中是可以这样直接使用而不加思索,那么当我们的view足够复杂时,在面对庞大的逻辑运算,不规范的系统调用等等所带来的性能开销面前,我们就不能熟视无睹。

因此深入系统内部,可以让我们规避很多不必要的麻烦,要知道系统代码也是人写出来的,不会为你考虑所有的复杂情况而优化,而了解内部实现机制,了解实现者的意图,就可以做针对性的优化,进而提升性能和稳定性。

invalidate,直译,使失效。
使什么失效?使当前的状态失效
为什么要失效?因为只有标记为失效之后,系统才会在下一次屏幕刷新时执行重绘
什么时候失效?在例如点击效果,背景颜色,高度,长度等等当前屏幕元素需要更新的时候。
那么失效做了什么?为什么等下一次屏幕刷新?而不是直接触发重新绘制?这样做的好处是什么?

写在前边

由于整个invalidate()周期较长,继续深入会涉及屏幕刷新等底层知识,所以我们会分为三个部分来讲,分别为View发起绘制、系统处理绘制请求的逻辑、View执行真实绘制。

(以scheduleTraversals()为第一个分割点,以doFrame()为第二个分割点)

这篇文章是第一部分,主要讲View发起绘制

View发起绘制主要涉及三个类:

android.view.View
android.view.ViewGroup
android.view.ViewParent
android.view.ViewRootImpl

类图

三个类的结构大概是这样的:

FuCOWq.png

ViewParent指明了父View要实现的职责,我们熟知的ViewGroup就实现了这个接口,那么ViewGroup是包裹View的对象,ViewRootImpl是做什么的?

我们知道Android中Window是View的载体,每个Window下都挂着一棵View树,那与window直接相关的View是谁?没错,就是ViewRootImpl,不过这不是一个View,因为没有继承自View,而只是实现了ViewParent标明的父View职责,可以理解为他是所有View的顶辈儿,要对View树做什么,都会经过这个实体的居中调停

View和ViewParent的关系就不用多说了,ViewParent只是一个可以容纳子View的特殊View

时序图

FuCLYn.png

以上为整个的时序图,我们后边所讲的流程,都是基于此图,接下来我们就详细过一下这个流程图

流程

从上边的时序图看,这个流程并不复杂,只涉及10个函数,那么复杂的是什么,复杂的是其中类的关系,以及要处理的各种情况,我们分开来看

/**
 * Invalidate the whole view. If the view is visible,
 * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
 * the future.
 * <p>
 * This must be called from a UI thread. To call from a non-UI thread, call
 * {@link #postInvalidate()}.
 */
// android.view.View#invalidate()
public void invalidate() {
    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.
 * @hide
 */
// android.view.View#invalidate(boolean)
public void invalidate(boolean invalidateCache) {
    invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}

标记View失效,然后会在接下来某个时间点调用onDraw()来重绘,且这个函数只能在主线程调用,要在非主线程调用,请使用postInvalidate(),这个我们会在最后讲述两个的区别。

另外,参数invalidateCache很重要,用来标记缓存是否失效。像如位移,alpha变换等这种大小和内容没有改变的情况,是不需要重新绘制内容的,系统会将view缓存到layer(层)中,通过对整个层进行变换来避免再次绘制。

这对于提高很多动画的性能是很有帮忙的,例如位移动画或者alpha动画。

// android.view.View#invalidateInternal
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    ......

    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();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY; // 增加脏区标记位

        if (invalidateCache) {  // 如果缓存失效
            mPrivateFlags |= PFLAG_INVALIDATED; // 标记失效
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // 清除正在绘制缓存标记位
        }

        // 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);
            p.invalidateChild(this, damage); // 向父View报告脏区位置
        }

        ......
    }
}

invalidateInternal()为内部实现,包括增加脏区标记,失效标记,并向父View报告脏区位置

/**
 * All or part of a child is dirty and needs to be redrawn.
 * 
 * @param child The child which is dirty
 * @param r The area within the child that is invalid
 *
 * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
 */
 // android.view.ViewParent#invalidateChild
@Deprecated
public void invalidateChild(View child, Rect r);

ViewParent的接口,对于ViewGroup和ViewRootImple有各自的实现,用于触发childr标志的脏区重绘

/**
 * Don't call or override this method. It is used for the implementation of
 * the view hierarchy.
 *
 * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
 * draw state in descendants.
 */
// android.view.ViewGroup#invalidateChild
@Deprecated
@Override
public final void invalidateChild(View child, final Rect dirty) {
    ......

    ViewParent parent = this;
    if (attachInfo != null) {
        
        ......

        do {
            View view = null;
            if (parent instanceof View) {
                view = (View) parent;
            }

            ......

            parent = parent.invalidateChildInParent(location, dirty);
            
            ......
            
        } while (parent != null);
    }
}

// android.view.ViewRootImpl#invalidateChild
@Override
public void invalidateChild(View child, Rect dirty) {
    invalidateChildInParent(null, dirty);
}

上下两个方法分别是:ViewGroup的invalidateChild的实现 和 ViewRootImpl的invalidateChild实现

我们知道view的父控件肯定是ViewGroup,但ViewGroup的父控件除了ViewGroup,还有ViewRootImpl

所以在触发invalidateChild后,会递归向上依次调用父控件的invalidateChildInParent,然后返回父控件的父控件,一直到顶层,也就是ViewRootImpl,最终调用ViewRootImpl的invalidateChildInParent,结束递归

/**
 * Don't call or override this method. It is used for the implementation of
 * the view hierarchy.
 *
 * This implementation returns null if this ViewGroup does not have a parent,
 * if this ViewGroup is already fully invalidated or if the dirty rectangle
 * does not intersect with this ViewGroup's bounds.
 *
 * @deprecated Use {@link #onDescendantInvalidated(View, View)} instead to observe updates to
 * draw state in descendants.
 */
// android.view.ViewGroup#invalidateChildInParent
@Deprecated
@Override
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
        
        ......

        return mParent;
    }

    return null;
}

// android.view.ViewRootImpl#invalidateChildInParent
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    .....

    invalidateRectOnScreen(dirty);

    return null;
}

二者实现,不再多说。

private void invalidateRectOnScreen(Rect dirty) {
    ......

    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

结束递归的最后,触发了view树的刷新,也就是我们的终极大bossscheduleTraversals

sechduleTraversals,拆开来分别是调度,递归

什么递归?view树的递归,为什么是调度,因为不是立刻执行,还需要时机触发。

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true; // 标记已经触发过调度了,所以如果有多次invalidate,其实并没有实际产生调度
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); // 加入回调队列
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

终于来到最后一步,并没看到所谓的触发刷新逻辑,只是将一个Runnable任务加入了一个任务队列中??说好的触发刷新呢?
一切的一切,都在这里的新面孔Choreographer

Choreographer直译:编舞者

结束语

欲知

编舞者Choreographer怎样接管后续工作?
刷新逻辑何时才能执行?

请听下回分解。

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值