The drawing process of View has three steps: onMeasure, onLayout and onDraw. This article will talk about onMeasure related knowledge points.

However, before this step there are more important preparation steps, onAttachWindow (View binding window rendering information) and onApplyWindowInsets(distribution window spacing consumption), hardware rendering preparation. When the View is stripped out of the View tree for destruction, the onDetachWindow cycle is called.

The body of the

When the View is instantiated with setContentView, the Activity’s onCreate life cycle is completed. It’s going to go into the onResume life cycle.

After the Activity’s onResume callback is processed, it continues back to the ActivityThread’s handleResumeActivity method. HandleResumeActivity will call WM’s addView method.

At the heart of this is the call to the setView method of ViewRootImpl. Before we talk about the setView method of ViewRootImpl, the ViewRootImpl class has a class called View.attachInfo that uses the entire View drawing logic.

View.AttachInfo

final static class AttachInfo { interface Callbacks { void playSoundEffect(int effectId); boolean performHapticFeedback(int effectId, boolean always); } static class InvalidateInfo { private static final int POOL_LIMIT = 10; private static final SynchronizedPool<InvalidateInfo> sPool = new SynchronizedPool<InvalidateInfo>(POOL_LIMIT); View target; int left; int top; int right; int bottom; public static InvalidateInfo obtain() { InvalidateInfo instance = sPool.acquire(); return (instance ! = null) ? instance : new InvalidateInfo(); } public void recycle() { target = null; sPool.release(this); } } final IWindowSession mSession; final IWindow mWindow; final IBinder mWindowToken; Display mDisplay; final Callbacks mRootCallbacks; IWindowId mIWindowId; WindowId mWindowId; View mRootView; IBinder mPanelParentWindowToken; boolean mHardwareAccelerated; boolean mHardwareAccelerationRequested; ThreadedRenderer mThreadedRenderer; List<RenderNode> mPendingAnimatingRenderNodes; int mDisplayState = Display.STATE_UNKNOWN; float mApplicationScale; boolean mScalingRequired; int mWindowLeft; int mWindowTop; boolean mUse32BitDrawingCache; final Rect mOverscanInsets = new Rect(); final Rect mContentInsets = new Rect(); final Rect mVisibleInsets = new Rect(); final Rect mStableInsets = new Rect(); final DisplayCutout.ParcelableWrapper mDisplayCutout = new DisplayCutout.ParcelableWrapper(DisplayCutout.NO_CUTOUT); final Rect mOutsets = new Rect(); boolean mAlwaysConsumeNavBar; final ViewTreeObserver.InternalInsetsInfo mGivenInternalInsets = new ViewTreeObserver.InternalInsetsInfo(); boolean mHasNonEmptyGivenInternalInsets; final ArrayList<View> mScrollContainers = new ArrayList<View>(); final KeyEvent.DispatcherState mKeyDispatchState = new KeyEvent.DispatcherState(); boolean mHasWindowFocus; int mWindowVisibility; long mDrawingTime; boolean mIgnoreDirtyState; boolean mSetIgnoreDirtyState = false; boolean mInTouchMode; boolean mUnbufferedDispatchRequested; boolean mRecomputeGlobalAttributes; boolean mForceReportNewAttributes; boolean mKeepScreenOn; boolean mNeedsUpdateLightCenter; int mSystemUiVisibility; int mDisabledSystemUiVisibility; int mGlobalSystemUiVisibility = -1; boolean mHasSystemUiListeners; boolean mOverscanRequested; boolean mViewVisibilityChanged; boolean mViewScrollChanged; boolean mHandlingPointerEvent; final int[] mTransparentLocation = new int[2]; final int[] mInvalidateChildLocation = new int[2]; . final ViewTreeObserver mTreeObserver; Canvas mCanvas; final ViewRootImpl mViewRootImpl; final Handler mHandler; . final Outline mTmpOutline = new Outline(); . int mAccessibilityWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; . AttachInfo(IWindowSession session, IWindow window, Display display, ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer, Context context) { mSession = session; mWindow = window; mWindowToken = window.asBinder(); mDisplay = display; mViewRootImpl = viewRootImpl; mHandler = handler; mRootCallbacks = effectPlayer; mTreeObserver = new ViewTreeObserver(context); }}Copy the code

AttachInfo is actually a global information bound to the View drawing process of the Entire Android Framework. It determines how the entire Android View tree should be rendered. Here are the core attributes:

  • 1.Callbacks internal interface, the implementor is ViewRootImpl, can make some simple key sound
  • 2.InvalidateInfo Internal class. This class records which parts of the View tree are dirty and need to be refreshed.
  • IWindowSession mSession A WindowSession object that connects to IMS, WMS, and other services. Is a service-oriented facade operator.
  • 4.IWindow mWindow corresponds to the WMS client, that is, a Window object that communicates across processes
  • 5.IBinder mWindowToken Currently AttachInfo The Token corresponding to Window. This handle will have a corresponding record in WMS.
  • 6.Display mDisplay The object obtained by the DisplayManager in which the screen properties are recorded.
  • 7.View mRootView The root layout of the View tree
  • 8. Boolean mHardwareAccelerated Whether hardware acceleration is required
  • MThreadedRenderer is the core object for hardware rendering
  • 10. MDisplayState Specifies the current screen state. If the state changes, the ViewRootImpl needs to change as well.
  • 11. MApplicationScale determines whether the View needs to be enlarged during the drawing process according to the Display object.
  • 12. MWindowLeft coordinates of the Window corresponding to the current AttachInfo View
  • MUse32BitDrawingCache Whether to draw using 32-bit cache
  • MOverscanInsets, mContentInsets, mVisibleInsets and other insects, these are the areas discussed in the fourth ARTICLE of WMS, such as scanning area, content area, visible area and so on
  • 15. Boolean mAlwaysConsumeNavBar This flag is half of a multi-window display that tells the Android system not to display the navigation bar all the time
  • 16. MScrollContainers Views that can be scrolled, or that can be shifted due to soft keyboard changes
  • 17. MHasWindowFocus Whether the window has a focus
  • 18. MInTouchMode Whether the View is in touch mode
  • 19. Whether to keep the screen lit while mKeepScreenOn refreshes
  • 20. MSystemUiVisibility Check whether the SYSTEM UI, such as statusBar, is displayed
  • ViewTreeObserver mTreeObserver listens for the rendering of the View tree
  • 22.Canvas mCanvas; The cache of the bitmap for the View tree

