Specifically, the onMeasure of the View in the page will be called at least twice when the Activity is first started. Why is that? Next, we take LinearLayout as an example for analysis.

Custom LinearLayout validation

CustomLinearLayout first custom a LinearLayout: CustomLinearLayout, mainly add log print:

CustomLinearLayout print log

public class CustomLinearLayout extends LinearLayout { private static final String TAG = "CustomLayout-Linear"; public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public CustomLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); Log.e(TAG, String.format("onMeasure widthSpecSize:%s, widthSpecMode:%s, heightSpecSize:%s, heightSpecMode:%s", widthSpecSize, widthSpecMode, heightSpecSize, heightSpecMode)); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); Log.e(TAG, String.format("onLayout changed:%s, l:%s, t:%s, r:%s, b:%s", changed, l, t, r, b)); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e(TAG, "onDraw"); }}Copy the code

Simple XML file with width and height match_parent

Take a look at the layout file for the Activity. The layout file is very simple, which is a custom LinearLayout:

<? The XML version = "1.0" encoding = "utf-8"? > <com.tinytongtong.androidstudy.measure.view.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".measure.MeasureInvokeTestActivity"> </com.tinytongtong.androidstudy.measure.view.CustomLinearLayout>Copy the code

To view the log

Finally, start the Activity. Let’s look at the corresponding log:

E/CustomLayout-Linear: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:1073741824
E/CustomLayout-Linear: onMeasure widthSpecSize:1080, widthSpecMode:1073741824, heightSpecSize:1823, heightSpecMode:1073741824
E/CustomLayout-Linear: onLayout changed:true, l:0, t:0, r:1080, b:1823
Copy the code

As shown above, the onMeasure method on the custom LinearLayout is actually called twice.

Analyze principles through debugging

Let’s add a debug breakpoint to the CustomLinearLayout#onMeasure method to see the actual call chain.

Debug result: Invoke onMeasure based on the API30 for the first time:As can be clearly seen from the picture,ViewRootImpl#performMeasureMethod is inViewRootImpl 2228 linesThe call. The call chain is as follows:

--> ViewRootImpl#performTraversals --> ViewRootImpl#measureHierarchy; Call --> viewrotimpl #performMeasure(childWidthMeasureSpec, childIghtMeasurespec); Line 2228 call --> View#measure; --> View#onMeasure; Starting from the root View, the sequential measure process is executed from top to bottom.Copy the code

In view otimpl #performTraversals, line 2486.

private void performTraversals() { ... // Ask host how big it wants to be windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); . }Copy the code

4. Second callonMeasure:

The call chain is as follows:

--> ViewRootImpl#performTraversals --> ViewRootImpl#performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); Line 2280 call --> View#measure; --> View#onMeasure; Starting from the root View, the sequential measure process is executed from top to bottom.Copy the code

The specific code is as follows:

private void performTraversals() { ... // Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code

In general, both measurements are triggered in the View traversals #performTraversals method, the first at line 2486 by calling the View Traversals #measureHierarchy method, The second call is ViewRootImpl#performMeasure(childWidthMeasureSpec, childHeightMeasureSpec) at line 2280.

What do these two calls mean? The first call to the ViewRootImpl#measureHierarchy method at line 2486 measures the ViewTree at this step to determine the size of the RootView and thus the size of the Window at this step.

The second call to viewrotimpl #performMeasure(childWidthMeasureSpec, childHeightMeasureSpec) on line 2280 determines the window size. Measure the RootView again, this time can determine the size of each part in the ViewTree.

Careful students may have noticed that the width and height of the Activity layout in our demo are written as match_parent, which belongs to the simplest scene in measurement. In this case, the width and height of RootView will not exceed the size of window. If we modify the width and height of our layout, the measurement process will be more than two times. For those who are interested, please refer to a long-standing puzzle on Android: why onMeasure() is executed multiple times? .

conclusion

In a simple Activity, write a simple layout with the width and height match_parent. When the Activity is started, the drawing process (onMeasure method) is executed at least twice, starting with RootView and working down.

The relevant data

Why onMeasure() is executed multiple times?

The demo address