View drawing process

  • Q: Now that you know how activity_main.xml is added to the DecorView, how is this DecorView added to the Window?
  • A quick review of Phonewindow#setCountView

1. Create a DecorView. 2. Make sure that the parent layout of activtiy is #generateLayout. In the generateLayout method, determine the layoutResource(system-default XML cloth) bureau 4 by judgment. Found in layoutResource id for the com. Android. Internal, R.i, dc ontent of ViewGroup mContentParent assignment 5. LayoutResource is added to DecorView 6 via the mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource) method layoutResource. Finally, add activity_main to mContentParent, which completes the process without adding the DecorView to the window step.

  • About LayoutInflater# inflate
  • We parse the XML, instantiate the view by name reflection using LayoutInflater#createViewFromTag and add it to the ViewGroup root, Child Views are added to the ViewGroup by finding views through the rInflateChildren loop

Formal analysis

  • Go directly to ActivityThread#handleResumeActivity
@Override public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration // TODO Push resumeArgs into the activity for consideration // TODO Push resumeArgs into the activity for consideration MInstrumentation callActivityOnResume / / this is onResume is the page can be seen that the final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); if (r == null) { // We didn't actually resume the activity, so skipping any follow-up actions. return; } if (mActivitiesToBeDestroyed.containsKey(token)) { // Although the activity is resumed, it is going to be destroyed. So the following // UI operations are unnecessary and also prevents exception because its token may // be gone that window manager cannot recognize it. All necessary cleanup actions // performed below will be done while handling destruction. return; } final Activity a = r.activity; if (localLOGV) { Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); } final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = ! a.mStartedActivity; if (! willBeVisible) { try { willBeVisible = ActivityTaskManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); If (r.window == null &&!) {// If (r.window == null &&! A.finished &&willbevisible) {// get phoneWindow R.window = R.acty.getwindow (); // Get phoneWindow DecorView View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl ! = null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (! a.mWindowAdded) { a.mWindowAdded = true; ViewManager wm = a.getwinDowManager (); ViewManager wm = a.getwinDowManager ();  // Active # Attach method -- "mWindowManager = mwindow.getwinDowManager (); / / Window# setWindowManager - "mWindowManager = ((WindowManagerImpl) wm). CreateLocalWindowManager (this); wm.addView(decor, l); } else { //.... }} / /.. }Copy the code
  • Go to WindowManagerImpl#addView, where WindowManagerGlobal#addView is called again