ViewRootImpl setView

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;
                }
                attrs = mWindowAttributes;
                setTag();


                // Keep track of the actual window flags supplied by the client.
                mClientWindowLayoutFlags = attrs.flags;

                setAccessibilityFocus(null, null);

                if (view instanceof RootViewSurfaceTaker) {
                    mSurfaceHolderCallback =
                            ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                    if (mSurfaceHolderCallback != null) {
                        mSurfaceHolder = new TakenSurfaceHolder();
                        mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                        mSurfaceHolder.addCallback(mSurfaceHolderCallback);
                    }
                }

                // Compute surface insets required to draw at specified Z value.
                // TODO: Use real shadow insets for a constant max Z.
                if (!attrs.hasManualSurfaceInsets) {
                    attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
                }

                CompatibilityInfo compatibilityInfo =
                        mDisplay.getDisplayAdjustments().getCompatibilityInfo();
                mTranslator = compatibilityInfo.getTranslator();

                // If the application owns the surface, don't enable hardware acceleration
                if (mSurfaceHolder == null) {
                    // While this is supposed to enable only, it can effectively disable
                    // the acceleration too.
                    enableHardwareAcceleration(attrs);
                    final boolean useMTRenderer = MT_RENDERER_AVAILABLE
                            && mAttachInfo.mThreadedRenderer != null;
                    if (mUseMTRenderer != useMTRenderer) {
                        // Shouldn't be resizing, as it's done only in window setup,
                        // but end just in case.
                        endDragResizing();
                        mUseMTRenderer = useMTRenderer;
                    }
                }

                boolean restore = false;
                if (mTranslator != null) {
                    mSurface.setCompatibilityTranslator(mTranslator);
                    restore = true;
                    attrs.backup();
                    mTranslator.translateWindowLayout(attrs);
                }

                if (!compatibilityInfo.supportsScreen()) {
                    attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                    mLastInCompatMode = true;
                }

                mSoftInputMode = attrs.softInputMode;
                mWindowAttributesChanged = true;
                mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
                mAttachInfo.mRootView = view;
                mAttachInfo.mScalingRequired = mTranslator != null;
                mAttachInfo.mApplicationScale =
                        mTranslator == null ? 1.0f : mTranslator.applicationScale;
                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();
                if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                    mInputChannel = new InputChannel();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                if (mTranslator != null) {
                    mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
                }
                mPendingOverscanInsets.set(0, 0, 0, 0);
                mPendingContentInsets.set(mAttachInfo.mContentInsets);
                mPendingStableInsets.set(mAttachInfo.mStableInsets);
                mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout);
                mPendingVisibleInsets.set(0, 0, 0, 0);
                mAttachInfo.mAlwaysConsumeNavBar =
                        (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
                mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
...

                if (view instanceof RootViewSurfaceTaker) {
                    mInputQueueCallback =
                        ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
                }
                if (mInputChannel != null) {
                    if (mInputQueueCallback != null) {
                        mInputQueue = new InputQueue();
                        mInputQueueCallback.onInputQueueCreated(mInputQueue);
                    }
                    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
                }

                view.assignParent(this);
                mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
                mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

                if (mAccessibilityManager.isEnabled()) {
                    mAccessibilityInteractionConnectionManager.ensureConnection();
                }

                if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                    view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
                }

                // Set up the input pipeline.
                CharSequence counterSuffix = attrs.getTitle();
                mSyntheticInputStage = new SyntheticInputStage();
                InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
                InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
                InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
                InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
                InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
                InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

                mFirstInputStage = nativePreImeStage;
                mFirstPostImeInputStage = earlyPostImeStage;
                mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
            }
        }
    }
Copy the code

The things that are done here, apart from the performance tracking logic, are the following three big things, the source code is scattered, and here is the overall package:

  • 1. Bind the Display status obtained from DisplayManager to AttachInfo. And register the Viewrotimpl Handler with the DisplayManager to listen for callbacks. Get the compatibility information of the current screen from Display, and get the coordinate converter. If yes, the contents on the screen need to be scaled. Finally, store all this information in the mApplicationScale at AttachInfo and record the root View at AttachInfo (DecorView)

  • 2. According to the transfer of the WindowManager. LayoutParams set soft keyboard mode, and call requestLayout trigger the next round of the View tree traversal. The addToDisplay method of WindowSession is then called to add the current Window to the WMS service for processing. Several mContentInsets obtained from addToDisplay are recorded in mAttachInfo and ViewRootImpl.

  • 3. Clear the focus of AccessibilityService. And verify that the service is still on the link listener.

  • 4. If there is no InputChannel, create an InputChannel and listen for click events. This object listens for click events sent by the IMS service through the socket and is eventually passed to our Activity.

RequestLayout requests the next View tree traversal

@Override public void requestLayout() { if (! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } void checkThread() { if (mThread ! = Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); }}Copy the code

We can see this in action and why an asynchronous call to the requestLayout method breaks. RequestLayout calls the checkThread for each call to the main thread.

If you are walking onLayout method, mHandlingLayoutInLayoutRequest flag to true, call requestLayout is prohibited. If requestLayout is invoked, mLayoutRequested is set to true. Finally, scheduleTraversals are called to perform the View tree traversal and rendering of the Handler’s next Looper.

scheduleTraversals

void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

1. First set mTraversalScheduled to true. The postSyncBarrier method is then called to set up the synchronization barrier.

2. MChoreographer sends a CALLBACK_TRAVERSAL through the postCallback method, listening for the arrival of the Vsync signal, and then executing the mTraversalRunnable method.

  • 3. ScheduleConsumeBatchedInput treatment is not the key message is sent

  • 4. NotifyRendererOfFramePending inform hardware rendering mechanism, trying to make the current state of the animation.

  • PokeDrawLockIfNeeded If the current screen is found to be in the dormancy low consumption DOZE state, powerManager. WakeLock will be used to force the screen to light up. Inside this is an object that lights up the screen).

The key is the View tree traversal process. To understand this process thoroughly, let’s take a look at how Handler’s synchronization barrier works.

Handler synchronization barrier

public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } private int postSyncBarrier(long when) { synchronized (this) { final int token = mNextBarrierToken++; final Message msg = Message.obtain(); msg.markInUse(); msg.when = when; msg.arg1 = token; Message prev = null; Message p = mMessages; if (when ! = 0) { while (p ! = null && p.when <= when) { prev = p; p = p.next; } } if (prev ! = null) { // invariant: p == prev.next msg.next = p; prev.next = msg; } else { msg.next = p; mMessages = msg; } return token; }}Copy the code

You can see that this method is actually quite simple, just inserting a MSG recorded as the current moment into the mMessages list. Of course, the biggest difference between this synchronous barrier message and normal messages is that we can go back to the normal method of queuing and see what is done:

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
Copy the code

In fact, for every message queued from Handler, the target points to Handler with the intent of calling back to Handler’s callback. The biggest difference is that normal messages have a target set, while message barrier messages have no target set.

When Looper is awakened, message. next in Looper’s loop method will be called to iterate over MSG, executing the following fragment:

if (msg ! = null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg =  msg; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); }Copy the code

In this code, check that msg.target is null and walk in. Handler information that has the isAsynchronous flag turned on is then preferentially executed. Move the other message execution back. Then we can assume that the message that the handler draws as it traverses the View tree is an asynchronous message.

            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
Copy the code

MChoreographer postCallback in this method according to my previous articles will call to FrameDisplayEventReceiver. OnVsync. The run method of mTraversalRunnable is called from the doFrame method by the Handler in this callback.

@Override
        public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
....
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }
Copy the code

You can see the Message receiving the Vsync signal callback, which is an Asynchronous Message. Can be executed within a synchronization barrier.

            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
Copy the code

You can see that the message Choreographer.CALLBACK_TRAVERSAL is the penultimate order of execution, and the mTraversalRunnable method is executed in doCallbacks.

This is how Android prioritizes the View drawing message as much as possible. The principle is very similar to the drawing mechanism of flutter. Now that we know how mTraversalRunnable is executed, let’s take a look at what happens inside the runnable.

TraversalRunnable

final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); performTraversals(); if (mProfile) { Debug.stopMethodTracing(); mProfile = false; }}}Copy the code

As you can see, the mTraversalBarrier is first removed by removeSyncBarrier. This allows the Handler to proceed to the next message. However, this is still in a method executed by the Handler, so no other message will interrupt the View tree’s drawing priority.

Then there is the rendering core of the whole View tree, performTraversals.

performTraversals

This method is very long, here we divide it into 4 parts: drawing preparation, onAttachWindow binding window,onApplyWindowInsets distributing window spacing, preparing hardware rendering Surface,onMeasure, onLayout, onDraw.

Draw ready onAttachWindow

Next, I will put aside the hardware rendering process and focus on the software rendering process.

