Measuring piece of Android UI drawing process

After the analysis of the previous prelude, we know that the performTraversals method of ViewRootImpl has officially entered the measurement, layout and drawing process of View. This paper focuses on the measurement process of View. Go straight to the code

frameworks/base/core/java/android/view/ViewRootImpl.java

private void performTraversals() {
    ...
    if (!mStopped || mReportNextDraw) {
        boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
            (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
                    // Note 2
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                    if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                            + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                            + " mHeight=" + mHeight
                            + " measuredHeight=" + host.getMeasuredHeight()
                            + " coveredInsetsChanged=" + contentInsetsChanged);

                    // Note 1
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    				...
                }
        ...
    }
    ...   
}

Find the performMeasure method at annotation 1 of the measurement related logic code in performTraversals method. Locate the code at annotation 2 according to the parameters of the method. As the name implies, it means "measurement specification" of width and height. What is the measurement specification? Enter the getRootMeasureSpec method with questions:

frameworks/base/core/java/android/view/ViewRootImpl.java

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_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;
}

Here we see the MeasureSpec object. Its function is to convert the LayoutParams of view into the corresponding MeasureSpec according to the rules imposed by the parent container in the measurement process, and then determine the measurement width and height of view according to the MeasureSpec in onMeasure. This is the source code of MeasureSpec. In the middle, we will see the following methods:

 public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT; 
     /**
      * UNSPECIFIED pattern:
      * The parent View does not have any restrictions on the child View. The child View needs to be as large as possible
      */ 
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;

    /**
      * EXACTYLY pattern:
      * The parent View has measured the exact size required by the child vive. At this time, the final size of the View
      * Is the value specified by SpecSize. Corresponding to match_parent and exact values
      */ 
    public static final int EXACTLY     = 1 << MODE_SHIFT;

    /**
      * AT_MOST pattern:
      * The final size of the child View is the SpecSize value specified by the parent View, and the size of the child View cannot be greater than this value,
      * That is to say, wrap_content mode
      */ 
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    //Package size and mode into a 32-bit int value
    //The high 2 bits represent SpecMode, measurement mode, and the low 30 bits represent SpecSize, which is the specification size in a certain measurement mode
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    //Unpack the 32-bit MeasureSpec and return to SpecMode
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    //Unpack the 32-bit MeasureSpec and return SpecSize, which is the specification size in a certain measurement mode
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
    //...
}

MeasureSpec represents a 32-bit int value, the high 2 bits represent SpecMode, which means measurement mode, and the low 30 bits represent SpecSize, which means specification size in a certain measurement mode. MeasureSpec packs SpecMode and SpecSize into an int value to avoid excessive object memory allocation. To facilitate operation, it provides a packed method makeMeasureSpec. SpecMode and SpecSize are also int values. MeasureSpec can also get the original SpecMode and SpecSize by unpacking methods getMode and getSize. The specific operation is to use mode_ The constant mask is used to assist the implementation.

ModeMask

The first constant, ModeMask, is that 3 is shifted 30 bits to the left. Because int is stored in four bytes, the binary of 3 is stored in memory:
00000000 00000000 00000000 00000011
After the left displacement of 30 bits:
11000000 00000000 00000000 00000000


There are three types of SpecMode, each of which represents a special meaning, as shown below.

UNSPECIFIED

The parent container does not have any restrictions on the View. It is usually used inside the system to indicate the size of the View

State of measurement.

Exact: exact mode

The parent container has detected the exact size required by View. At this time, the final size of View is SpecSize
The specified value. It corresponds to match in LayoutParams_ Parent and specific values.

The exact constant 1 is stored in memory:
00000000 00000000 00000000 00000001
After the left displacement of 30 bits:
01000000 00000000 00000000 00000000


AT_MOST: maximum mode

The parent container specifies an available size, SpecSize. The size of View cannot be greater than this value. What is the specific value
It depends on the specific implementation of different views. It corresponds to wrap in LayoutParams_ content.

AT_MOST constant 2 is stored in memory:
00000000 00000000 00000000 00000010
After the left displacement of 30 bits:
10000000 00000000 00000000 00000000


Related calculation principle analysis:

Symbol describe Operation rules
& And When both bits are 1, the result is 1
| or When both bits are 0, the result is 0
^ Exclusive or Two bits are the same, the result is 0, the difference is 1
Reverse 0 becomes 1, 1 becomes 0
<< Shift left Move all binary bits to the left by several bits, discard the high bit, and fill 0 for the low bit
>> Shift right All binary bits are shifted to the right by several bits. For unsigned numbers, high-order complements 0 and signed numbers, the processing methods of each compiler are different. Some complements sign bits (arithmetic shift to the right) and some complements 0 (logical shift to the right)

For example, makeMeasureSpec(8, MeasureSpec.EXACTLY)

That is, size=8, binary representation: 00000000 00000000 000000000

MeasureSpec.EXACTLY= 1 << 30

Binary representation: 0100000000000000 00000000