@Override public void addView(@NonNull View view, @nonnull viewGroup. LayoutParams params) {// Set the default token applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId()); }Copy the code
  • Enter the WindowManagerGlobal# addView
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) { //... Omit ViewRootImpl root; View panelParentView = null; synchronized (mLock) { //... // Create ViewRootImpl root = new ViewRootImpl(view.getContext(), display); // Set the LayoutParams message to DecorView view.setLayoutParams(wparams); // Save DecorView mviews.add (view); // Save ViewRootImpl mroots.add (root); / / save WindowManager. LayoutParams mParams. Add (wparams); // do this last because it fires off messages to start doing things try { //ViewRootImpl#setView root.setView(view, wparams, panelParentView, userId); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up. if (index >= 0) { removeViewLocked(index, true); } throw e; }}}Copy the code
  • Enter ViewRootImpl# setView, length is too long to keep only the key code requestLayout () and mWindowSession addToDisplayAsUser
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) { synchronized (this) { if (mView == null) { //.. // Any other events from the system.requestLayout (); / /... Omit try {morigwintributes; / / add the window to the WMS above to make a mark here. Keep an eye on the res = mWindowSession addToDisplayAsUser (mWindow mSeq, mWindowAttributes, getHostVisibility (), mDisplay.getDisplayId(), userId, mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mDisplayCutout, inputChannel, mTempInsets, mTempControls); setFrame(mTmpFrame); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; inputChannel = null; mFallbackEventHandler.setView(null); unscheduleTraversals(); setAccessibilityFocus(null, null); throw new RuntimeException("Adding window failed", e); } finally { if (restore) { attrs.restore(); }} // Set mParent view.assignparent (this); / /... Omit}}}Copy the code
  • RequestLayout () {ViewRootImpl#requestLayout() {requestLayout() {ViewRootImpl#requestLayout() {ViewRootImpl#requestLayout() {ViewRootImpl#requestLayout() {ViewRootImpl#requestLayout() {ViewRootImpl#requestLayout() {ViewRootImpl#requestLayout(
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }
     
Copy the code
  • The checkThread has a classic question: why can’t the CHILD thread update the UI?
  • PrepareMainLooper (); prepareMainLooper(); prepareMainLooper(); prepareMainLooper(); HandleResumeActivity creates viewrotimpl on the main thread. Strictly speaking, you cannot update the UI on different threads.
 void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
Copy the code
  • ViewRootImpl#scheduleTraversals, then scheduleTraversals
  • This is also familiar, looking for a screen refresh mechanism on the Internet will mention this. This method is the most important method of screen refresh, recommend a related article
@UnsupportedAppUsage void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; / / here to create a message / / synchronization barrier synchronization barrier [about] mTraversalBarrier = (https://blog.csdn.net/start_mao/article/details/98963744) mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code
  • So if we go back to the body, we pass in a Runnable, so we end up with this doTraversal()
final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; Mhandler.getlooper ().getQueue().removesyncBarrier (mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } // performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; }}}Copy the code
  • Look at the performTraversals method
Private void performTraversals () {/ / code too much Keep only the key code windowSizeMayChange | = measureHierarchy (host, lp, res, desiredWindowWidth, desiredWindowHeight); / /... performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); / /... performLayout(lp, mWidth, mHeight); / /... performDraw() //... }Copy the code
  • #measureHierarchy three predictions are made here, and two when the view width is set to WRAP_CONTENT
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { int childWidthMeasureSpec; int childHeightMeasureSpec; boolean windowSizeMayChange = false; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag, "Measuring " + host + " in display " + desiredWindowWidth + "x" + desiredWindowHeight + "..." ); boolean goodMeasure = false; if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) { // On large screens, we don't want to allow dialogs to just // stretch to fill the entire width of the screen to display // one line of text.  First try doing the layout at a smaller // size to see if it will fit. final DisplayMetrics packageMetrics = res.getDisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); int baseSize = 0; if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = (int)mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize + ", desiredWindowWidth=" + desiredWindowWidth); if (baseSize ! ChildWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // First measure performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec) + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec)); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = true; } else {// Didn't fit in that size... try expanding a bit. baseSize = (baseSize+desiredWindowWidth)/2; if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize=" + baseSize); childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); // The second measure is performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured (" + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")"); if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) { if (DEBUG_DIALOG) Log.v(mTag, "Good!" ); goodMeasure = true; } } } } if (! ChildWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childWidthMeasureSpec (desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // The third performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight()) { windowSizeMayChange = true; } } if (DBG) { System.out.println("======================================"); System.out.println("performTraversals -- after measure"); host.debug(); } return windowSizeMayChange; }Copy the code
  • According to the above three measurement conditions, we can add the following measurement at most 4 times and at least 2 times. According to the predicted method, it will be faster to set width not to WRAP_CONTENT (manual head).
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); Mview. measure-> mview. measure(childWidthMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code
  • Here mView.measure is actually a DecorView, so the onMeasure actually calls DecorView#onMeasure and then the DecorView inherits from FrameLayout, Call super.onMeasure(widthMeasureSpec, heightMeasureSpec); So go straight to FrameLayout’s onMeasure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); / / here is a premise to onMeasure force rendering layouts, or need to refresh the if (forceLayout | | needsLayout) {/ / first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); / / here look at the number of cache is less than 0 or omitted cache (less than sdk19 ignore cache) if (cacheIndex < 0 | | sIgnoreMeasureCache) {/ / measure ourselves. OnMeasure (widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) ! = PFLAG_MEASURED_DIMENSION_SET) {// If the onMeasure method is overwritten, setMeasuredDimension or super.onMeasure will throw an exception. Because mPrivateFlags is set at setMeasuredDimension, Throw new IllegalStateException("View with ID "+ getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; / / added to the cache list mMeasureCache. Put (key, ((long) mMeasuredWidth) < < 32 | (long) mMeasuredHeight & 0 XFFFFFFFFL); Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); final boolean measureMatchParentChildren = MeasureSpec.getMode(widthMeasureSpec) ! = MeasureSpec.EXACTLY || MeasureSpec.getMode(heightMeasureSpec) ! = MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() ! = GONE) { //... }} //... SetMeasuredDimension (resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { for (int i = 0; i < count; i++) { //... Set the size of the child view by MATCH_PARENT or by getChildMeasureSpec child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}}Copy the code
  • Door-to-door is the measurement process, first predict the width and height, no matter what the result is to measure again to make sure ah, and then loop to call the sub-view measurement, okay
  • After the measurement, take a look at performLayout, here the idea is the same as the above measurement
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mScrollMayChange = true; mInLayout = true; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); Host. layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); / /.. } / /.. }Copy the code
  • Host is a DecorView and it inherits FrameLayout, and FrameLayout is viewGroup
  • viewGroup#layout->View#layotu->View#onLayout
  • So you end up doing FrameLayout#onlayout
