1 Relationship between Activity and Window, PhoneWindow, and DecorView

  • Each Activity holds a Window object
  • Window is an abstract class that provides a common set of apis for drawing Windows. Can be thought of as a display View carrier.
  • PhoneWindow is the only implementation class for Window. The Window instance in the Activity is a PhoneWindow object.
  • The DecorView class is an inner class of the PhoneWindow class. Each PhoneWindow holds a DecorView object, Most of the view-related operations in an Activity are done through a DecorView (the root View of the Activity interface).
  • DecorView inherits FrameLayout (DecorView→FrameLayout→ViewGroup→View). Usually the DecorView contains a vertical LinearLayout, The TitleView is on the top of the LinearLayout and the ContentView is on the bottom. DecorView takes the layout ID passed in by the Activity’s setContentView() and uses the layout filler to load the layout resource ID into a View and add it to the content column (r.android.id.content).

2. Context of View drawing

  • First of all, we can think of everything that needs to be drawn as a View tree, and a tree is essentially a root node, and the root node of a View tree is either a View or a ViewGroup, and the ViewGroup inherits from the View, so essentially the root node is the View. So basically, what we want to draw is the outermost root View, the root of the entire View tree.
  • The root View of an Activity is a DecorView. On the Android level, the display of the View depends on the Window, so the display of the DecorView depends on the PhoneWindow object in the Activity.
  • Since a Window object corresponds to a View object, there is a one-to-one correspondence between the two, so the View is actually added and displayed during the creation of the Window.
  • So how does an Activity create its own Window object? The Window object is actually created by a remote WMS system service implementation, and the Activity as a local application can only create Window objects through a WindowManager object via IPC (cross-process communication). How do you get a WindowManager object? Context provides some methods to get a WindowManager object.
  • Activity: handleResumeActivity (this method uses context. getWindowManager to create WindowManager objects) → WindowManager: AddView (where WindowManager delegates to a WindowManagerGLobal object) → WindowManagerGLobal: AddView (this method creates ViewRootImpl object) → ViewRootImpl: SetView →requestLayout→scheduleTraversals→ doduleTraversal →performTraversals
    • Windowmanager.addview is the process for creating a Window. The View is drawn in performTraversals of the ViewRootImpl object (a Window object corresponds to a ViewViewRootImpl object and a View object corresponds to a DecorView)
    • PerformTraversals () is the rendering entry, which calls performMeasure(), performLayout() and performDraw() in turn. The three methods call the measure(), layout(), and draw methods of the DecorView, respectively.

Conclusion: A DecorView is drawn by calling its measure(), layout(), and draw() methods, which can be summarized as three processes.

3. Three processes of View drawing

Measure: measure the width and height of all views in the whole View tree, and give the “measurement width and height” of each View (it will judge whether it needs to be recalculated first). Determine the “final width and height” of all views in the whole View tree and the position of the four vertices (will be judged first whether to recalculate)

3.1 measure measuring

Before we do that, we need to look at the MeasureSpec class, which plays a huge role in measurement and is often passed in as a parameter.

  • MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec MeasureSpec provides apis for packaging and unpacking a 32-bit integer into a <mode,size> tuple, making these special 32-bit integers mean something, in an abstract senseEncapsulates layout constraints that are passed from parent to child), three specmodes:
    • UNSPECIFIED(00) : The measurement mode is UNSPECIFIED. The parent view has no limits on the size of its child views, which can be of any desired size. The child views are typically used internally, and are rarely used in application development.
    • EXACTLY (01) : Precise measurement mode. This mode takes effect when layout_width or layout_height of the View is specified as a specific value or match_parent, indicating that the parent View has determined the exact size of the child View. In this mode, the measured value of the View is the value of SpecSize.
    • AT_MOST(10) : Maximum mode. This mode takes effect when the layout_width or layout_height of the current view is specified as wrap_content. In this case, the size of the child view can be any size less than the maximum size of the parent view.

MeasureSpec: performMeasure() = MeasureSpec: performMeasure() = Measure()

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {...// The mView in the Activity is a DecorViewmView.measure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code

The measurement starts from the measure method of the root View, but the actual measurement is performed in the onMeasure method called in the measure. You can see that performMeasure passes in two layout constraints of width and height from the parent layout. These layout constraints are passed to the root View’s measure method and then to the onMeasure method. So we know that measurements accept the constraints of width and height from the parent layout.

The view. measure method is a final method and cannot be rewritten, while onMeasure, the actual measurement method, is also a method that can be rewritten. In addition, the other two processes of View drawing are similar. So customizing a View is usually done by overriding onMeasure, onLayout and onDraw

Before looking at the onMeasure method, you might be wondering where the two layout constraints passed in by performMeasure came from, from the parent of the root View. What’s the parent of that View? This brings us back to view owl’s performTraversals method.

private void performTraversals(a) {...// The MeasureSpec obtained here is the layout constraint passed to the DecorView
	int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
	intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); . performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . performLayout(lp, mWidth, mHeight); . performDraw(); . }Copy the code

