已开通新的博客,后续文字都会发到新博客
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
类图
三个类的结构大概是这样的:
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 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有各自的实现,用于触发child
的r
标志的脏区重绘
/**
* 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
怎样接管后续工作?
刷新逻辑何时才能执行?
请听下回分解。