Method returns the expression (size & ~ mode_ MASK) | (mode & MODE_ Value of mask)

MODE_MASK : 11000000 00000000 00000000 00000000

~MODE_MASK: 00111111 11111111 11111111 11111111

size & ~MODE_MASK:

​ 00000000 00000000 00000000 00001000

&

​ 00111111 11111111 11111111 11111111

=

​ 00000000 00000000 00000000 00001000

mode = MeasureSpec.EXACTLY= 1 << 30 : 01000000 00000000 00000000 00000000

mode & MODE_MASK

​ 01000000 00000000 00000000 00000000

​ &

​ 11000000 00000000 00000000 00000000

​ =

​ 01000000 00000000 00000000 00000000

(size & ~MODE_MASK) | (mode & MODE_MASK)

​ 00000000 00000000 00000000 00001000

​ |

​ 01000000 00000000 00000000 00000000

​ =

​ 01000000 00000000 00000000 00001000

The value of measureSpec, binary representation: 01000000 00000000 00000000 00001000

See the getSize() method again: measurespec & ~ mode_ MASK

01000000 00000000 00000000 00001000

&

00111111 11111111 11111111 11111111

=

00000000 00000000 00000000 00001000

The return value is binary 8

After having a preliminary understanding of MeasureSpec, we go back to the getRootMeasureSpec method in Note 2 of performTraversals method, and click enter. We find that the parameter mWindow corresponding to the parameter windowSize represents the window width, lp.width The corresponding rootDimension represents the width set by the top-level View, i.e. DecorView layout property. Combined with the switch statement inside the method, it is not difficult to draw a conclusion. For DecorView, its MeasureSpec is determined by the size of the window and its LayoutParams.

Return to the source code analysis process again and enter the performMeasure method:

frameworks/base/core/java/android/view/ViewRootImpl.java

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    if (mView == null) {
        return;
    }
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
        // Note 1
        // mView means DecorView, which can be viewed in setView
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
}

Enter the measure method and come to the measure method of View. We find that it internally calls the onMeasure method:

frameworks/base/core/java/android/view/View.java

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ...
    onMeasure()
    ...
}

Enter the onMea method of View:

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

Generally speaking, performMeasure will call the onMeasure method of specific View finally, and our control will have its own business requirements to rewrite the onMeasure method. The logic of onMeasure is not the same whether it is the system's FrameLayout, LinearLayout and other controls, or when we customize controls. This is why there is no onMeasure method in ViewGroup, that is, there is no specific measurement process defined. ViewGroup is an abstract class. The onMeasure method of measurement process needs to be implemented by each subclass. Different ViewGroup subclasses have different layout characteristics, which results in different measurement details. Therefore, ViewGroup cannot be implemented uniformly (onMeasure method).

Because the previous mView represents DecorView, and DecorView inherits FrameLayout, this paper takes FrameLayout as an example to analyze the measurement process of ViewGroup. Enter the onMeasure method of FrameLayout:

frameworks/base/core/java/android/widget/FrameLayout.java

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	//Get the number of child views in the current layout
	int count = getChildCount();

	//Determine whether the width and height of the current layout are match_parent mode or specify an exact size, if there is only one for
	//wrap_content, then measureMatchParentChildren is true, otherwise it is false
	final boolean measureMatchParentChildren =
	MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    ...
	// Traverse all child views with visible type other than GONE
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // Measure each subview
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // Find the largest width and height in the child View, because if FrameLayout is wrap_content attribute
            // So its size depends on the size of subview plus margin
            maxWidth = Math.max(maxWidth,
            child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
            child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            // Indicates that at least one of FrameLayout's width and height is wrap_content
            // When FrameLayout is wrap_ When content, the measurement size of subview will affect the measurement size of FrameLayout
            if (measureMatchParentChildren) {
            if (lp.width == LayoutParams.MATCH_PARENT ||
            lp.height == LayoutParams.MATCH_PARENT) {
            // One of the FrameLayout width or height is wrap_content, the width or height of child View has one
            // match_ When parent, add the child View to the collection
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

    // Account for padding too
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
	// Match_ Number of child views of parent
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
			// Set the width specification of FrameLayout, because it will affect the measurement of subview
            final int childWidthMeasureSpec;
            /**
             * If the width of the child View is match_parent property, modify the MeasureSpec of the current FrameLayout:
             * Change the width specification of the widthMeasureSpec to: total width - padding - margin, which means:
             * For child VIV, if you want to match_parent, then it can cover the measurement width of FrameLayout
             * The space left after subtracting padding and margin.
          	 *
             * See the getChildMeasureSpec() method for the following two conclusions:
             *
             * If the width of the subview is a certain value, such as 50dp, then the FrameLayout's widthMeasureSpec
             * The width specification of is modified to:
             * SpecSize Is the width of the child View, i.e. 50dp, SpecMode is EXACTLY mode
             * 
             * If the width of the child View is wrap_content property, then the widthMeasureSpec of FrameLayout
             * The width specification of is modified to:
             * SpecSize Subtract padding and margin for the width of child View, and SpecMode is AT_MOST mode
             */

            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() 
                        - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() 
                        +lp.leftMargin + lp.rightMargin,lp.width);
            }

            // Do the same for height, omit
			...
            //The sub View of this part needs to be re measure d
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

For the detailed analysis of the measurement process of FrameLayout, you can refer to the notes. To sum up again, FrameLayout measures each sub View according to its MeasureSpec, that is, it calls measureChildWithMargin method, which will be described in detail below. For each sub View completed by measurement, it will find the largest width and height, so the measurement width of FrameLayout The height will be affected by the maximum width and height of this subview (wrap_content mode), and then call the setMeasureDimension method to save the measurement width and height of FrameLayout. Finally, it deals with special cases, that is, when FrameLayout is wrap_ When the content attribute is set, if its child View is match_ If the parent property is set, the measurement specification of FrameLayout should be reset, and then the View of this part should be measured again.

The setMeasureDimension method mentioned above is used to save the measurement results. In the above source code, the parameters of the method receive the return value of the resolveSizeAndState method. Then we can directly see the view ා resolveSizeAndState method:

frameworks/base/core/java/android/view/View.java

public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState){
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
 }