private void performTraversals() { // cache mView since it is used so much below... final View host = mView; if (host == null || ! mAdded) return; mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; int desiredWindowHeight; final int viewVisibility = getHostVisibility(); final boolean viewVisibilityChanged = ! mFirst && (mViewVisibility ! = viewVisibility || mNewSurfaceNeeded || mAppVisibilityChanged); mAppVisibilityChanged = false; final boolean viewUserVisibilityChanged = ! mFirst && ((mViewVisibility == View.VISIBLE) ! = (viewVisibility == View.VISIBLE)); WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } CompatibilityInfo compatibilityInfo = mDisplay.getDisplayAdjustments().getCompatibilityInfo(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { params = lp; mFullRedrawNeeded = true; mLayoutRequested = true; if (mLastInCompatMode) { params.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = false; } else { params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; mLastInCompatMode = true; } } mWindowAttributesChangesFlag = 0; Rect frame = mWinFrame; if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; final Configuration config = mContext.getResources().getConfiguration(); if (shouldUseDisplaySize(lp)) { // NOTE -- system code, won't try to do compat mode. Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { desiredWindowWidth = mWinFrame.width(); desiredWindowHeight = mWinFrame.height(); } mAttachInfo.mUse32BitDrawingCache = true; mAttachInfo.mHasWindowFocus = false; mAttachInfo.mWindowVisibility = viewVisibility; mAttachInfo.mRecomputeGlobalAttributes = false; mLastConfigurationFromResources.setTo(config); mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility; if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) { host.setLayoutDirection(config.getLayoutDirection()); } host.dispatchAttachedToWindow(mAttachInfo, 0); mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true); dispatchApplyInsets(host); } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth ! = mWidth || desiredWindowHeight ! = mHeight) { mFullRedrawNeeded = true; mLayoutRequested = true; windowSizeMayChange = true; } } if (viewVisibilityChanged) { mAttachInfo.mWindowVisibility = viewVisibility; host.dispatchWindowVisibilityChanged(viewVisibility); if (viewUserVisibilityChanged) { host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE); } if (viewVisibility ! = View.VISIBLE || mNewSurfaceNeeded) { endDragResizing(); destroyHardwareResources(); } if (viewVisibility == View.GONE) { mHasHadWindowFocus = false; } } if (mAttachInfo.mWindowVisibility ! = View.VISIBLE) { host.clearAccessibilityFocus(); }... }Copy the code

In this process, we can divide the situation into two types, the first view of the first render and the second view of the first render.

The first rendering can be divided into the following steps:

  • 1. If shouldUseDisplaySize is used to determine the System UI, such as statusbar, volume, etc., desiredWindowWidth and desiredWindowHeight will be set to the screen width and height.
  • 2. If it is not the System UI, set the mWindowFrame obtained from session.addTodislPay to the current width and height of the screen ready for rendering.
  • 3. SetLayoutDirection: Sets the rendering direction of the entire View tree. Those of you familiar with overseas development should know the difference between marginStart and marginLeft before. The former can be set according to region reading preferences such as text from left to right or right to left rendering.
  • 4. DispatchAttachedToWindow dispatches the onAttachWindow method starting from the current root View(DecorView).

If not first render: the steps are much simpler:

  • 1. Set desiredWindowWidth and desiredWindowHeight to mWindowFrame width and height.
  • 2. At this time will directly determine whether DecorView also visible, if the visible the callback passed out from the root DecorView dispatchWindowVisibilityChanged method to change the visibility of the Window.

If not, clear the focus of accessibility services.

There are three points worth noting in this preparation process:

  • 1. How is mWindowFrame calculated, and what is the difference between the screen width and height
  • 2. The dispatchAttachedToWindow of the first render call, which dispatches what AttachInfo did.
  • 3. DispatchWindowVisibilityChanged distribute what events.

AddToDisplay preliminarily calculates the screen area

We focus on the initial method of area calculation of addToDisplay PhoneWindowManager. GetLayoutHintLw.

public boolean getLayoutHintLw(WindowManager.LayoutParams attrs, Rect taskBounds, DisplayFrames displayFrames, Rect outFrame, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) { final int fl = PolicyControl.getWindowFlags(null, attrs); final int pfl = attrs.privateFlags; final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs); final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs); final int displayRotation = displayFrames.mRotation; final int displayWidth = displayFrames.mDisplayWidth; final int displayHeight = displayFrames.mDisplayHeight; final boolean useOutsets = outOutsets ! = null && shouldUseOutsets(attrs, fl); if (useOutsets) { int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources()); if (outset > 0) { if (displayRotation == Surface.ROTATION_0) { outOutsets.bottom += outset; } else if (displayRotation == Surface.ROTATION_90) { outOutsets.right += outset; } else if (displayRotation == Surface.ROTATION_180) { outOutsets.top += outset; } else if (displayRotation == Surface.ROTATION_270) { outOutsets.left += outset; } } } final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) ! = 0; final boolean layoutInScreenAndInsetDecor = layoutInScreen && (fl & FLAG_LAYOUT_INSET_DECOR) ! = 0; final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) ! = 0; if (layoutInScreenAndInsetDecor && ! screenDecor) { int availRight, availBottom; if (canHideNavigationBar() && (sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) ! = 0) { outFrame.set(displayFrames.mUnrestricted); availRight = displayFrames.mUnrestricted.right; availBottom = displayFrames.mUnrestricted.bottom; } else { outFrame.set(displayFrames.mRestricted); availRight = displayFrames.mRestricted.right; availBottom = displayFrames.mRestricted.bottom; } outStableInsets.set(displayFrames.mStable.left, displayFrames.mStable.top, availRight - displayFrames.mStable.right, availBottom - displayFrames.mStable.bottom); if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) ! = 0) { if ((fl & FLAG_FULLSCREEN) ! = 0) { outContentInsets.set(displayFrames.mStableFullscreen.left, displayFrames.mStableFullscreen.top, availRight - displayFrames.mStableFullscreen.right, availBottom - displayFrames.mStableFullscreen.bottom); } else { outContentInsets.set(outStableInsets); } } else if ((fl & FLAG_FULLSCREEN) ! = 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) ! = 0) { outContentInsets.setEmpty(); } else { outContentInsets.set(displayFrames.mCurrent.left, displayFrames.mCurrent.top, availRight - displayFrames.mCurrent.right, availBottom - displayFrames.mCurrent.bottom); } if (taskBounds ! = null) { calculateRelevantTaskInsets(taskBounds, outContentInsets, displayWidth, displayHeight); calculateRelevantTaskInsets(taskBounds, outStableInsets, displayWidth, displayHeight); outFrame.intersect(taskBounds); } outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame) .getDisplayCutout()); return mForceShowSystemBars; } else { if (layoutInScreen) { outFrame.set(displayFrames.mUnrestricted); } else { outFrame.set(displayFrames.mStable); } if (taskBounds ! = null) { outFrame.intersect(taskBounds); } outContentInsets.setEmpty(); outStableInsets.setEmpty(); outDisplayCutout.set(DisplayCutout.NO_CUTOUT); return mForceShowSystemBars; }}Copy the code

We can see that the region of the form is actually computed for the first time according to the SystemUI flag bit:

  • 1. According to the rotation direction of the current window, set the rotation degree of the current window to display.

  • 2. If FLAG_LAYOUT_IN_SCREEN is enabled, the FLAG_LAYOUT_INSET_DECOR flag is disabled, and the PRIVATE_FLAG_IS_SCREEN_DECOR flag is disabled. The following branches will follow:

    • 1. If the navigation bar is allowed to be hidden, the SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION flag is enabled. Set the region to the mUnrestricted area, which is the official screen size, but does not include the scanned area, as I described in calculating the form size.
    • 2. Otherwise, set outFrame to the mRestricted area. This area is the screen size, but if the status bar cannot be hidden, the height of the status bar is subtracted, excluding the scanned area.

Next, the outStableInsets are embedded in the region, and the content is embedded in the region. That is to say, based on these embedded areas that are padded to the left, the next position is the true Stable Content area.

  • If the FLAG_LAYOUT_IN_SCREEN flag is turned on, set mUnrestricted to the mWindowFrame area. Otherwise, it is mStable.

Leave the rest of the logic behind. We can conclude that mUnrestricted is the largest scanning area. The restricted area can contain both the navigation bar and the status bar, but the two areas can overlap. Next up is the Stable area, or Stable content area. In fact, the different immersion mode is to change the view wrotimPL display area to switch between these three display areas.

ViewGroup dispatchAttachedToWindow

void dispatchAttachedToWindow(AttachInfo info, int visibility) { mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; super.dispatchAttachedToWindow(info, visibility); mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW; final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; child.dispatchAttachedToWindow(info, combineVisibility(visibility, child.getVisibility())); } final int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size(); for (int i = 0; i < transientCount; ++i) { View view = mTransientViews.get(i); view.dispatchAttachedToWindow(info, combineVisibility(visibility, view.getVisibility())); }}Copy the code

Since the first layout in ViewRootImpl is a DecorView, it is both a ViewGroup and a FrameLayout. It essentially iterates through the dispatchAttachedToWindow method of all the child views that are bound to the ViewGroup.

