preface

In the last article, we talked about the touch event transfer mechanism of Android. In addition, the drawing process of Android View is also the core knowledge of View. As we all know, PhoneWindow is the most basic window system in Android, and one is created for each Activity. PhoneWindow is also the interface for the Activity to interact with the View system. A DecorView is essentially a FrameLayout that is the ancestor of all views in your Activity.

Start: The DecorView is loaded into the Window

Starting with the Activity’s startActivity, we call the ActivityThread’s handleLaunchActivity method to create the Activity.

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { .... A = performLaunchActivity(r, customIntent); a = performLaunchActivity(r, customIntent); if (a ! = null) { r.createdConfig = new Configuration(mConfiguration); Bundle oldState = r.state; handleResumeActivity(r.tolen, false, r.isForward, ! r.activity.. mFinished && ! r.startsNotResumed); } } final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { unscheduleGcIdler(); mSomeActivitiesChanged = true; ActivityClientRecord r = performResumeActivity(token, clearHide); if (r ! = null) { final Activity a = r.activity; . if (r.window == null &&& ! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); // getDecorView View decor = r.window.getdecorview (); decor.setVisibility(View.INVISIBLE); ViewManager ViewManager wm = a.getwinDowManager (); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; // The WindowManager implementation class is WindowManagerImpl, // the WindowManagerImpl addView method wm.addView(decor, L); } } } } public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); . @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); }... }Copy the code

Before we look at the overall process of drawing a View, we must first understand the concepts of View wroot and DecorView. ViewRoot corresponds to the ViewRootImpl class, which is the link between Windows Manager and the DecorView, and all three of the View’s processes are done through ViewRoot. In ActivityThread, when the Activity object is created, the DecorView is added to the Window, the ViewRootImpl object is created, and the ViewRootImpl object is associated with the DecorView.

// WindowManagerGlobal addView method public void addView(View View, ViewGroup.LayoutParams params, Display Display, Window parentWindow) { ... ViewRootImpl root; View pannelParentView = null; synchronized (mLock) { ... Root = new ViewRootImpl(view.. getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } try {// Load the DecorView into the Window root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; }}Copy the code

Second, understand the overall process of drawing

Drawing starts with the performTraversals() method of the root View ViewRoot and traverses the tree from top to bottom. Each View control paints itself, and the ViewGroup tells its children to draw. The core code for performTraversals() is as follows.

private void performTraversals() { ... int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . PerformMeasure (childWidthMeasureSpec, childHeightMeasureSpec); . // Execute the layout process performLayout(LP, desiredWindowWidth, desiredWindowHeight); . // Execute the draw process performDraw(); }Copy the code

PerformTraversals working flow diagram is shown below:

If not, click here

Note:

  • PreformLayout and performDraw are similar to performMeasure, except that performDraw is delivered via dispatchDraw in the draw method.

  • Access to the content:

    ViewGroup content = (ViewGroup)findViewById(android.R.id.content);

  • Get the View set:

    content.getChildAt(0);

MeasureSpec

1.MeasureSpec source code analysis

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec is a static inner class of the View class that describes how the View should be measured. The core code for MeasureSpec is as follows.

public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0X3 << MODE_SHIFT; // The measurement mode is not specified. The parent view does not limit the size of the child view. The child view can be any size you want. public static final int UNSPECIFIED = 0 << MODE_SHIFT; // In this mode, the measurement of the View is the value of SpecSize, which is used when the width and height of the View are match_parent or a specific value. public static final int EXACTLY = 1 << MODE_SHIFT; Max measurement mode, when the width and height of the view is specified as wrap_content, in which case the size of the child view can be any size that does not exceed the maximum size allowed by the parent view. public static final int AT_MOST = 2 << MODE_SHIFT; // Create a MeasureSpec(MeasureSpec) public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); Static int adjust(int MeasureSpec, int delta) {final int mode = MeasureSpec; if (mode == UNSPECIFIED) { // No need to adjust size for UNSPECIFIED mode. return make MeasureSpec(0, UNSPECIFIED); } int size = getSize(measureSpec) + delta; if (size < 0) { size = 0; } return makeMeasureSpec(size, mode); }}Copy the code

MeasureSpec is a MeasureSpec that packages SpecMode and SpecSize into a single int to avoid too much object memory allocation. For ease of operation, the MeasureSpec package is makeMeasureSpec.

public static int getMode(int measureSpec) {
    return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
    return (measureSpec & ~MODE_MASK);
}
Copy the code
= MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec
ChildWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lP.width); // desiredWindowHeight is the size of the screen.  childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); private static int getRootMeaureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATRCH_PARENT: // Window can't resize. Force root view to be windowSize. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; Case ViewGroup. LayoutParams. WRAP_CONTENT:  // Window can resize. Set max size for root view. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break default: // Window wants to be an exact size. Force root view to be that size. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }Copy the code
3. The MeasureSpec creation process for child elements
// ViewGroup measureChildWithMargins protected void measureChildWithMargins(View Child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // The child element's MeasureSpec is created in relation to the parent container's MeasureSpec and the child element's LayoutParams. Final int childWidthMeasureSpec = getChildChildMeasurespec (parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec( parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height); child.. measure(childWidthMeasureSpec, childHeightMeasureSpec); } public static int getChildMeasureSpec(int spec, int padding, int childDimesion) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // padding-size = math.max (0, specsize-padding); // padding-size = math.max (0, specsize-padding); int resultSize = 0; int resultMode = 0; switch (sepcMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimesion == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger  than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... // find out how big it should be resultSize = 0; resultMode == MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }Copy the code

The rules for creating a MeasureSpec for a normal View are as follows:

Note: The UNSPECIFIED mode is mainly used for multiple measures in the system, and generally does not need attention.

Conclusion: For a DecorView, its MeasureSpec is determined by the window size and its own LayoutParams; For a normal View, its MeasureSpec is determined by the parent’s MeasureSpec and its own LayoutParams.

4. Measure of View drawing process

1. Basic process of Measure

According to the previous analysis, the page measurement process starts from performMeasure method, and the related core code process is as follows.

private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { ... Measure (childWidthMeasureSpec, childHeightMeasureSpec); . MeasureChildren (int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; If ((child.mViewFlags & VISIBILITY_MASK)! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); MeasureChild (View Child, int parentWidthMeasureSpec, int parentWidthMeasureSpec) int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); ChildWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); } // Public final void measure(int widthMeasureSpec, int heightMeasureSpec) {... // ViewGroup does not define the measurement process, because the ViewGroup is an abstract class, the measurement process of the onMeasure method requires each child to implement onMeasure(widthMeasureSpec, heightMeasureSpec);  . } // Different ViewGroup subclasses have different layout characteristics, which results in different measurement details. If you need to customize the measurement process, Protected void onMeasure(int widthMeasureSpec, Int heightMeasureSpec) {// The setMeasureDimension method is used to set the measureDimension of the View setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } // If the View does not override the onMeasure method, Public static int getDefaultSize(int size, int measureSpec) {int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = sepcSize; break; } return result; }Copy the code
GetSuggestMinimumWidth. 2
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}

protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
Copy the code

If the View does not have a background, it returns the value specified by the Android :minWidth property, which can be 0; If the View has a background, it returns the maximum of android:minWidth and the minimum background width.

3. The case of manually processing wrAP_content in custom View

Controls that inherit directly from the View need to override the onMeasure method and set the size of wrap_content itself, otherwise using wrap_content in the layout is equivalent to using match_parent. Solutions are as follows:

protected void onMeasure(int widthMeasureSpec, int height MeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int widtuhSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); Int heightSpecSize = MeasureSpec.AT_MOST &&heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mWidth, mHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(mWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasureDimension(widthSpecSize, mHeight); }}Copy the code
4. The onMeasure method of LinearLayout is implemented and analyzed
protected void onMeasure(int widthMeasureSpec, int hegithMeasureSpec) { if (mOrientation == VERTICAL) { measureVertical(widthMeasureSpec, heightMeasureSpec); } else { measureHorizontal(widthMeasureSpec, heightMeasureSpec); // See how tall everyone is. Also remember Max width. For (int I = 0; i < count; ++i) { final View child = getVirtualChildAt(i); . // Determine how big this child would like to be. If this or // previous children have given a weight, then we allow it to // use all available space (and we will shrink things later // if need) measureChildBeforeLayout( child, i, widthMeasureSpec, 0, heightMeasureSpec, totalWeight == 0 ? mTotalLength : 0); if (oldHeight ! = Integer.MIN_VALUE) { lp.height = oldHeight; } final int childHeight = child.getMeasuredHeight(); final int totalLength = mTotalLength; mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }Copy the code

MeasureChildBeforeLayout (measureChildBeforeLayout, measureChildBeforeLayout, measureChildBeforeLayout, measureChildBeforeLayout, measureChildBeforeLayout) The system uses the mTotalLength variable to store the initial vertical height of the LinearLayout. MTotalLength will increase every time a child element is measured, including the height of the child element and the vertical margin of the child element.

// Add in our padding mTotalLength += mPaddingTop + mPaddingBottom; int heightSize = mTotalLength; // Check against our minimum height heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Reconcile our calculated size with the heightMeasureSpec int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); HeightSize = heightSizeAndState & MEASURED_SIZE_MASK; . setMeasuredDimension(resolveSizeAndSize(maxWidth, widthMeasureSpec, childState), heightSizeAndState); public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { int result = size; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; Case MeasureSpec. AT_MOST: / / height should not exceed the surplus space of the parent container if (specSize < size) {result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; } return result | (childMeasuredState & MEASURED_STATE_MASK); }Copy the code
5. Get the width and height of a View in the Activity

Since the View’s measure procedure and the Activity’s lifecycle methods are not executed synchronously, the width/height obtained is 0 if the View has not finished measuring. Therefore, the width and height of a View cannot be correctly obtained in onCreate, onStart and onResume. Solutions are as follows:

  • Activity/View#onWindowFocusChanged

    // The Activity window is called once when it gets focus and when it loses focus. // If onResume and onPause are used frequently, Public void onWindowFocusChanged(Boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasureWidth(); int height = view.getMeasuredHeight(); }}

  • view.post(runnable)

    // Post a runnable to the end of the message queue and wait for Looper to call runnable Protected void onStart() {super.onstart (); view.post(new Runnable() {

    @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); }});Copy the code

    }

  • ViewTreeObserver

    Protected void onStart() {super.onstart ();

      ViewTreeObserver observer = view.getViewTreeObserver();
      observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
          
          @SuppressWarnings("deprecation")
          @Override
          public void onGlobalLayout() {
              view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
              int width = view.getMeasuredWidth();
              int height = view.getMeasuredHeight();
          }
      });
    Copy the code

    }

  • View.measure(int widthMeasureSpec, int heightMeasureSpec)

