invalidate 三部曲之始于 invalidate

invalidate  - to make invalid.

Choreographyer 三部曲


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

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

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

写在前边

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

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

1
2
3
4
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

流程

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

全部流程请参考序中的时序图:invalidate 三部曲序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 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动画。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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报告脏区位置

1
2
3
4
5
6
7
8
9
10
11
/**
* 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标志的脏区重绘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 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,结束递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 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;
}

二者实现,不再多说。

1
2
3
4
5
6
7
private void invalidateRectOnScreen(Rect dirty) {
......

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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怎样接管后续工作?
刷新逻辑何时才能执行?

请听下回分解。

invalidate 三部曲之始于 invalidate

http://www.0xforee.top/2018/10/13/invalidate-of-invalidate/

作者

0xforee

发布于

2018-10-13

更新于

2018-11-22

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论