View dispatchAttachedToWindow

void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; if (mOverlay ! = null) { mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility); } mWindowAttachCount++; mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY; if (mFloatingTreeObserver ! = null) { info.mTreeObserver.merge(mFloatingTreeObserver); mFloatingTreeObserver = null; } registerPendingFrameMetricsObservers(); if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) ! = 0) { mAttachInfo.mScrollContainers.add(this); mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED; } // Transfer all pending runnables. if (mRunQueue ! = null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; } performCollectViewAttributes(mAttachInfo, visibility); onAttachedToWindow(); ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = li ! = null ? li.mOnAttachStateChangeListeners : null; if (listeners ! = null && listeners.size() > 0) { for (OnAttachStateChangeListener listener : listeners) { listener.onViewAttachedToWindow(this); } } int vis = info.mWindowVisibility; if (vis ! = GONE) { onWindowVisibilityChanged(vis); if (isShown()) { onVisibilityAggregated(vis == VISIBLE); } } onVisibilityChanged(this, visibility); if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) ! = 0) { // If nobody has evaluated the drawable state yet, then do it now. refreshDrawableState(); } needGlobalAttributesUpdate(false); notifyEnterOrExitForAutoFillIfNeeded(true); }Copy the code
  • 1. Assign AttachInfo of the ViewRootImpl to mAttachInfo of the View and then to OverLayer of the View. This float layer is useful because it doesn’t take up too many layers and is good for adding ICONS to a View that don’t do anything.

  • 2. Merge the View’s own mFloatingTreeObserver(if any) into the global ViewRootImpl’s ViewTreeObserver listener.

  • 3. RegisterPendingFrameMetricsObservers set hardware rendering frame drop and render finished listening.

    private void registerPendingFrameMetricsObservers() {
        if (mFrameMetricsObservers != null) {
            ThreadedRenderer renderer = getThreadedRenderer();
            if (renderer != null) {
                for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
                    renderer.addFrameMetricsObserver(fmo);
                }
            } else {
...
            }
        }
    }
Copy the code
  • 4. Check whether the PFLAG_SCROLL_CONTAINER flag is enabled using the setScrollContainer method. This flag bit sets whether the View can be moved up when the keyboard pops up. The effect is similar to adjustPan. Can also through XML android: isScrollContainer Settings.

  • 5. Execute the Runnable method set in View via post. One way to use it is very common:

view.post(new Runnable());
Copy the code

This ensures that the Runnable object is handled in the main thread. The principle is simple:

public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo ! = null) { return attachInfo.mHandler.post(action); } getRunQueue().post(action); return true; }Copy the code

This essentially determines if the current View is bound to mAttachInfo, and delegates the event to the ViewRootImpl Handler. Otherwise the event will be stored in the HandlerActionQueue until it is consumed before the actual rendering begins.

  • 6. PerformCollectViewAttributes collection View visible attributes:
    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
        if ((visibility & VISIBILITY_MASK) == VISIBLE) {
            if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
                attachInfo.mKeepScreenOn = true;
            }
            attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
                attachInfo.mHasSystemUiListeners = true;
            }
        }
    }
Copy the code

If a View’s mKeepScreenOn is true, the global View is true. If a View has SystemUI visible, then it is globally visible. This is collecting the flag bit behavior of each View.

  • 7. Call the onAttachedToWindow method and then call all callbacks that listen for onAttachedToWindow behavior.
    protected void onAttachedToWindow() {
        if ((mPrivateFlags & PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
            mParent.requestTransparentRegion(this);
        }

        mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;

        jumpDrawablesToCurrentState();

        resetSubtreeAccessibilityStateChanged();

        rebuildOutline();

        if (isFocused()) {
            InputMethodManager imm = InputMethodManager.peekInstance();
            if (imm != null) {
                imm.focusIn(this);
            }
        }
    }
Copy the code

There is not much work to do here. The main thing is to handle the situation where there are several states in Drawable and select one of them. Add a flag to tell Accessibility that the entire View hierarchy has been rebuilt. Rebuild the View’s ViewOutlineProvider, also known as the outer box (which can be used for rounded corners, and QMUI is also implemented this way); If the foucs flag bit is typed, tell InputMethodManager(soft keyboard manager) to pop up with that View as the focus.

  • 8. If the View is not Gone, will call onWindowVisibilityChanged set the current window when the visibility of the need to deal with.
protected void onWindowVisibilityChanged(@Visibility int visibility) { if (visibility == VISIBLE) { initialAwakenScrollBars(); } } private boolean initialAwakenScrollBars() { return mScrollCache ! = null && awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade * 4, true); }Copy the code

You can see the display time that actually wakes up the Scrollbar(the pulley that rolls up and down). The default time is 300 milliseconds for fade-in and fade-out.

  • 9. OnVisibilityAggregated the main job here is to call aggregated views that you focus on in AutofillManager, those that are displayed. By the way, AutofillManager is a system service for automating string filling, which is not discussed here.

  • 10. Call the onVisibilityChanged method callback. We can override this method when we’re writing a View to listen for visibility.

  • 11. Added the PFLAG_DRAWABLE_STATE_DIRTY flag bit above. RefreshDrawableState is called to refresh the state content of the Drawable.

  • 12. NotifyEnterOrExitForAutoFillIfNeeded is actually AutofillManager call notifyViewEntered method, tell AutofillManager services have traverse to this level.

dispatchWindowVisibilityChanged

public void dispatchWindowVisibilityChanged(@Visibility int visibility) { onWindowVisibilityChanged(visibility); } protected void onWindowVisibilityChanged(@Visibility int visibility) { if (visibility == VISIBLE) { initialAwakenScrollBars(); }}Copy the code

In fact, the logic here is almost identical to the Window Attach step above. Note Any Window from invisible to visible will attempt to display the scroll wheel once.

The viewrotimpl draws ready to distribute WindowInsets

getRunQueue().executeActions(mAttachInfo.mHandler); boolean insetsChanged = false; boolean layoutRequested = mLayoutRequested && (! mStopped || mReportNextDraw); if (layoutRequested) { final Resources res = mView.getContext().getResources(); if (mFirst) { mAttachInfo.mInTouchMode = ! mAddedTouchMode; ensureTouchModeLocally(mAddedTouchMode); } else { if (! mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) { insetsChanged = true; } if (! mPendingContentInsets.equals(mAttachInfo.mContentInsets)) { insetsChanged = true; } if (! mPendingStableInsets.equals(mAttachInfo.mStableInsets)) { insetsChanged = true; } if (! mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout)) { insetsChanged = true; } if (! mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); } if (! mPendingOutsets.equals(mAttachInfo.mOutsets)) { insetsChanged = true; } if (mPendingAlwaysConsumeNavBar ! = mAttachInfo.mAlwaysConsumeNavBar) { insetsChanged = true; } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowSizeMayChange = true; if (shouldUseDisplaySize(lp)) { Point size = new Point(); mDisplay.getRealSize(size); desiredWindowWidth = size.x; desiredWindowHeight = size.y; } else { Configuration config = res.getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); } } } windowSizeMayChange |= measureHierarchy(host, lp, res, desiredWindowWidth, desiredWindowHeight); } if (collectViewAttributes()) { params = lp; } if (mAttachInfo.mForceReportNewAttributes) { mAttachInfo.mForceReportNewAttributes = false; params = lp; } if (mFirst || mAttachInfo.mViewVisibilityChanged) { mAttachInfo.mViewVisibilityChanged = false; int resizeMode = mSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST; if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) { final int N = mAttachInfo.mScrollContainers.size(); for (int i=0; i<N; i++) { if (mAttachInfo.mScrollContainers.get(i).isShown()) { resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; } } if (resizeMode == 0) { resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN; } if ((lp.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) ! = resizeMode) { lp.softInputMode = (lp.softInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) | resizeMode;  params = lp; } } } if (params ! = null) { if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) ! = 0) { if (! PixelFormat.formatHasAlpha(params.format)) { params.format = PixelFormat.TRANSLUCENT; } } mAttachInfo.mOverscanRequested = (params.flags & WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN) ! = 0; } if (mApplyInsetsRequested) { mApplyInsetsRequested = false; mLastOverscanRequested = mAttachInfo.mOverscanRequested; dispatchApplyInsets(host); if (mLayoutRequested) { windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); } } if (layoutRequested) { mLayoutRequested = false; } boolean windowShouldResize = layoutRequested && windowSizeMayChange && ((mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight()) || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth &&  frame.width() ! = mWidth) || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() ! = mHeight)); windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM; windowShouldResize |= mActivityRelaunched; final boolean computesInternalInsets = mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners() || mAttachInfo.mHasNonEmptyGivenInternalInsets; boolean insetsPending = false; int relayoutResult = 0; boolean updatedConfiguration = false; final int surfaceGenerationId = mSurface.getGenerationId(); final boolean isViewVisible = viewVisibility == View.VISIBLE; final boolean windowRelayoutWasForced = mForceNextWindowRelayout;Copy the code
  • 1. It has its own RunQueue in ViewRootImpl. It’s also passed in via post. However, this task queue is specifically used to process click events received from IMS, etc.

  • 2. MLayoutRequested This flag bit is set to true in requestLayout. And mStopped is false. So it now goes to the branch of layoutRequested.

    • 1. This is the first rendering, and the ADD_FLAG_IN_TOUCH_MODE flag will set the reverse state to MattachInfo.minTouchMode. This flag bit actually indicates keystroke navigation rather than the touch screen.
    • 2. If it is not the first rendering, insetsChanged will be true if the current display area obtained by the above addToDisplay is compared to the current display area as long as one of them has changed. If the final judgment to the WindowManager. LayoutParams are wide high WRAP_CONTENT, windowSizeMayChange force is set to true. And update the global width and height.