5, View drawing process Layout

1. Basic Layout process
// ViewRootImpl.java private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ... host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); . } // View.java public void layout(int l, int t, int r, int b) { ... Boolean changed = isLayoutModeOptical(mParent)? set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b); . onLayout(changed, l, t, r, b); . } // empty method, subclass ViewGroup, override this method, Protected void onLayout(Boolean changed, int left, int top, int right, int bottom) {}Copy the code
2.LinearLayout onLayout method implementation analysis
protected void onlayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else {layoutHorizontal(l,)}} void layoutVertical(int left, int top, int right, int bottom) { ... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() ! = GONE) { final int childWidth = child.getMeasureWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); . if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; SetChildFrame (Child, childLeft, childTop + getLocationOffset(Child), childWidth, childHeight); ChildTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child,i) } } } private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }Copy the code

Note: In the default implementation of a View, the measurement width/height and final width/height are the same, except that the measurement width/height is formed in the measure process of the View, while the final width/height is formed in the layout process of the View. In some special cases the two are not equal:

  • Rewrite the View layout method so that the final width is always 100px wider/taller than measured

    public void layout(int l, int t, int r, int b) { super.layout(l, t, r + 100, b + 100); }

  • How long does a View need to measure to determine its measurement width/height? In the first few measurements, the measured width/height may be inconsistent with the final width/height, but in the end, the measured width/height is the same as the final width/height

6. Draw the process of View

1. The basic process of Draw
private void performDraw() { ... draw(fullRefrawNeeded); . } private void draw(boolean fullRedrawNeeded) { ... if (! drawSoftware(surface, mAttachInfo, xOffest, yOffset, scalingRequired, dirty)) { return; }... } private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scallingRequired, Rect dirty) { ... mView.draw(canvas); . } public void draw(Canvas Canvas) {... DrawBackground (canvas); drawBackground(canvas); . // Step 2: Keep the canvas layer if necessary, and prepare for fading saveCount = canvas.getSavecount (); . canvas.saveLayer(left, top, right, top + length, null, flags); . // Step 3: Draw the View's content onDraw(canvas); . // Step 4: Draw the child View dispatchDraw(canvas); . DrawRect (left, top, right, top + Length, p); drawRect(left, top, right, top + Length, p); . canvas.restoreToCount(saveCount); . OnDrawForeground (canvas)}Copy the code
2. SetWillNotDraw role
// If a View does not need to draw anything, then setting this flag bit to true will optimize it accordingly. public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }Copy the code
  • By default, the optimization bit is not enabled for a View, but it is enabled for a ViewGroup by default.
  • When our custom control inherits from the ViewGroup and does not have the ability to draw itself, we can turn on this flag bit to facilitate the system for subsequent optimization.
  • When we explicitly know that a ViewGroup needs to draw with onDraw, we need to explicitly turn off the WILL_NOT_DRAW flag.

Seven,

View drawing process and event distribution mechanism are the core knowledge in The Development of Android, but also the internal work of a customized View master. For an excellent Android development, mainstream source analysis and Android core source code analysis can be said to be a required course, the next article, will lead you to further in-depth Android.

Reference links:

1. Android development art exploration

2. Android advanced Light

3, Android advanced advanced

4. View drawing process and source code analysis of Android application layer

5. Brief analysis of View drawing process in Android


Thank you for reading this article and I hope you can share it with your friends or technical group, it means a lot to me.