Look again at the parameter name of the getRootMeasureSpec method and the branch judgment to the rootDimension.

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
        // It will end up in this branch
        case ViewGroup.LayoutParams.MATCH_PARENT:
        	measureSpec = View.MeasureSpec.makeMeasureSpec(windowSize, View.MeasureSpec.EXACTLY);
        break; . }return measureSpec;
}
Copy the code

Believe that you can infer that came out, and the Activity of lp. The width = lp. Height = ViewGroup. LayoutParams. MATCH_PARENT, mWidth is screen width, mHeight is screen height. In getRootMeasureSpec method calls the View. The MeasureSpec. MakeMeasureSpec method mode and the size of packed into a 32-bit integer, These are the initial two layout constraints passed to the DecorView (screen width for exact mode and screen height for exact mode).

DecorView→FrameLayout→ViewGroup→View, which onMeasure method is called? In fact, both DecorView and FrameLayout override the onMeasure method. **ViewGroup does not override the onMeasure method itself. Classes that inherit the ViewGroup override the onMeasure method and call the methods provided by the ViewGroup in the onMeasure method to complete the measurement. ** Since the core idea of the onMeasure method of the class inheriting ViewGroup is similar, here take AbsoluteLayout (ViewGroup subclass) source code example.

Obviously a ViewGroup and a View measure differently, a View only measures itself. A ViewGroup measures itself and asks all its child views (or viewgroups) to complete their measurements. So measuring the entire View tree is like a recursive process.

Take a look at AbsoluteLayout’s rewritten onMeasure method:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...// Measure the entry of all child viewsmeasureChildren(widthMeasureSpec, heightMeasureSpec); .// After all subviews are measured, the ViewGroup itself is measured in resolveSizeAndState
    // Call setMeasuredDimension to set the measurement result. Calling this method means the View is finished measuring
    // the setMeasuredDimension method must be called in the onMeasure method, or an exception will be thrown.
    setMeasuredDimension(
        resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
        resolveSizeAndState(maxHeight, heightMeasureSpec, 0)); }Copy the code

Most ViewGroup subclasses will eventually call the resolveSizeAndState method to measure themselves. We’ll come back to this method later. How does it measure each child View

protected void measureChildren(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) {// Measure the entry of each child ViewmeasureChild(child, widthMeasureSpec, heightMeasureSpec); }}}Copy the code
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
    // The child View gets its own LayoutParams
    final LayoutParams lp = child.getLayoutParams();
	// Obtain the parent's layout constraints for each child View (i.e., calculate the MeasureSpec of the measure method passed to each child View)
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);
    // Subview calls its own measure to complete its own measurement work
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code

Let’s look at how the getChildMeasureSpec method uses the parent’s layout constraint and the child View’s LayoutParams to get the parent’s layout constraint for each child View (or: Calculate the MeasureSpec passed to each child View using the MeasureSpec passed to the ViewGroup and the LayoutParams passed to each child View.

In FrameLayout: onMeasure→measureChildWithMargins→getChildMeasureSpec

Again: MeasureChildren, measureChild, measureChildWithMargins, and getChildMeasureSpec are all ViewGroup methods. Many of the different subclasses of ViewGroup override onMeasure methods. These methods provided by the ViewGroup are then called.

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // The spec here is the parent layout constraint on the ViewGroup, and we need to calculate the parent layout constraint on the child View
    // Get the Mode and Size of the spec
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    
	// Get the size of the Parent after subtracting the Parent padding
    int size = Math.max(0, specSize - padding);

    // Define the return value
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // When the parent layout constraint is in exact mode
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {// All constants in LayoutParams are < 0
            // The current subview has an exact size
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // The current child View layout is MATCH_PARENT
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // The current subview layout is WRAP_CONTENT
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // When the parent layout constraint is in Max mode
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Do not specify the measurement mode, system use, development rarely used
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    
    // Returns the parent's layout constraint on the child View
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code

If the child View is a View group it’s going to recursively call all the way up to the leaf of the View tree, which is the View. Let’s see how the View’s onMeasure method does with its parent’s constraints on its layout.

View onMeasure method:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
    );
}
Copy the code

While setMeasuredDimension was used to set up the measurements, let’s take a look at the simple getSuggestedMinimumWidth() method

// Return the larger of the android:minWidth property and the background image width, or mMinWidth (default 0) if no background is set.
protected int getSuggestedMinimumWidth(a) { 
	return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code

MBackgroud is a Drawable object. I’m not going to go into Drawable here, but getDefaultSize.

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:
        // The UNSPECIFIED mode returns the return value from the getSuggestedMinimumWidth() method
    	result = size;
    	break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        // The other two modes return specSize
    	result = specSize;
    	break;
  }
  // Return the measured size of the View
  return result;
}
Copy the code

As you can see, both modes return the size of the parent layout, so if you’re customizing the View even if you’re wrap_content, it’s going to be match_parent. Therefore, you can add AT_MOST handling to your custom View as needed.