In either case, the measureHierarchy method is used to measure the width and height of the View at each level of the View tree. We’ll talk about performMeasure in the next section.

  • 3. If the View.requestApplyInsets method is invoked, the dispatchApplyInsets method is invoked to distribute insets. If requestLayout starts the call mLayoutRequested is true, Call measureHierarchy to determine if a change has occurred.
  • 4. Proceed with the flag bit, windowShouldResize. The windowShouldResize flag bit indicates whether the window is recalculated. MWindowFrame is true if the width is smaller than the window and different from the current width and height. The ViewRootImpl also enforces true if it finds that the Activity is re-logged in or logged in for the first time.

Of note in this process is the distribution of Inset.

dispatchApplyInsets

void dispatchApplyInsets(View host) { WindowInsets insets = getWindowInsets(true /* forceConstruct */); final boolean dispatchCutout = (mWindowAttributes.layoutInDisplayCutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS); if (! dispatchCutout) { insets = insets.consumeDisplayCutout(); } host.dispatchApplyWindowInsets(insets); }Copy the code

Very simply, get a WindowInsets object by getWindowInsets. If LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS did not open, is refers to the window does not use Liu Haibing excavated area, call the WindowInsets. This area consumeDisplayCutout consumption, expand. Finally, it is distributed through the DecorView.

So there are three functions worth noting:

  • 1. The getWindowInsets function retrieves the contents of the region
  • 2. How do consumeDisplayCutout WindowInsets consume areas
  • What did 3. DispatchApplyWindowInsets ViewGroup distribution.
getWindowInsets
/* package */ WindowInsets getWindowInsets(boolean forceConstruct) { if (mLastWindowInsets == null || forceConstruct) { mDispatchContentInsets.set(mAttachInfo.mContentInsets); mDispatchStableInsets.set(mAttachInfo.mStableInsets); mDispatchDisplayCutout = mAttachInfo.mDisplayCutout.get(); Rect contentInsets = mDispatchContentInsets; Rect stableInsets = mDispatchStableInsets; DisplayCutout displayCutout = mDispatchDisplayCutout; if (! forceConstruct && (! mPendingContentInsets.equals(contentInsets) || ! mPendingStableInsets.equals(stableInsets) || ! mPendingDisplayCutout.get().equals(displayCutout))) { contentInsets = mPendingContentInsets; stableInsets = mPendingStableInsets; displayCutout = mPendingDisplayCutout.get(); } Rect outsets = mAttachInfo.mOutsets; if (outsets.left > 0 || outsets.top > 0 || outsets.right > 0 || outsets.bottom > 0) { contentInsets = new Rect(contentInsets.left + outsets.left, contentInsets.top + outsets.top, contentInsets.right + outsets.right, contentInsets.bottom + outsets.bottom); } contentInsets = ensureInsetsNonNegative(contentInsets, "content"); stableInsets = ensureInsetsNonNegative(stableInsets, "stable"); mLastWindowInsets = new WindowInsets(contentInsets, null /* windowDecorInsets */, stableInsets, mContext.getResources().getConfiguration().isScreenRound(), mAttachInfo.mAlwaysConsumeNavBar, displayCutout); } return mLastWindowInsets; }Copy the code

Note that the outsets are the mOutSets parameter in the getLayoutHintL method above. The mOutSets object is actually retrieved by the relayout method of the subsequent Session. If it’s not the first rendering, Also through ScreenShapeHelper. GetWindowOutsetBottomPx access com. Android. Internal. R.i nteger. Config_windowOutsetBottom this resource to set the spacing area.

You can see that the process is actually to set the Inset of each type of area in the Window into the WindowInsets.

consumeDisplayCutout

So consumeDisplayCutout makes a lot of sense:

    public WindowInsets consumeDisplayCutout() {
        final WindowInsets result = new WindowInsets(this);
        result.mDisplayCutout = null;
        result.mDisplayCutoutConsumed = true;
        return result;
    }
Copy the code

Actually the space area of the distance screen of bang screen is smoothed.

ViewGroup dispatchApplyWindowInsets

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { insets = super.dispatchApplyWindowInsets(insets); if (! insets.isConsumed()) { final int count = getChildCount(); for (int i = 0; i < count; i++) { insets = getChildAt(i).dispatchApplyWindowInsets(insets); if (insets.isConsumed()) { break; } } } return insets; }Copy the code

Actually very simple ViewGroup, first dispatchApplyWindowInsets method of dealing with the View. Then iterate through each child View dispatchApplyWindowInsets method. Determine if the View needs to consume Inset. If it does, jump out of the loop.

    public boolean isConsumed() {
        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
                && mDisplayCutoutConsumed;
    }
Copy the code

True when you see that only four areas of the WindowInsets need to be consumed. So we pay attention to have a look at the View dispatchApplyWindowInsets method.

View dispatchApplyWindowInsets

public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) { try { mPrivateFlags3 |= PFLAG3_APPLYING_INSETS; if (mListenerInfo ! = null && mListenerInfo.mOnApplyWindowInsetsListener ! = null) { return mListenerInfo.mOnApplyWindowInsetsListener.onApplyWindowInsets(this, insets); } else { return onApplyWindowInsets(insets); } } finally { mPrivateFlags3 &= ~PFLAG3_APPLYING_INSETS; }}Copy the code

In this case, it’s easy to turn the PFLAG3_APPLYING_INSETS flag on and off each time you experience the method. If there are mOnApplyWindowInsetsListener callback, according to the result of callback onApplyWindowInsets returns. So let’s focus on the default onApplyWindowInsets method.

    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        if ((mPrivateFlags3 & PFLAG3_FITTING_SYSTEM_WINDOWS) == 0) {
            if (fitSystemWindows(insets.getSystemWindowInsets())) {
                return insets.consumeSystemWindowInsets();
            }
        } else {
            if (fitSystemWindowsInt(insets.getSystemWindowInsets())) {
                return insets.consumeSystemWindowInsets();
            }
        }
        return insets;
    }
Copy the code

Note that in this method, the PFLAG3_FITTING_SYSTEM_WINDOWS flag bit is checked to see if it is enabled. It’s going to go to the branch above the if. Note that the object passed in to fitSystemWindowsInt is actually the mSystemWindowInsets object in the entire WindowInsets, but instead of manipulating that object, we’re copying it. Therefore, mSystemWindowInsets values obtained by WindowInsets are not affected.

