先来一张流程图
Android invalidate是如何导致View重绘的.jpg
我以前一直以为invalidate
是刷新的意思,查了下词典才知道原来是废弃,使无效的意思。在Android中即意味着view的某个显示区域内容变脏了,该显示区域需要被重新绘制。
invalidate方法是在View
类中声明的。
/**
* 废弃整个view。如果view可见,在将来的某个时间点view的
* {onDraw(android.graphics.Canvas)} 方法将会被调用
*
* 必须在主线程调用。在非UI线程请调用 {#postInvalidate()}.
*/
public void invalidate() {
invalidate(true);
}
/**
* 这里是invalidate() 工作真正发生的地方。一个完整的invalidate()会导致绘制缓存被废
* 弃。如果不需要废弃绘制缓存,调用此方法的时候可以将参数invalidateCache设为false
* 来跳过废弃绘制缓存的步骤(例如,一个组件的尺寸和内容都没有改变的时候)。
*
* @param invalidateCache view的绘制缓存是否也要被废弃。true,表示完全废弃。
* false,不需要废弃绘制缓存
*/
public void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
内部调用了invalidateInternal方法。View的invalidateInternal方法精简版。
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
//如果当前view不可见,并且没有在执行动画,直接return
if (skipInvalidate()) {
return;
}
//...
// 把需要重绘的View区域传递给父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);
//注释1处,调用parent的invalidateChild方法,向上传递重绘事件
p.invalidateChild(this, damage);
}
//...
}
方法内部判断如果的主流程就是设置重绘区域,然后调用ViewParent的invalidateChild方法,并传入自身和重绘区域。
ViewParent的invalidateChild方法
/**
* 所有或者部分子view需要被重新绘制
*
* @param 需要被重新绘制的子view
* @param r 子view废弃的矩形区域
*
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
*/
@Deprecated
public void invalidateChild(View child, Rect r);
ViewGroup和ViewRootImpl都实现了ViewParent的invalidateChild方法。普通的view的parent是ViewGroup。我们先看一下ViewGroup的实现。
ViewGroup的invalidateChild方法精简版
public final void invalidateChild(View child, final Rect dirty) {
final AttachInfo attachInfo = mAttachInfo;
//...
ViewParent parent = this;
if (attachInfo != null) {
//...
//保存子view的left和top
final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
//...
//设置绘制区域
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}
//...
parent = parent.invalidateChildInParent(location, dirty);
if (view != null) {
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
//重新设置重绘区域
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
}
}
} while (parent != null);
}
}
在上述方法中,设置了需要重绘的区域dirty。之后再do...while方法中,反复的调用parent = parent.invalidateChildInParent(location, dirty)方法,来调用父类的invalidateChildInParent对View的重绘请求进行传递。
ViewParent的invalidateChildInParent方法。
/**
* 所有或者部分子view需要被重新绘制。
*
* location数组保存了两个int值,分别代表需要重绘的子view的左和上的位置。
*
* <p>如果指定的矩形区域必须在当前ViewParent中被废弃, 那么这方法必须返回当前
* ViewParent的parent。如果指定的矩形区域不需要在当前ViewParent中被废弃,
* 或者当前ViewParent对象的parent不存在,那么这个方法必须返回null。
*
* 如果此方法返回了一个非null值,那么这个loaction数组一定被更新为当前ViewParent左和上的坐标了。
* @param location 一个有两个值的数组,保存要被重绘的子view的左和上的坐标。
* @param r 子view要被重绘的矩形区域
*
* @return 当前ViewParent的parent或者null。
* @deprecated Use {@link #onDescendantInvalidated(View, View)} instead.
*/
@Deprecated
public ViewParent invalidateChildInParent(int[] location, Rect r);
ViewGroup和ViewRootImpl都实现了ViewParent的invalidateChildInParent方法。我们先看一下ViewGroup的实现。
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
//...
//返回mParent
return mParent;
}
return null;
}
在这里我们直接认定ViewGroup会返回mParent。如果当前ViewGroup对象是DecorView了。那么DecorView返回的mParent
就不是ViewGroup了,而是ViewRootImpl。
ViewRootImpl实现了ViewParent
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
//...
}
ViewRootImpl的invalidateChildInParent(View child, Rect dirty)方法
@Override
public void invalidateChild(View child, Rect dirty) {
invalidateChildInParent(null, dirty);
}
ViewRootImpl的invalidateChildInParent(int[] location, Rect dirty)方法精简版
@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//注释1处,检查线程
checkThread();
//...
//注释2处
invalidateRectOnScreen(dirty);
return null;
}
在上面方法的注释1处,检查当前线程是否是view的原始线程。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这个异常信息也是很眼熟啊。
注释2处,调用了ViewRootImpl的invalidateRectOnScreen方法。
private void invalidateRectOnScreen(Rect dirty) {
//...
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
//调用scheduleTraversals方法
scheduleTraversals();
}
}
ViewRootImpl的scheduleTraversals方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//注释1处
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//注释2处
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//...
}
}
在注释1处,向消息队列发送了一个同步屏障,这样,looper会暂停处理同步消息,优先处理异步消息。直到同步屏障被解除。在注释2处,调用Choreographer的postCallback发送了一个异步消息。这样做的原因是防止消息队列中堆积大量的同步消息,导致我们的重绘操作延迟,造成界面更新不及时。在异步消息被处理完毕后会解除同步屏障,恢复正常的消息处理。
我们注意一下我们postCallback发送的mTraversalRunnable对象。
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
//mTraversalRunnable对象
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
当我们的异步消息被处理的时候,mTraversalRunnable对象的run方法会被执行,从而调用doTraversal方法。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//注释1处,移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//注释2处
performTraversals();
//...
}
}
在上面的注释1处,移除了同步屏障,在注释2处调用了ViewRootImpl的的performTraversals方法。
ViewRootImpl的的performTraversals方法精简版
private void performTraversals() {
performMeasure();
performLayout();
performDraw();
}
我们只看performDraw方法。
private void performDraw() {
//...
try {
//调用draw方法
boolean canUseAsync = draw(fullRedrawNeeded);
if (usingAsyncReport && !canUseAsync) {
mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false;
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
//...
}
内部调用了ViewRootImpl的draw方法。
private boolean draw(boolean fullRedrawNeeded) {
//...
//注释1处
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
注释1处,调用了ViewRootImpl的drawSoftware方法。
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
//注释1处
mView.draw(canvas);
}
注释1处,调用DecorView的draw方法,DecorView是继承FrameLayout的,这个时候开始就进入了常规的ViewGroup的绘制流程。
参考链接
网友评论