Finally, let’s go back and look at how the ViewGroup is measuring itself when the child View is measuring. The previous resolveSizeAndState method.

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    // Unpack the constraints of the parent on the ViewGroup
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    // Define the return value
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            // In the parent wrap_content case
            if (specSize < size) {
                // The size of the parent constraint is smaller than the ViewGroup wants
                // The extra bits can be set to flags
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                // Take the size the ViewGroup wants
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            // If the parent constraint has an exact size, the ViewGroup takes the size of the parent constraint
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    // Return the measured size of the ViewGroup
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
Copy the code

Summary of measurement process:

  • The ViewGroup accepts layout constraints from the parent (i.e. the ViewGroup’s MeasureSpec) and lets the child views calculate their own MeasureSpec (according to the ViewGroup’s MeasureSpec and its own LayoutParams). Then recursively call the measurement method.

  • In the View. OnMeasure method, the MeasureSpec is a MeasureSpec of your own

  • After all the child views of a ViewGroup have their measurement size, the ViewGroup calculates its measurement size.

3.2 layout layout

The basic idea of layout process is similar to measure. It starts from the root View and completes the layout of the whole View tree recursively.

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {...final View host = mView;
	if (host == null) {
		return; }... host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }Copy the code

The View layout method can be overridden, but the ViewGroup encapsulates it a little bit. It basically calls the View layout method. DecorView and FrameLayout are not overridden. So basically we call the Layout method of the View.

public void layout(int l, int t, int r, int b) {
    // l is the distance between the left edge of this View and the left edge of the parent View
    // t is the distance between the top edge of this View and the top edge of the parent View
    // r is the distance between the right edge of the View and the left edge of the parent View
    // b is the distance between the lower edge of the View and the upper edge of the parent View.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); . }... }Copy the code

The setFrame() method sets the mLeft, mTop, mRight, and mBottom parameters of the View, which describe the position of the View relative to the parent View. The setFrame() method also determines whether the View needs to be rearranged. Then go to the onLayout method.

The View’s onLayout method is an empty implementation, and the ViewGroup is an abstract method that requires subclasses to override the onLayout function. ViewGroup and the like rewrite onLayout in the same way, recursive through the child View’s onLayout method.

// Basic logic
protected void onLayout(boolean changed,int l, int t, int r, int b) {
    int childCount = getChildCount();
    for (int i = 0;i < childCount; i++){
        View child = getChildAt(i);
        child.layout(l, t, r, b);
    }
}
Copy the code

Take a quick look at the FrameLayout implementation:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
Copy the code
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

	// The top, bottom, and left of the parent
    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

	// Calculate the position of each child View relative to the parent View
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if(child.getVisibility() ! = GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();

			// The width measured by the subview
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

			// Combine layout-specific properties, such as FrameLayout and layout_gravity.
            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                    lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if(! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin;break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }

            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }

			// After the calculation, the setFrame method in the layout method of the child View is used to set the layout and finally complete the layout processchild.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code

3.3 the draw draw

Jump all the way from performDraw()

private void performDraw(a) {... mFullRedrawNeeded =false;
    booleancanUseAsync = draw(fullRedrawNeeded); . }Copy the code
private boolean draw(boolean fullRedrawNeeded) {... drawSoftware(surface, mAttachInfo, xOffset, yOffset,scalingRequired, dirty, surfaceInsets)) ... }Copy the code
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {... mView.draw(canvas);// Call the draw method of the DecorView. }Copy the code

Call DecorView’s draw method, which is actually the View’s draw method:

public void draw(Canvas canvas) {.../* * 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) */

    // Step 1, draw the background, if needed. drawBackground(canvas);// skip step 2 & 5 if possible (common case).if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
        onDraw(canvas);

        // Step 4, draw the childrendispatchDraw(canvas); .// Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);

        // Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas); .return; }...// There is a more complete process behind this, but it usually doesn't go there.
}
Copy the code

Jump all the way to the draw(Canvas Canvas) method to start drawing. There are 6 steps in drawing (the annotation says 6 steps, but there are 7 steps now), usually skipping 2 and 5. Let’s look at the remaining five steps.

Step 1: Draw the background
// Call draw() to draw the background image onto the canvas
background.draw(canvas); 
Copy the code

Step 2 save the Canvas layer

Step 3:Draw the contents of the View itself
// Step 3, draw the content
onDraw(canvas);
Copy the code

The view.ondraw (Canvas) method is an empty implementation and the ViewGroup is not overwritten. Usually we can override the onDraw method to draw what we want from a custom View.

Step 4 Draw all the child Views
// Step 4, draw the children
dispatchDraw(canvas);
Copy the code

A View has no children, so its dispatchDraw(Canvas) method is an empty implementation, which is overridden in the ViewGroup. The actual code logic is to traverse the child View and call the child view.draw () method.

Step 5 Draw the faded edges of the View if necessary

Step 6 Draw decorations, such as the View’s scroll bar

Call onDrawForeground of View to draw decoration

Step 7 Draw the default focus highlights