The difference between the three

Let’s start with the differences between the three methods:

  1. Invalidate only calls the onDraw method and must be called in the UI thread
  2. PostInvalidate only calls the onDraw method, which can be called back in the UI thread
  3. RequestLayout calls onMeasure, onLayout, and onDraw(under certain conditions) methods

invalidate

We can refresh the page directly in the view with invalidate(), which actually calls invalidate(true). The method call is:

/** * Redraw the entire page if the View is visible, * this method must be called in the UI thread */ public voidinvalidate() {
    invalidate(true); } /** * This is where invalidate() actually happens, a full flush flushes the entire view cache, Public void invalidate(Boolean invalidateCache) {public void invalidate(Boolean invalidateCache) { InvalidateInternal (0, 0, mright-mleft, mbottom-mtop, mright-mtop, mright-mtop, mright-mtop, mright-mtop, mright-mtop, mright-mtop, mright-mtop) invalidateCache,true);
}
Copy the code

Int l, int t, int r, int b, Boolean invalidateCache, Boolean fullInvalidate

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
    if(mGhostView ! Mghostview.invalidate (= null) {// Check whether there is a layer and draw a layer mghostView.invalidate (true);
        return;
    }
    if (skipInvalidate()) {
        return;
    }
    
    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)) {// Invalidate () is called without argumentstrue
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;

            if(invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // Rect the redrawn area 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); } // Damage the entire projection receiver,if necessary.
            if(mBackground ! = null && mBackground.isProjected()) { final View receiver = getProjectionReceiver();if(receiver ! = null) { receiver.damageInParent(); }}}}Copy the code

In the current View:

  1. invalidateInternalThe layer View is refreshed when the View is refreshed, not as part of the parent View of the current View
  2. skipInvalidate: Check whether the View needs to skip drawing. The conditions to skip drawing include: The View is not visible, there are animated objects, the parent View is not ViewGroup, or is not in transition state.
private boolean skipInvalidate() {
        return(mViewFlags & VISIBILITY_MASK) ! = VISIBLE && mCurrentAnimation == null && (! (mParent instanceof ViewGroup) || ! ((ViewGroup) mParent).isViewTransitioning(this)); }Copy the code
  1. The next step is to determine if you need to redraw. You need to make sure that the current View is not executing the method: satisfy one of the following criteria
  • Animate or View size is not 0Copy the code
  • Full draw is required and draw cache is availableCopy the code
  • Not redrawnCopy the code
  • Transparency has changed since last timeCopy the code
  1. Determine whether a full redraw is required based on the input parameter, and set the draw cache unavailable flag to mPrivateFlags. During drawing, it will decide whether to use drawing cache according to this identifier. If it is a complete redrawing, it will skip drawing cache and use drawing cache to directly redraw the View to canvas.
  2. The next step is to call mParent’sinvalidateChildMethod to trigger the parent class to adjust the redrawn region (possibly the original region) and the relative coordinates of the changed region. The AttachInfo class stores information about the View when it is mounted to the parent View. The InvalidateInfo contains information about the current View, including the View to be redrawn and the coordinates of the region. The View will call the parent ViewinvalidateChildMethod and pass the redrawn area.

In the ViewGroup

 public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        if(attachInfo ! = null && attachInfo.mHardwareAccelerated) { onDescendantInvalidated(child, child);return;
        }

        ViewParent parent = this;
        if(attachInfo ! Final Boolean drawAnimation = (child.mprivateFlags &) {//drawAnimation records whether the child View calling the method is animating PFLAG_DRAW_ANIMATION) ! = 0; // Whether the child View calling this method is opaque: it is opaque and not animating and the change matrix is unchanged &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; ChildMatrix = child.getmatrix (); childMatrix = child.getmatrix (); final boolean isOpaque = child.isOpaque() && ! drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); // Make sure that there are no two flags int opaqueFlag = isOpaque? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;if(child.mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } / / record the parent View left the distance View boundary and the boundary of the distance to the Location, used in the next piece of code in calculating the final int [] Location = attachInfo. MInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; // If the subview has a transformation matrix, adjust the area to be refreshed according to the transformation matrixif(! childMatrix.isIdentity() || (mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ! = 0) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); Matrix transformMatrix;if((mGroupFlags & ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS) ! = 0) { Transformation t = attachInfo.mTmpTransformation; boolean transformed = getChildStaticTransformation(child, t);if (transformed) {
                        transformMatrix = attachInfo.mTmpMatrix;
                        transformMatrix.set(t.getMatrix());
                        if (!childMatrix.isIdentity()) {
                            transformMatrix.preConcat(childMatrix);
                        }
                    } else{ transformMatrix = childMatrix; }}else{ transformMatrix = childMatrix; } transformMatrix.mapRect(boundingRect); dirty.set((int) Math.floor(boundingRect.left), (int) Math.floor(boundingRect.top), (int) Math.ceil(boundingRect.right), (int) Math.ceil(boundingRect.bottom)); } // From the current layout View up through the parent layout of the current layout View, and finally through to ViewRootImpldo{ View view = null; // Parent may be of ViewGroup type or ViewRootImpl type // ViewRootImpl type when the last loop is executedif(parent instanceof View) { view = (View) parent; } // If the child View is performing animation, set the animation identity of the parent layout View traversedif (drawAnimation) {
                    if(view ! = null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; }else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true; }} // Set the redraw identifier of the current ViewGroup, indicating that the current ViewGroup needs to be redrawnif(view ! = null) {if((view.mViewFlags & FADING_EDGE_MASK) ! = 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; }if((view.mPrivateFlags & PFLAG_DIRTY_MASK) ! = PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } // Call the invalidateChildParent() method of the current layout View and return the value of the parent layout of the current layout View &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; / / cycle through call up, and finally return to the root of the layout is ViewRootImpl object 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);
        }
    }
Copy the code

Execute invalidateChild in ViewGroup.

  1. Initialize a variable:
  • Is it in animation
  • Opaque or not: Depending on whether the child View’s opacity needs to be redrawn, there is no animation and the redrawn area of the child View does not change
  • Record the relative coordinates of the child View relative to the parent ViewGroup: in order to redraw the area of the child View and ViewGroup can display the matrix to do intersection or phase processing
  1. Handle the effect of the comfort area of sub-view and ViewGroup transform object on the redraw matrix:
  • The static transformation object set by ViewGroup is processed, the transformation object set is obtained, and then the transformation matrix is obtained
  • Merge child’s original matrix with the acquired matrix
  • Apply the processed matrix to the Dirty matrix, and also apply the change matrix of Child to the Dirty matrix.
  1. The dowhile loop sets the ViewGroup property and handles the relationship between the child’s dirty matrix and the ViewGroup displayable matrix:invalidateChildInParent

There are two implementations of the loop: ViewRootImpl and ViewGroup both implement the ViewParent interface, making invalidateChildInParent have two implementations.

ViewGroup implemented
 public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) ! = 0) {// If ViewGroup has no animation executed or animation completedif((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ! FLAG_OPTIMIZE_INVALIDATE) {// Dirty is the area of the View where invalidate() was originally called. // Dirty's four coordinate values are determined relative to the current loop to the previous ViewGroup. &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; // The offset is the offset of the previous ViewGroup relative to the current ViewGroup &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; // After performing the following offsets, Dirty. Offset (location[CHILD_LEFT_INDEX] -mscrollx, location[CHILD_TOP_INDEX] - mScrollY); // If the current ViewGroup needs to crop view&emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; &emsp; // Merge the current ViewGroup area with the View areaif((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) { dirty.union(0, 0, mRight - mLeft, mBottom - mTop); } final int left = mLeft; final int top = mTop; // If the current ViewGroup needs to crop views and there is no union between the ViewGroup area and the View area, dirty is nullif ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if(! dirty.intersect(0, 0, mRight - left, mBottom - top)) { dirty.setEmpty(); } } location[CHILD_LEFT_INDEX] = left; location[CHILD_TOP_INDEX] = top; }else{// If the current ViewGroup has animations to perform // If subviews need to be clipped, set dirty to the current ViewGroup region // If not, combine the current ViewGroup region with the original ditry regionif ((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);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;

                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            if(mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; }return mParent;
        }

        return null;
    }
Copy the code
  • First check if animation exists or animation cache is available, execute only if this condition is met otherwise null is returned
  • When the animation is complete or there is no animation: Offsets the dirty position coordinates relative to the parent View’s viewregion origin, and updates Location to the ViewGroup’s relative coordinates relative to its ViewParent. Finally, return its ViewParent.
  • Location is the relative coordinates of the ViewGroup relative to its ViewParent. The child VIew animation will completely affect the ViewGroup rendering. InvalidateChildInParent () mainly completes the update of the dirty region in the ViewGroup that calls the method. Dirty indicates the region that needs to be redrawn. If the ViewGroup has no animation running, then the dirty area is still the same area. You just need to offset the coordinate value of the area from relative to the previous ViewGroup (parent ViewGroup) to relative to the current ViewGroup. If there are animations to be performed, then the entire ViewGroup needs to be redrawn, changing the dirty value to the current ViewGroup area.
ViewRootImpl implemented

Do – while the last cycle will end calls to ViewRootImpl. InvalidateChildInParent () method:

Public ViewParent invalidateChildInParent(int[] location, Rect dirty) {// Check whether the UI thread checkThread();if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if(dirty == null) {// If the redraw area is null, set dirty to the entire size of the redraw invalidate();return null;
        } else if(dirty.isEmpty() && ! MIsAnimating) {// Return null if the redraw area is empty and the animation is not presentreturn null;
        }

        if(mCurScrollY ! = 0 || mTranslator ! = null) { mTempRect.set(dirty); dirty = mTempRect;if(mCurScrollY ! // If ViewRoot is offset, mCurScrollY converts dirty positions to coordinates relative to ViewRoot's viewable area. }if(mTranslator ! = null) { mTranslator.translateRectInAppWindowToScreen(dirty); }if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }
        invalidateRectOnScreen(dirty);

        return null;
    }
Copy the code

This code does the following:

  • Check if it is a UI thread, if it does not throw an exception directly.
  • Check the redraw area.
  • Adds the repainted area to the area to be repainted.
  • Redraw tasks. Note:invalidate()Used in the method introductionThe refreshThe meaning expressed is equivalent toredrawChanged the method call:invalidateRectOnScreenMethods:
private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty;
        if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
            mAttachInfo.mSetIgnoreDirtyState = true;
            mAttachInfo.mIgnoreDirtyState = true;
        }

        // 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
        final float appScale = mAttachInfo.mApplicationScale;
        final boolean intersected = localDirty. Intersect (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));if(! intersected) {localDirty.setEmpty();
        }
      
        if (!mWillDrawSoon && (intersected || mIsAnimating)) {
            scheduleTraversals();
        }
    }
Copy the code

The scheduleTraversals() method is called, followed by the peformTraversals() method after a Vsync signal is requested. View’s invalidate does not cause the view’s invalidate to be called. Instead, it recursively calls the parent view’s invalidateChildInParent. Until ViewRootImpl’s invalidateChildInParent is triggered, then peformTraversals can be viewed for a more detailed source code analysis on the Invalidate () blog

postInvalidate

PostInvalidate can be called directly from a View, except that it can be called from a non-UI thread. Note that this method can only be called from outside the UI if the view is attached to the Window.

public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(Long delayMilliseconds) {final AttachInfo AttachInfo = mAttachInfo;if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
Copy the code

PostInvalidate calls the postInvalidateDelayed method, which accepts a delayed redraw time. The view tree is only notified to redraw when the view is sure to be added to the window, because this is an asynchronous method and an error occurs if the view is notified to redraw before it has been added to the window. Then call dispatchInvalidateDelayed, this method is defined in the ViewRootImpl:

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

DispatchInvalidateDelayed implements a message mechanism, sent an asynchronous messaging MSG_INVSLIDSTE to the main thread, and then look at the mHandler implementation logic:

final class ViewRootHandler extends Handler {
    public String getMessageName(Message message) {
        switch (message.what) {
            case MSG_INVALIDATE:
            return "MSG_INVALIDATE"; .return super.getMessageName(message);
        }
    }
     @Override
    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        if (msg.what == MSG_REQUEST_KEYBOARD_SHORTCUTS && msg.obj == null) {
                // Debugging for b/27963013
            throw new NullPointerException(
                    "Attempted to call MSG_REQUEST_KEYBOARD_SHORTCUTS with null receiver:");
        }
        return super.sendMessageAtTime(msg, uptimeMillis);
    }
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
            case MSG_INVALIDATE:
                ((View) msg.obj).invalidate();
            break; . }}}Copy the code

At this point, everything becomes clear. The argument Message is passed an instance of the View, and the invalidate method is called directly to continue the invalidate process. The invalidate refresh mechanism is the same as the postInvalidate refresh mechanism, except that postInvalidate invokes the Invalidate with a Handler that allows it to be invoked from a non-UI thread.

requestLayout

RequesetLayout executes only measure and Layout processes and does not call the Draw process to trigger a redraw.

public void requestLayout() {
        if(mMeasureCache ! = null) mMeasureCache.clear(); / / if the current process, the entire View tree in the layout are called requestLayoutDuringLayout () make the layout of the delayif(mAttachInfo ! = null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logicif 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; } //PFLAG_FORCE_LAYOUT determines that only the flag bit is set when executing the View's measure() and layout() methods. Will perform the measure () and layout () process mPrivateFlags | = PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED;if(mParent ! = null && ! MParent. IsLayoutRequested ()) {/ / ask the parent container layout mParent requestLayout (). }if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }
Copy the code

The whole process of the method is as follows: first determine whether the current View tree is laying out the process, and then set a marker bit for the current child View. The function of this marker bit is to mark the current View is to be relaid. Finally, call the requestLayout method of the parent View and keep calling the parent View until the ViewRootImpl. Until the code in ViewRootImpl looks like this:

  public void requestLayout() {
        if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code

The thread is checked first, and the scheduleTraversals method is called in the same way as invalidate. Followed by View drawing process three methods, measurement, layout, drawing.

conclusion

  • Invalidate() and postInvalidate() trigger redrawing of the View, which will eventually call performDraw() in performTraversals() to do the redrawing.
  • Both invalidate() and postInvalidate() are used to trigger the View’s update (redraw) action. The difference is that the invalidate() method is used in the UI thread itself, while postInvalidate() is used in the non-UI thread
  • The requestLayout method marks the current View and the parent container and submits it layer by layer until the ViewRootImpl handles the event. The ViewRootImpl calls three processes, starting with measure, OnMeasure, onLayout, onDraw (onMeasure, onLayout, onDraw) will be carried out for each view containing marker bits and its sub-views.
  • RequestLayout directly recursively calls the requestLayout of the parent window until ViewRootImpl, which then triggers peformTraversals, and since mLayoutRequested is true, OnMeasure and onLayout will be called. It doesn’t have to trigger OnDraw
  • RequestLayout will trigger an invalidate when it detects that L, T, R, or B is different from the previous one, in which case it will call onDraw
  • Invalidate is called whenever a refresh is required and requestLayout is called whenever a new measure is required

Finally, the source analysis of the three methods only goes to scheduleTraversals(), which involves the View rendering process. As for the drawing process of a View, keep an eye on the drawing Process of an Android View.