It can be seen that the idea of this method is quite clear. When specmode is exact, the width height specification in MeasureSpec is directly returned as the final measurement width height; when specmode is at_ When most, the minimum values of width height specification and size of MeasureSpec are taken. As mentioned earlier, when specmode is at_ When most, the parent container specifies an available size, SpecSize. The size of View cannot be greater than this value.

As mentioned above, the measurement subview will be traversed during the FrameLayout measurement process. The measureChildWithMargins method is called:

frameworks/base/core/java/android/view/ViewGroup.java

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(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);
}

The getChildMeasureSpec method is called internally. You can see from the parameters of the method. Pass the MeasureSpec of the parent container and its layoutparams property to get the MeasureSpec of the child view. It can be seen that the MeasureSpec of the common view is jointly determined by the MeasureSpec of the parent container and its layoutparams. Here we can see that the measurement method of subclass is called directly to traverse the measurement sub view. ViewGroup now we can get the overall measurement process: in performTraversals, we start to get the size of the system layout of the DecorView type, and then start to measure the process in performMeasure method. There are different implementation methods for different layout layouts, but in general, in onMeasure method, we traverse each sub view according to the MeasureSpec and sub view of ViewGroup View's layoutparams determines its own measurement width and height, and then determines the father's width and height according to the measurement width and height information of all child views
Continuously traverse the measure method of the subview, determine the measure spec of the subview according to the measure spec of the ViewGroup and the LayoutParams of the subview, further obtain the measure width and height of the subview, then return layer by layer, and continuously save the measure width and height of the ViewGroup

summary

  • For DecorView, its MeasureSpec is determined by the size of the window and its own LayoutParams

  • For a normal View, its MeasureSpec is determined by the MeasureSpec of the parent container and its LayoutParams

  • MeasureSpec represents a 32-bit int value, the high 2 bits represent SpecMode, which means measurement mode, and the low 30 bits represent SpecSize, which means specification size in a certain measurement mode. MeasureSpec packs SpecMode and SpecSize into an int value to avoid excessive object memory allocation. To facilitate operation, it provides a packed method makeMeasureSpec. SpecMode and SpecSize are also int values. MeasureSpec can also get the original SpecMode and SpecSize through unpacking methods getMode and getSize

  • There are three types of SpecMode

    • UNSPECIFIED: the parent container does not have any restrictions on the View. It is usually used inside the system to indicate a measured state.

    • Exact: the parent container has detected the exact size required by View. At this time, the final size of View is the value specified by SpecSize. It corresponds to match in LayoutParams_ Parent and specific values.

    • AT_MOST: the parent container specifies an available size, SpecSize. The size of View cannot be greater than this value. The specific value depends on the specific implementation of different views. It corresponds to wrap in LayoutParams_ content.

  • Measure method will be called in performMeasure, onMeasure method will be called in measure method, and measure process will be carried out for all child elements in onMeasure method. At this time, measure process will be transferred from parent container to child element, and then child element will repeat the measure process of parent container, so that the whole View number can be traversed repeatedly.

  • ViewGroup is an abstract class. There is no specific measurement method, and its measurement process is implemented by specific subclasses. Because different subclasses of ViewGroup have different layout characteristics, resulting in different measurement details. For example, the layout characteristics of FrameLayout, LinearLayout and relativelayout are different, so it cannot be implemented uniformly. But the same thing is that you have to traverse the measurement subelements. ViewGroup provides different measureChild, measureChildWithMargins and other methods for them to call. Internally, it contains MeasureSpec to get child elements and execute child.measure , traversing measurement subelements

Tags: Java Android Attribute

Posted on Fri, 22 May 2020 03:54:32 -0400 by irandoct