fitSystemWindows
protected boolean fitSystemWindows(Rect insets) { if ((mPrivateFlags3 & PFLAG3_APPLYING_INSETS) == 0) { if (insets == null) { return false; } try { mPrivateFlags3 |= PFLAG3_FITTING_SYSTEM_WINDOWS; return dispatchApplyWindowInsets(new WindowInsets(insets)).isConsumed(); } finally { mPrivateFlags3 &= ~PFLAG3_FITTING_SYSTEM_WINDOWS; } } else { return fitSystemWindowsInt(insets); }}Copy the code

Since every time you experience dispatchApplyInsets the PFLAG3_APPLYING_INSETS flag is turned on, you go to the following branch.

    private boolean fitSystemWindowsInt(Rect insets) {
        if ((mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS) {
            mUserPaddingStart = UNDEFINED_PADDING;
            mUserPaddingEnd = UNDEFINED_PADDING;
            Rect localInsets = sThreadLocal.get();
            if (localInsets == null) {
                localInsets = new Rect();
                sThreadLocal.set(localInsets);
            }
            boolean res = computeFitSystemWindows(insets, localInsets);
            mUserPaddingLeftInitial = localInsets.left;
            mUserPaddingRightInitial = localInsets.right;
            internalSetPadding(localInsets.left, localInsets.top,
                    localInsets.right, localInsets.bottom);
            return res;
        }
        return false;
    }
Copy the code

Check whether FITS_SYSTEM_WINDOWS flag is enabled or false if not. When is this flag bit turned on? This is the familiar fitsSystemWindows tag added to the XML layout file:

                case com.android.internal.R.styleable.View_fitsSystemWindows:
                    if (a.getBoolean(attr, false)) {
                        viewFlagValues |= FITS_SYSTEM_WINDOWS;
                        viewFlagMasks |= FITS_SYSTEM_WINDOWS;
                    }
                    break;
Copy the code

There are two key behaviors:

  • 1. Obtain localInsets from thread private data, and use computeFitSystemWindows to calculate whether or not systemSystemWindows is fitSystemWindows, thus consuming SystemWindowInsets and putting the application at the top.
  • 2. InternalSetPadding Sets the inset as the padding in the View.

Note that this method returns, determines the WindowInsets whether consumeSystemWindowInsets method of execution.

computeFitSystemWindows
    protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
        WindowInsets innerInsets = computeSystemWindowInsets(new WindowInsets(inoutInsets),
                outLocalInsets);
        inoutInsets.set(innerInsets.getSystemWindowInsets());
        return innerInsets.isSystemWindowInsetsConsumed();
    }

    public WindowInsets computeSystemWindowInsets(WindowInsets in, Rect outLocalInsets) {
        if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
                || mAttachInfo == null
                || ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
                && !mAttachInfo.mOverscanRequested)) {
            outLocalInsets.set(in.getSystemWindowInsets());
            return in.consumeSystemWindowInsets().inset(outLocalInsets);
        } else {
            final Rect overscan = mAttachInfo.mOverscanInsets;
            outLocalInsets.set(overscan);
            return in.inset(outLocalInsets);
        }
    }
Copy the code
  • 1. There is no need for fitSystemWindow, full screen, and no need to cover the scan area. After will call consumeSystemWindowInsets, first call an inset new WindowInsets size calculation.
  • 2. Call Inset to set mOverscanInsets to WindowInsets and then return.

After the return call isSystemWindowInsetsConsumed judge:

    boolean isSystemWindowInsetsConsumed() {
        return mSystemWindowInsetsConsumed;
    }
Copy the code

WindowInsets calculation principle

At this point, it seems possible to be a little confused about what WindowInsets are, and what they calculate. Let’s look at the Inset method in WindowInsets.

public WindowInsets inset(Rect r) { return inset(r.left, r.top, r.right, r.bottom); } public WindowInsets inset(int left, int top, int right, int bottom) { Preconditions.checkArgumentNonnegative(left); Preconditions.checkArgumentNonnegative(top); Preconditions.checkArgumentNonnegative(right); Preconditions.checkArgumentNonnegative(bottom); WindowInsets result = new WindowInsets(this); if (! result.mSystemWindowInsetsConsumed) { result.mSystemWindowInsets = insetInsets(result.mSystemWindowInsets, left, top, right, bottom); } if (! result.mWindowDecorInsetsConsumed) { result.mWindowDecorInsets = insetInsets(result.mWindowDecorInsets, left, top, right, bottom); } if (! result.mStableInsetsConsumed) { result.mStableInsets = insetInsets(result.mStableInsets, left, top, right, bottom); } if (mDisplayCutout ! = null) { result.mDisplayCutout = result.mDisplayCutout.inset(left, top, right, bottom); if (result.mDisplayCutout.isEmpty()) { result.mDisplayCutout = null; } } return result; }Copy the code

Here we can see that the WindowInsets actually manage each area of the Window from the edge of the screen. Why is it a spaced region? In fact, if you look at addToDisplay you can see that the actual content area is always the spacing area first and then the width and height of the entire screen area. , respectively,

  • 1. UI spacing area of the system
  • 2.Decor space area
  • 3.Stable content spacing area
  • 4.DisplayCutout Bangs space area

Note the logic here, if it is determined that the Insets spacing area of the corresponding area is not needed, the inset method WindowInsets will be used to adjust each spacing, so that the content in the App can erase these distances, so as to achieve immersive mode, suitable for the bangs screen.

    private static Rect insetInsets(Rect insets, int left, int top, int right, int bottom) {
        int newLeft = Math.max(0, insets.left - left);
        int newTop = Math.max(0, insets.top - top);
        int newRight = Math.max(0, insets.right - right);
        int newBottom = Math.max(0, insets.bottom - bottom);
        if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
            return insets;
        }
        return new Rect(newLeft, newTop, newRight, newBottom);
    }
Copy the code

Taking this approach further, we actually subtract the corresponding size from each of the four directions of the insets. Make the entire Rect smaller, but not less than 0.

internalSetPadding

This piece of code is the heart of the fitSystemWindows flag bit in the entire XML layout file