//view#layout public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! = 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; Boolean changed = isLayoutModeOptical(mParent)? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); / /... } / /... } //View#setFrame protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } if (mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed = true; / /... // invalidate our old position invalidate(sizeChanged); / /.. } return changed; } //FrameLayout#onlayout @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } //FrameLayout#layoutChildren void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); // Call the layout for the child view (int I = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() ! = GONE) { //... ChildLeft, childTop, childLeft + width, childTop + height); }}}Copy the code
  • Here the drawing process and the layout process are the same routine, look at performDraw
private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && ! mReportNextDraw) { return; } else if (mView == null) { return; } / /... 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) { Surface surface = mSurface; / /... mAttachInfo.mTreeObserver.dispatchOnDraw(); / /... if (! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { if (mAttachInfo.mThreadedRenderer ! = null && mAttachInfo.mThreadedRenderer.isEnabled()) { //... / / 1. This will eventually go the ontouch mAttachInfo. MThreadedRenderer. The draw (mView, mAttachInfo, this); } else {//2. Here will finally go view.ondraw if (! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) { return false; } } } if (animating) { mFullRedrawNeeded = true; scheduleTraversals(); } return useAsyncReport; }Copy the code
  • The viewrotimpl_draw method is used to draw the view. OnDraw method
//ThreadedRenderer#draw void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) { final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer; choreographer.mFrameInfo.markDrawStart(); / /.. Focused on this approach is mainly the updateViewTreeDisplayList (view) / / updateRootDisplayList (view, callbacks); / /... } / / save space jump straight to updateViewTreeDisplayList private void updateViewTreeDisplayList View (View) {the mPrivateFlags | = View.PFLAG_DRAWN; view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED) == View.PFLAG_INVALIDATED; view.mPrivateFlags &= ~View.PFLAG_INVALIDATED; / / main look here at this point of view or DecorView the updateDisplayListIfDirty (); view.mRecreateDisplayList = false; } //DecorView and FrameLayout and viewGroup are not overridden for updateDisplayListIfDirty, View#updateDisplayListIfDirty public RenderNode updateDisplayListIfDirty() {//... final RecordingCanvas canvas = renderNode.beginRecording(width, height); Try {if (layerType == LAYER_TYPE_SOFTWARE) {// Hardware acceleration is turned off // Draw the view as bitmap buildDrawingCache(true); Bitmap cache = getDrawingCache(true); if (cache ! = null) { canvas.drawBitmap(cache, 0, 0, mLayerPaint); } } else { computeScroll(); canvas.translate(-mScrollX, -mScrollY); mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; // Fast path for layouts with no backgrounds if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { // "SKIP_DRAW" = "SKIP_DRAW" = "SKIP_DRAW"; drawAutofilledHighlight(canvas); if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().draw(canvas); } if (isShowingLayoutBounds()) { debugDrawFocus(canvas); }} else {// draw(canvas); } } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); } } else { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; } return renderNode; }Copy the code
  • Let’s see what the draw method does
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) * 7. If necessary, draw the default focus highlight */ // Step 1, draw the background, if needed int saveCount; drawBackground(canvas); // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! horizontalEdges) { // Step 3, draw the content onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); drawAutofilledHighlight(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (isShowingLayoutBounds()) { debugDrawFocus(canvas); } // we're done... return; } / /.. }Copy the code
  • So let’s see how dispatchDraw draws child views,dispatchDraw is an abstract method so look for DecorView and FrameLayout, don’t find them so let’s go ahead and look for ViewGroup, ok
@Override protected void dispatchDraw(Canvas canvas) { //.. for (int i = 0; i < childrenCount; i++) { //... / / loop here to call the child view the draw method of final int childIndex = getAndVerifyPreorderedIndex (childrenCount, I, customOrder); final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); } } } //drawChild protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }Copy the code
  • Above viewrotimpl #draw calls the drawSoftware method, the same thing
  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    //...
        try {
          //...
            mView.draw(canvas);

            drawAccessibilityFocusedDrawableIfNeeded(canvas);
        } finally {
        //..
           
        }
        return true;
    }
Copy the code
  • Measurement drawing here, the process is finished in setview mWindowSession. AddToDisplayAsUser is to add Windows to WMS shows. Through the assignParent (this); DecorView has a parent class, so ViewRootImpl is the parent of the entire interface.

Analyze requestLayout and Invalidate

What happens when xxxView#requestLayout is called
//View#requestLayout 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; }}Copy the code
  • The ViewRootImpl is the parent of the entire page, so this code is actually recursively looking for ViewRootImpl and executing requestLayout.
  • DecorView -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView) -> ViewGroup (DecorView)
  • OnDraw () is usually triggered because l, T, R,b are not the same as before or the animation is executing
  • The flow chart of requestLayout

What happens when xxxView#invalidate is called
//view#invalidate public void invalidate() { invalidate(true); } @UnsupportedAppUsage public void invalidate(boolean invalidateCache) { invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) { if (mGhostView ! = null) { mGhostView.invalidate(true); return; } if (skipInvalidate()) { return; } // Reset content capture caches mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK; mContentCaptureSessionCached = false; 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)) { //... 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); } / /... } } //viewgroup#invalidateChild @Deprecated @Override public final void invalidateChild(View child, final Rect dirty) { //... ViewParent parent = this; / /... do { View view = null; if (parent instanceof View) { view = (View) parent; } / /... Focus on the parent here = parent. InvalidateChildInParent (location, dirty); / /.. } while (parent ! = null); }}Copy the code
  • Invoke the invalidate will eventually cycle call parent. InvalidateChildInParent. So it will find the page dad ViewRootImpl execute invalidateChildInParent similar to the logic above. The viewGroup invalidateChildInParent is just setting the size of the dirty area
//ViewRootImpl#invalidateChildInParent public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty); If (dirty == null) {// scheduleTraversals invalidate(); return null; } else if (dirty.isEmpty() && ! mIsAnimating) { return null; } / /... Go scheduleTraversals invalidateRectOnScreen (dirty); return null; }Copy the code
  • ScheduleTraversals again doduleTraversals ->performTraversals and then again measure, layout and draw
  • Invalidate the flow chart

  • That’s the end of it