美文网首页
Android invalidate是如何导致View重绘的

Android invalidate是如何导致View重绘的

作者: leilifengxingmw | 来源:发表于2019-06-25 23:21 被阅读0次

先来一张流程图


Android invalidate是如何导致View重绘的.jpg

我以前一直以为invalidate是刷新的意思,查了下词典才知道原来是废弃,使无效的意思。在Android中即意味着view的某个显示区域内容变脏了,该显示区域需要被重新绘制。

invalidate.png

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的绘制流程。

参考链接

  1. Android 自定义 View——invalidate 传递与绘制流程分析

相关文章

网友评论

      本文标题:Android invalidate是如何导致View重绘的

      本文链接:https://www.haomeiwen.com/subject/aqkofctx.html