protected void internalSetPadding(int left, int top, int right, int bottom) { mUserPaddingLeft = left; mUserPaddingRight = right; mUserPaddingBottom = bottom; final int viewFlags = mViewFlags; boolean changed = false; // Common case is there are no scroll bars. if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) ! = 0) { if ((viewFlags & SCROLLBARS_VERTICAL) ! = 0) { final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth(); switch (mVerticalScrollbarPosition) { case SCROLLBAR_POSITION_DEFAULT: if (isLayoutRtl()) { left += offset; } else { right += offset; } break; case SCROLLBAR_POSITION_RIGHT: right += offset; break; case SCROLLBAR_POSITION_LEFT: left += offset; break; } } if ((viewFlags & SCROLLBARS_HORIZONTAL) ! = 0) { bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight(); } } if (mPaddingLeft ! = left) { changed = true; mPaddingLeft = left; } if (mPaddingTop ! = top) { changed = true; mPaddingTop = top; } if (mPaddingRight ! = right) { changed = true; mPaddingRight = right; } if (mPaddingBottom ! = bottom) { changed = true; mPaddingBottom = bottom; } if (changed) { requestLayout(); invalidateOutline(); }}Copy the code

I’m going to set the default padding of the View based on how many insets are left after consumption. Usually you have toppadding and bottompadding. If a View such as a ScrollView has the potential to scroll horizontally, it will also set the padding value of the current View based on the remaining Insets.

WindowInsets and non-immersion Padding Settings

So by what judgment do I go to this method and do the Padding?

    public static final int SYSTEM_UI_LAYOUT_FLAGS =
            SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
            | SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
Copy the code

In general, common UI display SYSTEM_UI_LAYOUT_FLAGS the sign bit is closed, so will go computeSystemWindowInsets the branches above.

If set to transparent status bar, transparent navigation SYSTEM_UI_LAYOUT_FLAGS this mark will open, go computeSystemWindowInsets branches below. So it’s going to return two completely different results to the top, internalSetPadding set Padding.

The transparent status bar returns a padding of (0,0,0,0), whereas the normal UI display returns a padding of (0, spacing area of the status bar height,0,0). So where is it set up?

Call collectViewAttributes on the setView to get the View’s parameters. GetImpliedSystemUiVisibility method, which is the two WindowManager. LayoutParams special flag bits into the sign of the View:

private int getImpliedSystemUiVisibility(WindowManager.LayoutParams params) { int vis = 0; // Translucent decor window flags imply stable system ui visibility. if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) ! = 0) { vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; } if ((params.flags & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) ! = 0) { vis |= View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; } return vis; }Copy the code

In this way, the four padding directions obtained by setting the transparent flag bit are 0. The non-transparent (i.e., immersive) UI has four padding directions (0, spacing area of the status bar at the same height,0,0).

Pay attention to the process, in fact, the immersion judgment to mAttachInfo. MSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) to 0 (that is, no open), Adding a paddingTop value to the root layout of the DecorView results in a UI representation discrepancy between immersion and non-immersion.

Principle of fitsSystemWindows setup under immersion

After you figure out the difference between immersion and non-immersion and you set the Padding difference. Although fitWindowInsets is obtained through computeSystemWindowInsets padding to the four directions, but is not the same principle.

Here we can trace the loading of the DecorView method in setContentView.

private void installDecor() { mForceDecorInstall = false; . if (mContentParent == null) { mContentParent = generateLayout(mDecor); mDecor.makeOptionalFitsSystemWindows();Copy the code

DecorView makeOptionalFitsSystemWindows is the core. This method is underneath the ViewGroup.

public void makeOptionalFitsSystemWindows() { super.makeOptionalFitsSystemWindows(); final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { children[i].makeOptionalFitsSystemWindows(); }}Copy the code
    public void makeOptionalFitsSystemWindows() {
        setFlags(OPTIONAL_FITS_SYSTEM_WINDOWS, OPTIONAL_FITS_SYSTEM_WINDOWS);
    }
Copy the code

In other words, the first level child of each DecorView carries the OPTIONAL_FITS_SYSTEM_WINDOWS flag bit, and because it carries the flag bit. SYSTEM_UI_LAYOUT_FLAGS is enabled because it is in immersive mode. So by default, no padding is added to the root layout.

After the custom Xml root layout, fitSystemWindowsInt will start handling Insets by default because the FITS_SYSTEM_WINDOWS flag bit is turned on. Because the OPTIONAL_FITS_SYSTEM_WINDOWS flag bit was not turned on, a Padding was added to the current custom layout.

WindowInsets example

getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
Copy the code

If we use the theme NoActionBar. Will have the following expression:

You can see that when we don’t do anything, the entire content area is at the top, overlapping with the status bar. How do we usually solve this?

<LinearLayout 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:id="@+id/pp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
Copy the code

We usually add a fitsSystemWindows flag bit to the root layout so that the content layout is under the transparent status bar. The diagram below:

Here, to explore the process more clearly, I reflected a few key attributes from ViewRootImpl, View.attachInfo (reflected from Android 7.0). In general, not much has changed, except for the bangs.

The following is the transparent status bar parameter without fitsSystemWindows flag bit:

  • 1. All Insets such as mContentInsets, mStableInsets, and mVisibleInsets are 0 and 72 respectively. Only the mOverscanInsets that represent the scanned area are 0. These data are retrieved by addToDisplay(which is actually the addWindow of WMS).
  • 2. MWindowFrame is the size that needs to be drawn in the drawing preparation stage mentioned above, that is, the height of the whole screen.
  • 3. There is no padding in the layout file, so the padding is 0.
  • 4. By default, only mWindowDecorInsetsConsumed consumption, in all areas of the Decor is the distance between content area consumption.

Next look at the parameter with the fitsSystemWindows flag bit turned on:

You can actually see that only the fitSystemWindows flag bit in the View process has an mPaddingTop that has the same height difference as the WindowInsets. It fits the logic above. Because of the PaddingTop, the color below the transparent status bar is the background color.

The other parameters in addToDisplay already determine the size of the Insets, so they will all be the same.

Please note that only mWindowDecorInsetsConsumed flags here, is set to true, the other is false. Since many of these attributes and methods are hidden in Android 9.0, I’m reflecting Android 7.0, which is almost logically consistent, except for the lack of bangs.

The question is, why is it that once the flag bit of the transparent status bar is turned on, the entire content layout is on top? Here you need to see the relayoutWindow method below. ,

Recalculate the Window size and prepare the Surface based on the size

        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
            mForceNextWindowRelayout = false;

            if (isViewVisible) {

                insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
            }
            if (mSurfaceHolder != null) {
                mSurfaceHolder.mSurfaceLock.lock();
                mDrawingAllowed = true;
            }

            boolean hwInitialized = false;
            boolean contentInsetsChanged = false;
            boolean hadSurface = mSurface.isValid();

            try {

                if (mAttachInfo.mThreadedRenderer != null) {
                    if (mAttachInfo.mThreadedRenderer.pauseSurface(mSurface)) {
                        mDirty.set(0, 0, mWidth, mHeight);
                    }
                    mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
                }
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);



                if (!mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {

                    performConfigurationChange(mPendingMergedConfiguration, !mFirst,
                            INVALID_DISPLAY /* same display */);
                    updatedConfiguration = true;
                }

                final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
                        mAttachInfo.mOverscanInsets);
                contentInsetsChanged = !mPendingContentInsets.equals(
                        mAttachInfo.mContentInsets);
                final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
                        mAttachInfo.mVisibleInsets);
                final boolean stableInsetsChanged = !mPendingStableInsets.equals(
                        mAttachInfo.mStableInsets);
                final boolean cutoutChanged = !mPendingDisplayCutout.equals(
                        mAttachInfo.mDisplayCutout);
                final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
                final boolean surfaceSizeChanged = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
                surfaceChanged |= surfaceSizeChanged;
                final boolean alwaysConsumeNavBarChanged =
                        mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
                if (contentInsetsChanged) {
                    mAttachInfo.mContentInsets.set(mPendingContentInsets);
                }
                if (overscanInsetsChanged) {
                    mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);

                    contentInsetsChanged = true;
                }
                if (stableInsetsChanged) {
                    mAttachInfo.mStableInsets.set(mPendingStableInsets);
                    contentInsetsChanged = true;
                }
                if (cutoutChanged) {
                    mAttachInfo.mDisplayCutout.set(mPendingDisplayCutout);

                    contentInsetsChanged = true;
                }
                if (alwaysConsumeNavBarChanged) {
                    mAttachInfo.mAlwaysConsumeNavBar = mPendingAlwaysConsumeNavBar;
                    contentInsetsChanged = true;
                }
                if (contentInsetsChanged || mLastSystemUiVisibility !=
                        mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
                        || mLastOverscanRequested != mAttachInfo.mOverscanRequested
                        || outsetsChanged) {
                    mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                    mLastOverscanRequested = mAttachInfo.mOverscanRequested;
                    mAttachInfo.mOutsets.set(mPendingOutsets);
                    mApplyInsetsRequested = false;
                    dispatchApplyInsets(host);
                }
                if (visibleInsetsChanged) {
                    mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
                }

                if (!hadSurface) {
                    if (mSurface.isValid()) {
                        newSurface = true;
                        mFullRedrawNeeded = true;
                        mPreviousTransparentRegion.setEmpty();

                        if (mAttachInfo.mThreadedRenderer != null) {
                            try {
                                hwInitialized = mAttachInfo.mThreadedRenderer.initialize(
                                        mSurface);
                                if (hwInitialized && (host.mPrivateFlags
                                        & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
                                    mSurface.allocateBuffers();
                                }
                            } catch (OutOfResourcesException e) {
                                handleOutOfResourcesException(e);
                                return;
                            }
                        }
                    }
                } else if (!mSurface.isValid()) {

                    if (mLastScrolledFocus != null) {
                        mLastScrolledFocus.clear();
                    }
                    mScrollY = mCurScrollY = 0;
                    if (mView instanceof RootViewSurfaceTaker) {
                        ((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
                    }
                    if (mScroller != null) {
                        mScroller.abortAnimation();
                    }
                    // Our surface is gone
                    if (mAttachInfo.mThreadedRenderer != null &&
                            mAttachInfo.mThreadedRenderer.isEnabled()) {
                        mAttachInfo.mThreadedRenderer.destroy();
                    }
                } else if ((surfaceGenerationId != mSurface.getGenerationId()
                        || surfaceSizeChanged || windowRelayoutWasForced)
                        && mSurfaceHolder == null
                        && mAttachInfo.mThreadedRenderer != null) {
                    mFullRedrawNeeded = true;
                    try {
                        mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
                    } catch (OutOfResourcesException e) {
                        handleOutOfResourcesException(e);
                        return;
                    }
                }

                final boolean freeformResizing = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM) != 0;
                final boolean dockedResizing = (relayoutResult
                        & WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_DOCKED) != 0;
                final boolean dragResizing = freeformResizing || dockedResizing;
                if (mDragResizing != dragResizing) {
                    if (dragResizing) {
                        mResizeMode = freeformResizing
                                ? RESIZE_MODE_FREEFORM
                                : RESIZE_MODE_DOCKED_DIVIDER;
                        // TODO: Need cutout?
                        startDragResizing(mPendingBackDropFrame,
                                mWinFrame.equals(mPendingBackDropFrame), mPendingVisibleInsets,
                                mPendingStableInsets, mResizeMode);
                    } else {
                        endDragResizing();
                    }
                }
                if (!mUseMTRenderer) {
                    if (dragResizing) {
                        mCanvasOffsetX = mWinFrame.left;
                        mCanvasOffsetY = mWinFrame.top;
                    } else {
                        mCanvasOffsetX = mCanvasOffsetY = 0;
                    }
                }
            } catch (RemoteException e) {
            }


            mAttachInfo.mWindowLeft = frame.left;
            mAttachInfo.mWindowTop = frame.top;

            if (mWidth != frame.width() || mHeight != frame.height()) {
                mWidth = frame.width();
                mHeight = frame.height();
            }

            if (mSurfaceHolder != null) {
                if (mSurface.isValid()) {
                    mSurfaceHolder.mSurface = mSurface;
                }
                mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
                mSurfaceHolder.mSurfaceLock.unlock();
                if (mSurface.isValid()) {
                    if (!hadSurface) {
                        mSurfaceHolder.ungetCallbacks();

                        mIsCreating = true;
                        SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                        if (callbacks != null) {
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceCreated(mSurfaceHolder);
                            }
                        }
                        surfaceChanged = true;
                    }
                    if (surfaceChanged || surfaceGenerationId != mSurface.getGenerationId()) {
                        SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                        if (callbacks != null) {
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceChanged(mSurfaceHolder, lp.format,
                                        mWidth, mHeight);
                            }
                        }
                    }
                    mIsCreating = false;
                } else if (hadSurface) {
                    mSurfaceHolder.ungetCallbacks();
                    SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                    if (callbacks != null) {
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceDestroyed(mSurfaceHolder);
                        }
                    }
                    mSurfaceHolder.mSurfaceLock.lock();
                    try {
                        mSurfaceHolder.mSurface = new Surface();
                    } finally {
                        mSurfaceHolder.mSurfaceLock.unlock();
                    }
                }
            }

            final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
            if (threadedRenderer != null && threadedRenderer.isEnabled()) {
                if (hwInitialized
                        || mWidth != threadedRenderer.getWidth()
                        || mHeight != threadedRenderer.getHeight()
                        || mNeedsRendererSetup) {
                    threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
                            mWindowAttributes.surfaceInsets);
                    mNeedsRendererSetup = false;
                }
            }
