English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Comprehensive analysis of Android View measurement process (Measure)

Introduction

In the previous article, the author mainly explained the role of DecorView and ViewRootImpl, let's review the content mentioned in the previous chapter: DecorView is the top-level view of the view, the layout file we add is a child layout of it, and ViewRootImpl is responsible for rendering the view, it calls a performTraveals method to make the ViewTree start the three major workflow processes, and then makes the View appear in front of us. The main content of this article is: a detailed explanation of the View measurement (Measure) process, mainly presented in the form of source code, all of which are taken from the Android API 21.

Starting from ViewRootImpl#PerformTraveals

Let's start with this method, as it is the core of the entire workflow, let's take a look at its source code:

private void performTraversals() {
  ...
 if (!mStopped) {
  int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 1
  int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
  {}
 {}
 if (didLayout) {
  performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  ...
 {}
 if (!cancelDraw && !newSurface) {
  if (!skipDraw || mReportNextDraw) {
  if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
   for (int i = 0; i < mPendingTransitions.size(); ++i) {
   mPendingTransitions.get(i).startChangingAnimations();
   {}
   mPendingTransitions.clear();
  {}
  performDraw();
  {}
 {} 
 ...
{}

The method is very long, and here it has been simplified. We can see that it mainly executes three methods, namely performMeasure, performLayout, and performDraw. Within these three methods, measure, layout, and draw methods are called respectively to carry out different processes. Let's first look at the performMeasure(childWidthMeasureSpec, childHeightMeasureSpec) method, which takes two parameters, namely childWidthMeasureSpec and childHeightMeasureSpec. Then what do these two parameters represent? To understand the meaning of these two parameters, we must first understand MeasureSpec.

Understanding MeasureSpec

MeasureSpec is an inner class of the View class, let's first look at the official documentation for the MeasureSpec class: A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec consists of a size and a mode. This means that this class encapsulates the specification size of a View, including information about the width and height of the View, but it should be noted that MeasureSpec does not refer to the measured width and height of the View, which is different. It is measured based on MeasureSpec.
The role of MeasureSpec is: during the Measure process, the system will convert the View's LayoutParams according to the rules imposed by the parent container into the corresponding MeasureSpec, and then determine the View's measurement width and height in the onMeasure method based on this MeasureSpec.
Let's take a look at the source code of this class:

public static class MeasureSpec {
 private static final int MODE_SHIFT = 30;
 private static final int MODE_MASK = 0x3 << MODE_SHIFT;
 /**
  * UNSPECIFIED mode:
  * The parent View has no restrictions on the child View, the child View can be as large as needed
  */ 
 public static final int UNSPECIFIED = 0 << MODE_SHIFT;
 /**
  * EXACTLY mode:
  * The parent View has measured the exact size required by the child Viwe, at this time the final size of the View
  * which is the value specified by SpecSize. Corresponds to match_parent and exact number modes
  */ 
 public static final int EXACTLY = 1 << MODE_SHIFT;
 /**
  * AT_MOST mode:
  * 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
  * which corresponds to the wrap_content mode
  */ 
 public static final int AT_MOST = 2 << MODE_SHIFT;
 //pack size and mode into one32an integer value of the bit
 //high2The bit represents SpecMode, measurement mode, low30 bit represents SpecSize, which is the specification size under 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);
  {}
 {}
 //The32Unpack the SpecMode, which is the measurement mode
 public static int getMode(int measureSpec) {
  return (measureSpec & MODE_MASK);
 {}
 //The32Unpack the SpecSize of the specified MeasureSpec, returning the specification size under a certain measurement mode
 public static int getSize(int measureSpec) {
  return (measureSpec & ~MODE_MASK);
 {}
 //...
 {}

It can be seen that the logic of this class is quite clear. For each View, including DecorView, it holds a MeasureSpec that saves the size specifications of the View. In the View measurement process, width and height information is saved through makeMeasureSpec, and in other processes, mode and width/height are obtained through getMode or getSize. So the question arises, as mentioned above, MeasureSpec is affected by LayoutParams and the parent container's mode. For DecorView, as it is the top-level view, it has no parent container. Then how does its MeasureSpec come from?

To solve this question, we go back to the ViewRootImpl#PerformTraveals method and look at the ① code location, where the getRootMeasureSpec(desiredWindowWidth, lp.width) method is called. Here, desiredWindowWidth represents the screen size, and the returned result is assigned to the childWidthMeasureSpec member variable (childHeightMeasureSpec is similar), so childWidthMeasureSpec(childHeightMeasureSpec) should save the MeasureSpec of DecorView. Then let's take a look at the implementation of the ViewRootImpl#getRootMeasureSpec method:

/**
 * @param windowSize
 *  The available width or height of the window
 *
 * @param rootDimension
 *  The layout params for one dimension (width or height) of the
 *  window.
 *
 * @return The measure spec to use to measure the root view.
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
 int measureSpec;
 switch (rootDimension) {
 case ViewGroup.LayoutParams.MATCH_PARENT:
 // The window cannot be resized. Force the root view to be windowSize.
 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
 break;
 //Omitted...
 {}
 return measureSpec;
{}

The idea is also clear, setting MeasureSpec according to different modes. If it is LayoutParams.MATCH_PARENT mode, it is the size of the window, and WRAP_CONTENT mode is the size that is not determined, but cannot exceed the size of the window, etc.

So far, we have obtained a DecorView's MeasureSpec, which represents the specifications and dimensions of the root View. In the subsequent measure process, each child View is measured layer by layer based on the obtained MeasureSpec of the root View. Following the ① code, we come to the performMeasure method, looking at what it does, ViewRootImpl#performMeasure:

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
 try {
 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 finally {
 }
 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
 {}
{}

The method is very simple, it directly calls mView.measure, where mView is DecorView, which means that the measurement process starts from the top-level View, so we directly enter the measurement process.

measure measurement process

ViewGroup measurement process

Since DecorView inherits from FrameLayout and is an inner class of PhoneWindow, and FrameLayout does not have a measure method, the method called is the measure method of the parent class View. Let's take a look at its source code, View#measure:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 boolean optical = isLayoutModeOptical(this);
 if (optical != isLayoutModeOptical(mParent)) {
 ...
 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
  widthMeasureSpec != mOldWidthMeasureSpec ||
  heightMeasureSpec != mOldHeightMeasureSpec) {
  ...
  if (cacheIndex < 0 || sIgnoreMeasureCache) {
  // measure ourselves, this should set the measured dimension flag back
  onMeasure(widthMeasureSpec, heightMeasureSpec);
  mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  {} 
 ...
{}

As can be seen, it internally calls the onMeasure method. Since DecorView is a subclass of FrameLayout, it actually calls the DecorView#onMeasure method. Inside this method, some judgments are made, which will not be expanded here. Finally, it will call the super.onMeasure method, that is, the FrameLayout#onMeasure method.

Since different ViewGroup types have different properties, their onMeasure methods must be different. Therefore, it is impossible to analyze all layout types' onMeasure methods here. Hence, the onMeasure method of FrameLayout is chosen for analysis. Other layout types can be analyzed by the reader. Let's continue to look at this method:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 //Get the number of child Views within the current layout
 int count = getChildCount();
 //Determine if the current layout's width and height are in match_parent mode or a specified exact size, if so, set measureMatchParent to false.
 final boolean measureMatchParentChildren =
  MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
  MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
 mMatchParentChildren.clear();
 int maxHeight = 0;
 int maxWidth = 0;
 int childState = 0;
 //Iterate through all child Views of type not GONE
 for (int i = 0; i < count; i++) {
 final View child = getChildAt(i);
 if (mMeasureAllChildren || child.getVisibility() != GONE) {
  //Measure each child View
  measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
  final LayoutParams lp = (LayoutParams) child.getLayoutParams();
  //Find the maximum width and height of the child View, because if FrameLayout has the wrap_content attribute
  //Then its size depends on the maximum of the child View
  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());
  //If FrameLayout is in wrap_content mode, then add it to mMatchParentChildren
  //A child View with width or height set to match_parent, because the final measurement size of this child View will be affected by the final measurement size of FrameLayout
  if (measureMatchParentChildren) {
  if (lp.width == LayoutParams.MATCH_PARENT ||
   lp.height == LayoutParams.MATCH_PARENT) {
   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());
 {}
 //Save the measurement result
 setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
  resolveSizeAndState(maxHeight, heightMeasureSpec,
   childState << MEASURED_HEIGHT_STATE_SHIFT));
 //The number of child Views set to match_parent
 count = mMatchParentChildren.size();
 //The following statements will only be executed when the FrameLayout mode is wrap_content
 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 the child View
  final int childWidthMeasureSpec;
  /**
  * If the width of the child View is the match_parent attribute, then modify the MeasureSpec of the current FrameLayout:
  * Modify the width specification of widthMeasureSpec to: total width - padding - margin, which means:
  * For the child Viw, if it needs to match_parent, then the range it can cover is the measurement width of FrameLayout
  * The remaining space after subtracting padding and margin.
  *
  * The conclusions of the following two points can be found in the getChildMeasureSpec() method:
  *
  * If the width of the child View is a fixed value, such as50dp, then the width specification of FrameLayout's widthMeasureSpec is modified as follows:
  * SpecSize is the width of the child View, that is50dp, SpecMode is EXACTLY mode
  * 
  * If the width of the child View is set to wrap_content, then the width specification of FrameLayout's widthMeasureSpec is modified as follows:
  * SpecSize is the width of the child View minus padding minus margin, 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);
  {}
  //Similarly, the same treatment is applied to the height, and the details are omitted...
  //The measure process needs to be re-done for this part of child View
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
 {}
 {}
{}

From the above FrameLayout's onMeasure process, it can be seen that it still does a lot of work. Here is a brief summary: first, FrameLayout measures each child View according to its MeasureSpec, that is, calling the measureChildWithMargin method, which will be explained in detail later; for each child View that has been measured, it finds the maximum width and height among them, so the measurement width and height of FrameLayout will be affected by the maximum width and height of this child View (in wrap_content mode), and then call the setMeasureDimension method to save the measurement width and height of FrameLayout. Finally, there is the handling of special cases, that is, when FrameLayout is set to wrap_content attribute, if its child View is set to match_parent attribute, then the measurement specification of FrameLayout needs to be reset, and then the measurement of this part of View needs to be re-measured.

In the mentioned setMeasureDimension method, this method is used to save the measurement result. In the above source code, the parameter of this method receives the return value of the resolveSizeAndState method, so let's directly look at the View#resolveSizeAndState method:

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 logic of this method is quite clear. When specMode is EXACTLY, then directly return the width and height specifications in MeasureSpec as the final measurement width and height; when specMode is AT_MOST, then take the minimum value of the width and height specifications in MeasureSpec and size. (Note: Here, the size, for FrameLayout, is the measurement width and height of the largest subView).

Summary:So far, starting from DecorView, we have analyzed the measurement process of ViewGroup in detail. In ViewRootImpl#performTraversals, we obtain the size of DecorView, and then start the measurement process in performMeasure method. Different layout layouts have different implementation methods, but generally, it is in the onMeasure method where each subView is traversed, and the measurement width and height of itself are determined according to the ViewGroup's MeasureSpec and the layoutParams of the subView. Finally, the measurement width and height of the parent container are determined based on the measurement width and height information of all subViews.

Then, let's continue to analyze how a subView is measured.

The measurement process of View

Remember the measureChildWithMargin method mentioned in the FrameLayout measurement above, which mainly receives the child View and the parent container's MeasureSpec as parameters, so its function is to measure the child View. Let's take a look at this method directly, ViewGroup#measureChildWithMargins:

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); // 1
{}

The source code shows that the getChildMeasureSpec method is called, passing the parent container's MeasureSpec and its own layoutParams attributes to obtain the child View's MeasureSpec. This also verifies the conclusion that 'the MeasureSpec of the child View is determined by the MeasureSpec of the parent container and its own LayoutParams together.' Let's take a look at the ViewGroup#getChildMeasureSpec method together:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 int specMode = MeasureSpec.getMode(spec);
 int specSize = MeasureSpec.getSize(spec);
 //size represents the available space for the child View: the parent container size minus padding
 int size = Math.max(0, specSize - padding);
 int resultSize = 0;
 int resultMode = 0;
 switch (specMode) {
 // 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 (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 has imposed a maximum size on us
 case MeasureSpec.AT_MOST:
 //Omitted...Please refer to the source code for details
 break;
 // Parent asked to see how big we want to be
 case MeasureSpec.UNSPECIFIED:
 //Omitted...Please refer to the source code for details
 break;
 {}
 return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
{}

The above method is also very easy to understand, it is roughly to decide the specification size mode of the child View according to the mode of the parent container and the layoutParams of the child View. So, according to the above logic, we list the different combinations of MeasureSpec of the parent container and LayoutParams of the child View, and the different MeasureSpec of the child View that appears in these combinations:

(Note: the presentation form of this table is referenced from 'Android Development Art Exploration' by Ren Yugaang)

After the MeasureSpec of the child View is obtained, we return to the measureChildWithMargins method, and then the ① code will be executed: child.measure method, which means that the drawing process has shifted from ViewGroup to the child View. As can be seen, the parameters passed are exactly the MeasureSpec of the child View we just obtained. Then, View#measure will be called, which has been mentioned above, and will not be repeated here. In the measure method, the onMeasure method will be called, of course, the onMeasure method for different types of View is different. However, for different Views, even for custom Views, we will definitely call View#onMeasure method in the rewritten onMeasure method. Therefore, let's look at its source code:

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

It is obvious that the setMeasureDimension method is called here, as mentioned above, the function of this method is to set the measurement width and height, and the measurement width and height are obtained from getDefaultSize. Let's continue to look at this method View#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:
 result = size;
 break;
 case MeasureSpec.AT_MOST:
 case MeasureSpec.EXACTLY:
 result = specSize;
 break;
 {}
 return result;
{}

Alright, it's similar code again, setting different measurement widths and heights based on different modes, let's directly look at AT_MOST and EXACTLY modes, which directly return specSize, that is, the measurement width and height of View in these two modes directly depends on the specSize specification. That is to say, for a custom View that directly inherits from View, the effect of wrap_content and match_parent attributes is the same, so if you want to implement wrap_content for a custom View, you need to override the onMeasure method to handle the wrap_content attribute.
Next, let's look at the UNSPECIFIED mode, which may be less common and is generally used for internal system measurements, it directly returns size, not specSize, then where does size come from? Look up one level, it comes from getSuggestedMinimumWidth() or getSuggestedMinimumHeight(), let's choose one method and look at the source code, View#getSuggestedMinimumWidth:

protected int getSuggestedMinimumWidth() {
 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
{}

From the above logic, it can be seen that when the View does not have a background set, it returns mMinWidth, which corresponds to the android:minWidth attribute; if a background is set, then it returns the maximum value of mMinWidth and mBackground.getMinimumWidth. Then, what is mBackground.getMinimumWidth? In fact, it represents the original width of the background, for example, for a Bitmap, its original width is the size of the image. Up to this point, the measurement process of the child View is also completed.

Summary

Here is a brief summary of the entire process: measurement starts with DecorView, and through the continuous traversal of the measure method of the child View, the MeasureSpec of ViewGroup and the LayoutParams of the child View are used to determine the MeasureSpec of the child View, further obtain the measurement width and height of the child View, and then return layer by layer, continuously save the measurement width and height of ViewGroup.

From the beginning of the article to now, the measurement process of View has been fully analyzed. The View measurement process is the most complex of the three processes, and the MeasureSpec贯穿了整个测量流程,occupies an important position. I hope readers carefully experience this process, and finally hope this article can help you have a deeper understanding of the View measurement process. Thank you for reading.

More Reading
Complete Analysis of Android View Layout Process (Layout)
Complete Analysis of Android View Drawing Process (Draw)

That's all for this article. I hope it will be helpful to your studies, and I also hope everyone will support the Shouting Tutorial more.

Declaration: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#w3Please report via email to codebox.com (replace # with @ when sending an email) and provide relevant evidence. Once verified, this site will immediately delete the infringing content.

You May Also Like