invalidate和requestLayout流程认识

我们大多都了解Android中view的绘制过程,知道一个view是如何从父view往子view一层层的递归下去完成测量、布局和绘制的。
知道这些已经能完成基本的简单的自定义的view的开发了,但是在实际开发中我们往往会碰到或者使用两个同样很常见的方法—— invalidate和requestLayout.

invalidate

山不过来我就过去。话不多说,我们看看源码。

    /**
     * Invalidate the whole view. If the view is visible,
     * {@link #onDraw(android.graphics.Canvas)} will be called at some point in
     * the future.
     * 

* This must be called from a UI thread. To call from a non-UI thread, call * {@link #postInvalidate()}. */ 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. */ void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); }

这段代码还是很简单,注释也说的很清楚,只需要注意的是这个方法只能在主线程中调用,invalidate方法最终都是调用了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);
                //往父view传递,调用父view的invalidateChild方法
                p.invalidateChild(this, damage);
            }

            ...
        }
    }

从上述代码中可以发现,View方法中的invalidateInternal实际上是将刷新区域往上传给了父viewGroup的invalidateChild方法,也就是一个从下往上从子到父的一个回溯过程,在每一层view或者viewGroup中都对自己的显示区域和传过来的刷新的 damage区域Rect做一个交集。我们可以看看ViewGroup中的invalidateChild方法。

    /**
     * Don't call or override this method. It is used for the implementation of
     * the view hierarchy.
     */
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;

        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            ...
            if (!childMatrix.isIdentity() ||
                    (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) != 0) {
                RectF boundingRect = attachInfo.mTmpTransformRect;
                boundingRect.set(dirty);
                ...
                transformMatrix.mapRect(boundingRect);
                dirty.set((int) (boundingRect.left - 0.5f),
                        (int) (boundingRect.top - 0.5f),
                        (int) (boundingRect.right + 0.5f),
                        (int) (boundingRect.bottom + 0.5f));
            }

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

                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        dirty.set((int) (boundingRect.left - 0.5f),
                                (int) (boundingRect.top - 0.5f),
                                (int) (boundingRect.right + 0.5f),
                                (int) (boundingRect.bottom + 0.5f));
                    }
                }
            } while (parent != null);
        }
    }

其中最重要的就是第30行,parent = parent.invalidateChildInParent(location, dirty)
不停地往上递归调用invalidateChildInParent方法,直到顶层view也即是ViewRootImpl.

我们看看ViewGroup的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.
     */
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
                        FLAG_OPTIMIZE_INVALIDATE) {
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                return mParent;

            } else {
                mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID;

                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                if (mLayerType != LAYER_TYPE_NONE) {
                    mPrivateFlags |= PFLAG_INVALIDATED;
                }

                return mParent;
            }
        }

        return null;
    }

我们会发现其实这段代码主要是对传过来的Rect进行了运算,取了交集,对damage和自己的显示区域,返回的还是parent。

之前也讲到了,在invalidateChild中层层递归往父viewGroup回溯,直到ViewRootImpl才会停止,那我们看看ViewRootImpl中发生了什么。

    @Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        ...
        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        ...

        invalidateRectOnScreen(dirty);

        return null;
    }

最关键的一步在invalidateRectOnScreen中。

    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        ...

        // Add the new dirty rect to the current one
        localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
        // Intersect with the bounds of the window to skip
        // updates that lie outside of the visible region
        
        ...
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }
    }

这里需要解释一下的就是,invalidateChildInParent中返回的是null。这个结果在ViewGroup中分析时有用到,就结束了自子view到父view的递归过程。
因为invalidateChild中的do-while循环会终止。

往下我们看到在ViewRootImpl中调用了scheduleTraversals方法。这一步就是整个invalidate的关键执行步骤了。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

原来是scheduleTraversals向handler发送了一个异步消息,会执行TraversalRunnable,这个TraversalRunnable的run方法中,执行的就是
doTraversal方法。

    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            ...

            performTraversals();

            ...
        }
    }

看,doTraversal最后走到了我们熟悉的performTraversals了,performTraversals就是整个View树开始绘制的起始地方,所以说View调用invalidate方法的实质是层层回溯上传到父view,直到传递到ViewRootImpl后调用scheduleTraversals方法,然后整个View树开始
重新按照Android view的绘制过程分析分析的View绘制流程再来进行view的重绘任务。

postInvalidate

除了常见的invalidate外,我们还经常碰到postInvalidate,其实关于这个的特点我们在一开始有提到过invalidate只能在UI线程进行调用,所以如果想要在非主线程中进行
invalidate的效果,就需要使用postInvalidate。

    /**
     * 

Cause an invalidate to happen on a subsequent cycle through the event loop. * Use this to invalidate the View from a non-UI thread.

* *

This method can be invoked from outside of the UI thread * only when this View is attached to a window.

* * @see #invalidate() * @see #postInvalidateDelayed(long) */ public void postInvalidate() { postInvalidateDelayed(0); }

我们可以看到这里其实调用了以下的方法:

    /**
     * 

Cause an invalidate to happen on a subsequent cycle through the event * loop. Waits for the specified amount of time.

* *

This method can be invoked from outside of the UI thread * only when this View is attached to a window.

* * @param delayMilliseconds the duration in milliseconds to delay the * invalidation by * * @see #invalidate() * @see #postInvalidate() */ public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } }

可以发现实际上是调用了ViewRootImpl.dispatchInvalidateDelayed方法,那么我们来看看这个方法:

    public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }

这里也就是ViewRootImpl的handler发送了一条消息MSG_INVALIDATE,注意这里已经涉及到线程间消息传递了。

@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
    case MSG_INVALIDATE:
        ((View) msg.obj).invalidate();
        break;
    ...
    }
}

到这里为止,postInvalidate的实质就是在UI Thread中调运了View的invalidate方法,那接下来View的invalidate方法就不再重复说了,上面已经分析过了。

小结

关于二者的具体流程,上面已经写的很详细了,对于invalidate,就是从view开始一层层往上层调用,直到ViewRootImpl,然后重新绘制一遍。
对于postInvalidate,就是在viewRootImpl中给handler发送了一个请求重绘的消息,然后接着走invalidate,只是这个起始是可以在非UI线程上进行。

需要注意的是,invalidate和postInvalidate方法请求重绘View,只会调用draw方法,如果View大小没有发生变化就不会再调用layout,并且只绘制那些需要重绘的View的脏
的Rect,也就是谁调用,重绘谁。

在平常开发中,可能会有以下情况引起view重绘:

  • 直接手动调用invalidate方法.请求重新draw,但只会绘制调用者本身的view。
  • 调用setSelection方法。请求重新draw,但只会绘制调用者本身。
  • 调用setVisibility方法。 当View可视状态在INVISIBLE转换VISIBLE时会间接调用invalidate方法,继而绘制该View。当View的可视状态在INVISIBLE\VISIBLE转换为GONE状态时会间接调用requestLayout和invalidate方法,同时由于View树大小发生了变化,所以会请求measure过程以及layout过程,同样只绘制需要重新绘制的视图。
  • 调用setEnabled方法。请求重新draw,但不会重新绘制任何View包括该调用者本身。
  • 调用requestFocus方法。请求View树的draw,只绘制需要重绘的View。

requestLayout

本文最初提到了,除了invalidate外,常见以及常用的方法还有requestLayout。我们来看看这是怎么回事。

    /**
     * Call this when something has changed which has invalidated the
     * layout of this view. This will schedule a layout pass of the view
     * tree. This should not be called while the view hierarchy is currently in a layout
     * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the
     * end of the current layout pass (and then layout will run again) or after the current
     * frame is drawn and the next layout occurs.
     *
     * 

Subclasses which override this method should call the superclass method to * handle possible request-during-layout errors correctly.

*/ @CallSuper public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }

注意31-33行,是不是和invalidate的思路很像,也是一层层的回溯调用父view的requestLayout方法,直至顶级视图ViewRootImpl。
我们看一下ViewRootImpl的requstLayout方法:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

checkThread就是检查一下目标线程是不是当前线程。

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
            "Only the original thread that created a view hierarchy can touch its views.");
    }
}

scheduleTraversals做的事情就是和invalidate最后的过程差不多了,向viewRootImpl的

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

最后也是走到performTraversals了。

因此这些过程和invalidate一样的。

private void performTraversals() {
    ...
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    ...
    boolean insetsChanged = false;

    if (layoutRequested) {

        final Resources res = mView.getContext().getResources();

        if (mFirst) {
            // make sure touch mode code executes by setting cached value
            // to opposite of the added touch mode.
            mAttachInfo.mInTouchMode = !mAddedTouchMode;
            ensureTouchModeLocally(mAddedTouchMode);
        } else {
            if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
                insetsChanged = true;
            }
            if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
                insetsChanged = true;
            }
            if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
                insetsChanged = true;
            }
            if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
                mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: " + mAttachInfo.mVisibleInsets);
            }
            if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
                insetsChanged = true;
            }
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                windowSizeMayChange = true;

                if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
                        || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
                    // NOTE -- system code, won't try to do compat mode.
                    Point size = new Point();
                    mDisplay.getRealSize(size);
                    desiredWindowWidth = size.x;
                    desiredWindowHeight = size.y;
                } else {
                    DisplayMetrics packageMetrics = res.getDisplayMetrics();
                    desiredWindowWidth = packageMetrics.widthPixels;
                    desiredWindowHeight = packageMetrics.heightPixels;
                }
            }
        }

        // Ask host how big it wants to be
        windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight);
    }

    ...
    boolean windowShouldResize = layoutRequested && windowSizeMayChange
            && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
                || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.width() < desiredWindowWidth && frame.width() != mWidth)
                || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                        frame.height() < desiredWindowHeight && frame.height() != mHeight));
    ...

    if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) {
        ...
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        ...
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
        ...
    }
    ...
}

可以看看上述的关键代码,由于在ViewRootImpl的requestLayout中设置了mLayoutRequested为true,在一些boolean值的计算后,所以在performTraversal中可以进入走measure和layout,但是从invalidate中进入的performTraversal不会进入measure和layout。

而且由于surface是valid,所以也不会走到performDraw。

小结

requestLayout方法会层层递归到父view中,直至viewRootImpl,调用measure过程和layout过程,不会调用draw过程,也不会重新绘制任何View包括该调用者本身。

你可能感兴趣的:(invalidate和requestLayout流程认识)