preface
The View Measure process was systematically analyzed before: We know that the parent layout generates measurement patterns and measurements for the child layout according to its own and child layout requirements, and encapsulates them in a MeasureSpec object, which is passed to the child layout for its final measurement. It’s natural to think, since the child layout takes measurements from the parent layout, and the parent layout takes measurements from its parent layout, who measures the vertex root View of the ViewTree? Follow the problem and look at the source code.
Series of articles:
How does Android Window determine the size /onMeasure() reason for executing multiple times
Through this article, you will learn:
1. Window size measurement 2. Root View size measurement 3
1. Window size measurement
A small Demo
Display a floating window from WindowManager.addView(xx) :
Private void showView() {// Get WindowManager wm = (WindowManager) App.getApplication().getSystemService(Context.WINDOW_SERVICE); / / set properties LayoutParams LayoutParams = new WindowManager. LayoutParams (); / / wide high dimension layoutParams. Height = ViewGroup. LayoutParams. WRAP_CONTENT; layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT; layoutParams.format = PixelFormat.TRANSPARENT; / / set the background dark layoutParams. Flags. | = WindowManager layoutParams. FLAG_DIM_BEHIND; LayoutParams. DimAmount = 0.6 f; //Window type if (build.version.sdk_int >= build.version_codes.o) {layoutparams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; } else { layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE; TextView myView = new TextView(this); myView.setText("hello window"); / / set the background to red myView. SetBackgroundResource (R.c olor. ColorRed); FrameLayout.LayoutParams myParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 400); myParam.gravity = Gravity.CENTER; myView.setLayoutParams(myParam); //myFrameLayout as rootView FrameLayout myFrameLayout = new FrameLayout(this); / / set the background for the GREEN myFrameLayout setBackgroundColor (Color. GREEN); myFrameLayout.addView(myView); // Add to window wm.addView(myFrameLayout, layoutParams); }Copy the code
The above code is briefly summarized as follows:
Create a TextView and set its background to red. 3. Add TextView as a child View to FrameLayout 4. Add FrameLayout as a RootView to Window
Floating Window display complete Demo please move: Window/WindowManager must not know things
Pay attention to the
wm.addView(myFrameLayout, layoutParams);
Copy the code
LayoutParams focuses on the width and height fields. We know that this is a size constraint on the Window. For example, set the width to different values and see what effect it has: 1
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
RootView(FrameLayout) wraps TextView, and the width is the same.
2, match_parent
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
3. Set specific values
layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
layoutParams.width = 800;
Wm. addView(myFrameLayout, layoutParams) is used to bind myFrameLayout(RootView).
Determine the size of Window
Starting with wm.addView(xx), WindowManager is an interface whose implementation class is WindowManagerImpl.
#WindowManagerImpl. Java public void addView(@nonnull View View, @nonnull viewGroup.layoutParams params) { The value applyDefaultToken(params) is determined when Dialog/PopupDialog is started; AddView (View, params, McOntext.getdisplay (), mParentWindow); }Copy the code
Now look at Windows ManagerGlobal processing:
#WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... Root = new ViewRootImpl(view.getContext(), display); SetLayoutParams (wparams); // View as RootView // Pass the wParams as the LayoutParams view.setLayoutParams(wparams); // Record object mviews.add (view); mRoots.add(root); mParams.add(wparams); Try {//ViewRootImpl associate RootView root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { ... }}}Copy the code
The LayoutParams Settings passed in wm.addView(xx) are given to RootView. Moving on to the Viewrootimpl.setView (xx) procedure.
#ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; . / / record LayoutParams to member variable mWindowAttributes / / in the variable Window attributes used to describe the mWindowAttributes. CopyFrom (attrs); . RequestLayout (); . try { ... / / the IPC communication, Tell WindowManagerService to create a Window // pass mWindowAttributes to // return mTmpFrame to indicate the maximum size that the Window can display res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel, mTempInsets); // Record the returned value to the member variable mWinFrame setFrame(mTmpFrame); } catch (RemoteException e) { ... } finally { if (restore) { attrs.restore(); }}... }}}Copy the code
The paragraph above focuses on two areas:
1. The LayoutParams passed in record to the member variable mWindowAttributes, which is then used to constrain Windows. 2. Return the maximum size of the Window when adding it to the mWinFrame member.
In summary, we find that layoutParams in wm.addView(myFrameLayout, layoutParams) not only constrain RootView, but also constrain Window.
2. Root View size measurement
We also need to generate a MeasureSpec object for RootView. We need to generate a MeasureSpec object for RootView. We need to generate a MeasureSpec object for RootView. RequestLayout is called during setView(xx) to register the callback, and performTraversals() is performed when the screen refresh signal arrives to initiate the three processes.
#ViewRootImpl.java private void performTraversals() { ... / / record before the Window LayoutParams WindowManager. LayoutParams lp = mWindowAttributes; //Window needs the size int desiredWindowWidth; int desiredWindowHeight; . Rect frame = mWinFrame; if (mFirst) { ... if (shouldUseDisplaySize(lp)) { ... } else {//mWinFrame = Window size desiredWindowWidth = mWinframe.width (); desiredWindowHeight = mWinFrame.height(); }... } else { ... }... if (layoutRequested) { ... / / from the method name should be measured ViewTree -- -- -- -- -- -- -- -- -- -- - (1) windowSizeMayChange | = measureHierarchy (host, lp, res, desiredWindowWidth, desiredWindowHeight); }... if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params ! = null || mForceNextWindowRelayout) { ... try { ... // Resize Window --------(2) relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); . } catch (RemoteException e) { } ... if (! mStopped || mReportNextDraw) { ... if (focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { ... ViewTree -------- (3) performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . } } } else { ... }... If (didLayout) {// performLayout ---------- (4) performLayout(lp, mWidth, mHeight); . }... if (! cancelDraw) { ... // start ViewTree Draw process ------- (5) performDraw(); } else { ... }}Copy the code
(1) measureHierarchy(XX)
#ViewRootImpl.java private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { int childWidthMeasureSpec; int childHeightMeasureSpec; boolean windowSizeMayChange = false; . Boolean goodMeasure = false; / / width is wrap_content when the if (lp) width = = ViewGroup. LayoutParams. Wrap_content) {final DisplayMetrics packageMetrics = res.getDisplayMetrics(); res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true); int baseSize = 0; if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = (int)mTmpValue.getDimension(packageMetrics); } //baseSize is the preset width //desiredWindowWidth The desired width is greater than the preset width if (baseSize! ChildWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // measure ----------------- first performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); // If the child layout of the ViewTree needs more width than the parent layout can give, Then the tag is set the if ((host getMeasuredWidthAndState () & the MEASURED_STATE_TOO_SMALL) = = 0) {/ / the marker is not set, the size of the parent layout to illustrate, GoodMeasure = true; } else {// parent layout does not meet the needs of the child layout, try to expand the width //desiredWindowWidth > baseSize, BaseSize = (baseSize+desiredWindowWidth)/2; childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); ----------------- second performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); / / continue to test whether meet the if ((host. GetMeasuredWidthAndState () & the MEASURED_STATE_TOO_SMALL) = = 0) {called goodMeasure = true; }}}} // If (! GoodMeasure) {// Generate MeasureSpec for RootView ChildWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // Now that MeasureSpec is available, ViewTree----------------- third performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight()) {// The size of the Window changes, which is used to determine whether to perform performMeasure(xx) windowSizeMayChange = true; }}... return windowSizeMayChange; } private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: / / RootView hope fill the Window, then satisfy it, its size is exact value measureSpec = measureSpec makeMeasureSpec (windowSize, measureSpec. EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: / / RootView hope according to their own content to determine the size, is set to AT_MOST mode measureSpec = measureSpec. MakeMeasureSpec (windowSize, measureSpec. AT_MOST); break; default: / / RootView hope directly specify size value, then satisfy it, its size is exact value measureSpec = measureSpec makeMeasureSpec (rootDimension, measureSpec. EXACTLY); break; } return measureSpec; }Copy the code
The above code does two main things:
2, According to the result of the first step, initiate the measurement of ViewTree (starting from RootView)
(2) After the measurement of ViewTree, the measurement value of RootView has been determined. Take a look at relayoutWindow (xx) :
#ViewRootImpl.java private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { ... Int relayoutResult = mWindowSession.relayout(mWindow, MSeq, Params, (int) (mView.getMeasuredWidth() * appScale + 0.5F), (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber, mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets); // Associate Window with surface if (mSurfacecontrol.isValid ()) {mSurface.copyFrom(mSurfaceControl); } else { destroySurface(); } // Record Window size setFrame(mTmpFrame); return relayoutResult; }Copy the code
We found that:
- The size of the Window depends on the measured size of the RootView, and generally appScale=1, that is to say, the size of the Window is the size of the RootView.
- This is to explain the previous Demo phenomenon.
The measurement of ViewTree in step (1) is to determine the size of RootView so as to determine the size of Window in this step.
(3) (4) (5)
Why is onMeasure() executed more than once?
The three parts are known as the three processes of View, and it is worth noting here: Step (1) In Step (1), we note three measurements.
1. The first time: With preset first ViewTree width measurement, measurement results 2, found that does not meet the measurement results for the first time, because there is need width is larger than the preset width layout, and then to the width of the larger layout, then the second 3, found the second measurement results are still not satisfied, then use the Window to get the maximum width of measured again
MeasureHierarchy (XX) executes performMeasure() at least once and at most three times. Call the performMeasure() method, which eventually calls the onMeasure() of each View. Must performMeasure() trigger the execution of onMeasure() every time? Will be. Reason: One more quick review of the Measure (xx) code:
public final void measure(xx) { ... final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || ! isSpecExactly || ! matchesSpecSize); / / two conditions can meet the needs of one of the / / 1, mandatory layout / / 2, size change if (forceLayout | | needsLayout) {... onMeasure(); . }... }Copy the code
According to the above information, if onMeasure() can be executed, the above conditions are met. NeedsLayout is definitely not enough because the View size hasn’t changed. So it has to be forceLayout=true. When the ViewTree was measured for the first time, it only went through the Measure process instead of the Layout process. We know that the PFLAG_FORCE_LAYOUT flag is cleared after the Layout is finished, so the PFLAG_FORCE_LAYOUT flag is not cleared, and needsLayout=true is sufficient. Detailed Measure/Layout/Draw series please move:
- Android custom View Measure process
- Android custom View Layout process
- Android custom View Draw process (part 1)
The ViewTree is measured again in step (3), and the View/ViewGroup onMeasure() is executed again.
Combine steps (1) and (3) to summarize:
At least one measurement is performed in step (1) and at most three measurements are performed in step (3)
When requestLayout is implemented, step (1) is always executed, i.e. PerformMeasure (xx)->onMeasure() is executed at least once. Step (3) is executed when the View is first displayed or when the window size changes. So we came to the following conclusions:
When the View is displayed for the first time, steps (1) and (3) must be executed, so onMeasure() must be executed at least twice. Step (3) may not be executed when requestLayout() is triggered, so onMeasure() may only execute once
That’s why onMeasure() does it multiple times when does step (1) do three times? In general, in the step (1) usually will only go for the third time measurement, the first and the second will not go, because for DecorView as RootView, lp. Width = = ViewGroup. LayoutParams. MATCH_PARENT. Do not meet the conditions of the first and second walk. So you’ll usually only see onMeasure() execute twice, not multiple times. Of course we can satisfy this condition by having onMeasure() execute 5 times.
# show floating window private void testMeasure () {WindowManager. LayoutParams LayoutParams = new WindowManager. LayoutParams (); layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; / / must be WRAP_CONTENT layoutParams. Width = ViewGroup. LayoutParams. WRAP_CONTENT; //TransView for custom View final TransView TransView = new TransView(this); windowManager.addView(transView, layoutParams); } @override protected void onMeasure(int widthMeasureSpec, Int heightMeasureSpec) {// Width = resolveSizeAndState(10000, widthMeasureSpec, 0); setMeasuredDimension(width, View.MeasureSpec.getSize(heightMeasureSpec)); }Copy the code
TransView is a custom View and TransView is a RootView. The above code does two main things:
Set Window width to WRAP_CONTENT; set TransView width to WRAP_CONTENT
To verify how many times this is done, print it on TransView onMeasure(XX).
In addition to the above reasons, onMeasure() may execute more times. For example, FrameLayout will trigger child.measure() again under certain conditions when measuring the sub-layout. FrameLayout->onLayout(xx)
Why is onMeasure() executed twice
Now that we know why it’s going to be executed twice, why is it designed that way? Regardless of special cases, the View executes onMeasure(xx) twice during the first display. As mentioned earlier, as long as requestLayout() is executed, step (1) is always executed. The purpose of step (1) is to obtain the measurement of RootView, which will be used to re-determine the width and height of the Window after relayoutWindow(xx), and step (3) is executed after relayoutWindow(xx). Therefore, step (1) is necessary to be performed.
Now we know how the sizes of RootView and Window are determined.
3. The relationship between Window, Viewrotimpl and View
The above involves Window and RootView, and the methods used are basically provided in ViewRootImpl. So what is the relationship between the three? The RootView needs to be added to the Window to display it, but Windows does not manage the RootView directly, but via ViewRootImpl.
What is the relationship between Windows and RootView? As you may have noticed, addToDisplay(xx) does not pass RootView, so how does RootView get added to Window? In fact, the addition here is more anthropomorphic. In relayoutWindow(xx), the mSurfaceControl is passed in, and when returned, the Surface mSurface is associated. That is, the underlying Surface is associated with the Surface of the Java layer. With Surface, you can get Canvas. The drawing of each View needs to be associated with Canvas, and so on, the View is associated with the Surface, so the drawing on the View will be fed back to the Surface. This means that the View is added to the Window.
This article is based on Android 10.0