Resolving the relationship between computescroll () method and invalidate () from source code

This article is about custom controls. Why did I write this article? It's also because I think of something when I write a control of a custom broadcast chart, which makes me want to write this article. After writing the rotation map, organize the ideas and run the process again. It feels like there are still a lot of things. I want to understand this. I'll know more about customizing my own view in the future.
In this article, I will focus on the relationship between computeScroll () method and invalidate (). At first glance, it doesn't matter. In fact, at the bottom, it doesn't matter much, just when our hardware triggers the rendering operation. Let's put this in the back. Let's first look at the computeScroll () method. It is called by the draw () method of view. Let's see what the draw () method does.

            /**
             * This method is called by ViewGroup.drawChild() to have each child view draw itself.
             *
             * This is where the View specializes rendering behavior based on layer type,
             * and hardware acceleration.
            */  
            boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
            final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();

            boolean drawingWithRenderNode = mAttachInfo != null
                    && mAttachInfo.mHardwareAccelerated
                    && hardwareAcceleratedCanvas;

            ......

            int sx = 0;
            int sy = 0;
            if (!drawingWithRenderNode) {
                computeScroll();
                sx = mScrollX;
                sy = mScrollY;
            }

            ......

            return more;
        }

This method has a lot of code. We only look at the key part. We can see that to execute the computeScroll() method, the value of drawingWithRenderNode must be false, and the value of drawingWithRenderNode is controlled by the above three conditions. This mAttachInfo is a class encapsulating many attributes of view, so it is not empty, and then mHardware Accelerated of view. It means whether he provides hardware acceleration when drawing. We usually default that it is not turned on. Even if it is turned on, it's okay. We have a third condition to control. Let's see how the third condition comes from. We see that the first line of the method is there. He represents whether canvas is hardware acceleration. Let's go in and see if it's true.

public boolean isHardwareAccelerated() {
        return false;
    }

We see that the default is false, that is to say, our canvas does not turn on hardware acceleration, so hardware Accelerated Canvas; this value is for false, then we will execute the computeScroll() method anyway. OK implements the computeScroll() method in the draw () method, so where will we execute the draw () method? Let's go on exploring. We see the annotations of the draw () method. I don't think we need to explain this too much. It means that the drawChild () method of the viewgroup is executed, so let's go and see.

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            return child.draw(canvas, this, drawingTime);
        }

Sure enough, but the code for this method is too... No, but it's straightforward, haha, so we have to go ahead and see where the drawChild () method was called and find that it was called by the dispatchDraw(Canvas canvas) method of the viewgroup. So let's continue to see who called dispatchDraw ()? The discovery is called by the draw () method of view, but note that this draw () method is not the other draw () method. This draw () method is scheduled by the hardware of the system (or the system is scheduled by the thread). So its source is over (because the code of the system layer is going to the inner layer). OK, the entire call to this computeScroll() method has been analyzed.
Let's continue to look at the invocation of invalidate () method.

public void invalidate() {
        invalidate(true);
    }

We continue to look for:

void invalidate(boolean invalidateCache) {
        invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
    }  

Or a line of code. Let's go on looking for it.

```

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        if (mGhostView != null) {
            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)) {
            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);
            }

            // Damage the entire projection receiver, if necessary.
            if (mBackground != null && mBackground.isProjected()) {
                final View receiver = getProjectionReceiver();
                if (receiver != null) {
                    receiver.damageInParent();
                }
            }

            // Damage the entire IsolatedZVolume receiving this view's shadow.
            if (isHardwareAccelerated() && getZ() != 0) {
                damageShadowReceiver();
            }
        }
    }

Finally, it's not a line of code. We've found an important place, p.invalidateChild(this, damage), where we draw the corresponding child view. We see that P calls it. So let's see what this p is. Let's look for the source of this p.

/*
     * Caller is responsible for calling requestLayout if necessary.
     * (This allows addViewInLayout to not request a new layout.)
     */
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    }

If we find that the value of p is assigned here, when will this method be called? Let's look at it in ViewGroup first.

private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

       ...

        // tell our children
        if (preventRequestLayout) {
            child.assignParent(this);
        } else {
            child.mParent = this;
        }

      ...
    }   

Find that it's actually called here, but don't worry, let's see if this method is actually implemented. Let's go and find the caller of addViewInner ()?

public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }      

In the last line, we see that it is called, but the last parameter assignment is false, what the hell, that is, child.assignParent(this) is not executed.
So assignParent () is not executed in ViewGroup. Don't worry. This method is not only executed in this place, but also in ViewRootImpl.

 /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
           ......
                view.assignParent(this);
           .....
    } 

Seeing this, we are halfhearted (I'm afraid there's no place to execute it, haha). So where is this setView executed? Actually, it was activated by Activity.
Reference for process execution http://blog.csdn.net/u013866845/article/details/54408825 In this article, we find that the value of mParent is
ViewRootImpl. OK, the long search for "father" is finally over, let's go back here:

if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b);
                p.invalidateChild(this, damage);
            }

That is to say, this p is ViewRootImpl. Let's look at the invalidateChild () method of ViewRootImpl.

 @Override
    public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }

Here we are again. Let's keep looking.

@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            return null;
            invalidate();
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }   

Find that invalidateRectOnScreen(dirty) method is finally invoked anyway, and go on:

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();
        }
    }    

Because our View is being redrawn, this if condition is valid (we won't continue to look for it here), so we go into the scheduleTraversals method.

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

Yagi is very familiar with the rush, originally here through the thread to perform the redrawing task regularly, said that we have to mention the startScroll () when the code.
Actually, there is a default time when startScroll (), that is to say, in this default time to execute a series of redrawing actions, so this line
During this period of time, Cheng will continue to trigger the redraw action, that is, draw () action, which is also the redraw of our Vieww. At this point, we also know the whole process. We finally unveiled the whole process of computescroll () method and invalidate (). I believe we have a clearer understanding of this series after reading it.
I hope this article will be helpful to you, and I hope you can correct the shortcomings. Thank you!! __________.

Posted by smartsley on Tue, 18 Jun 2019 15:09:03 -0700