Copy the code

Here are a few things to be done:

    1. In the Settings in the WindowManager. LayoutParams parameters (such as transparent status bar logo) is passed to the relayoutWindow method to calculate the total size of the PhoneWindow.
  • 2. If the visible state of each area changes, the WindowInsets need to be redistributed and the proper padding set for the View.

  • 3. Update the Surface status. If found mThreadedRenderer is not null, and the Surface effectively, and has not been initialized, call the mThreadedRenderer. Initialization initialize hardware rendering Surface.

  • 4. If it is found that the Surface failure, will call mThreadedRenderer. Destroy destroy hardware rendering object.

  • 5. If it is found that the size of the Surface updated, will mThreadedRenderer. Update updateSurface hardware rendering corresponding size under the Surface.

  • 6. If the mSurfaceHolder is not empty, surfaceCreated, surfaceChanged, and surfaceDestroyed are called back to the listener according to the corresponding behavior.

  • 7. Find that if hardware rendering needs to load Surface initialization, call threadedRenderer.setup to initialize it.

conclusion

At this point, the View’s drawing process is ready.

Prior to onMeasure, an important preparatory step is performed, which covers the entire ViewRootImpl drawing scope. It can be broken down into five simple steps:

    1. AddToDisplay preliminarily calculates the screen values of the Window
  • 2.dispatchAttachedToWindow
  • 3.dispatchApplyWindowInsets
  • 4. RelayoutWindow calculates the size and location of the window
  • 5. Prepare hardware rendering

AddToDisplay summary

Essentially, it calls the addWindow method of WMS. This process saves the current PhoneWindow remote object to WMS for management. The IMS service is also saved to the App side in this way. Another important thing to do here is to take the distance between each different area of the form and the screen and return it to the App for consumption.

After this, Choreographer will listen on the synchronization signals of Vsync to begin the actual View tree traversal and drawing.

DispatchAttachedToWindow summary

DispatchAttachedToWindow is essentially the first time a View is bound to a method called in the View’s drawing tree throughout the View wrootimPL. At its core, the binding essentially synchronizes the parameters of the drawing process across view View PL. WindowInests, whether Windows are visible, root layouts, hardware render objects, screen states, and Binder communicators for WindowSession. With this context-through-drawing, ViewRootImpl can better manage the drawing of each View.

AutoFillManagerService is also initialized during distribution. And initialize the View’s boxed draw object Outline.

DispatchApplyWindowInsets summary

This process actually handles states such as the fitSysytemWindows flag bit. Essentially, Android forms have their own space between them and the screen. But you can consume, erase the spacing in this step. Common such as bangs screen adaptation, transparent navigation bar and transparent status bar adaptation.

When we have open these particular WindowManager. LayoutParams flags, the entire screen is normal structure, content area under the status bar area.

The reason why fitSysytemWindow is set in the transparent status bar is that fitSysytemWindow is set in the transparent status bar, and a normal display of the View tree without any flag bits does not work. Because in ViewRootImpl setView phase, parsing the WindowManager. LayoutParams of two special immersion flags, into the View of the flag.

And in the process of distributing consumer Insets, computeSystemWindowInsets judgement should be figuring out how much the size of the return an Inset area, and then to the current View of four directions padding set corresponding numerical values. This is the core idea of FitSytem Windows.

If the transparent status bar is set, the four padding values of (0,0,0,0) are returned because SYSTEM_UI_LAYOUT_FLAGS is turned on by default (because this flag contains the hidden navigation bar and full screen). A normal status bar returns (0, status bar height,0,0) to the Decorview layout.

The fitSystemWindows flag bit is only valid in immersive mode because in non-immersive mode, the Insets are already consumed in a DecorView subhierarchy. Never reach our custom layout for the padding Settings. FitSystemWindows works in immersive mode because the View is not marked with the OPTIONAL_FITS_SYSTEM_WINDOWS flag bit and is marked with the FITS_SYSTEM_WINDOWS flag bit.

The OPTIONAL_FITS_SYSTEM_WINDOWS flag will skip the calculation of Insets in immersive mode. That’s where this note comes from.

RelayoutWindow summary

Once we have the size of the Window and the padding value of the distance from the drawing area ready, we hand the data to WMS’s relayoutWindow method, which computes the entire Android system. The actual placement of the status bar and content area is also determined in this method.

Calculate the size of a window (4)

Prepare hardware rendering summaries

Initialization can be divided into the following three steps:

  • 1.mThreadedRenderer.initialize
  • 2.mThreadedRenderer.updateSurface
  • 3.threadedRenderer.setup

Destruction is

  • 4.mThreadedRenderer.destroy

Remember the 3 steps of hardware render object initialization and destruction. This will be followed by a special topic for analysis.