The words written in the front

SetContentView, as an entry point to your Activity, is a great first lesson in the source code exercise. Reading source code is painful, of course, but in the current employment environment, reading source code is imminent.

This diagram shows the relationship between Activity, Window, and DecorView. It also helps us understand setContentView.

Images from the Internet

The text start

The setContentView in the Activity points to AppCompatDelegate, which is an abstract class. The only implementation class is AppCompatDelegateImpl

So we go to the setContentView for AppCompatDelegateImpl and the code looks like this:

@Override public void setContentView(int resId) { ensureSubDecor(); // Make sure the DecorView is initialized. ViewGroup contentParent = mSubDecor. FindViewById (Android.r.d.c ontent); contentParent.removeAllViews(); LayoutInflater.from(mContext).inflate(resId, contentParent); / / add layout mAppCompatWindowCallback. GetWrapped () onContentChanged (); // Tell window to refresh}Copy the code

There are only five lines of code in this method, so let’s examine them one by one:

EnsureSubDecor (), by its name, is to make sure that Decor has been added. The goal is to make sure that mSubDecor objects are not empty.

MSubDecor is a ViewGroup object that follows the layout of an Activity. You might hear it called a DecorView a lot in an interview. So the second line of code is the Viewgroup that gets the content,

The third row clears. Make sure the root layout is clean without any child views.

Fourth line, add our layout to the content.

The fifth line, through windowCallback, refreshes the content.

Ok, the XML is displayed in the window.

ensureSubDecor

Private void ensureSubDecor() {// Check whether mSubDecor if (! MSubDecorInstalled) {// Create a DecorView. MSubDecor = createSubDecor(); // Check whether there is a title. // If a title was set before we installed the decor, propagate it now CharSequence title = getTitle(); if (! TextUtils.isEmpty(title)) { if (mDecorContentParent ! = null) { mDecorContentParent.setWindowTitle(title); } else if (peekSupportActionBar() ! = null) { peekSupportActionBar().setWindowTitle(title); } else if (mTitleView ! = null) { mTitleView.setText(title); } } applyFixedSizeWindow(); onSubDecorInstalled(mSubDecor); mSubDecorInstalled = true; // Invalidate if the panel menu hasn't been created before this. // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu // being called in the middle of onCreate or similar. // A pending invalidation will typically be resolved before the posted message // would run normally in order to satisfy instance state restoration. PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false); if (! mIsDestroyed && (st == null || st.menu == null)) { invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); }}}Copy the code

This method doesn’t expose much valid information, but the logic has a closed loop here, which is to determine if onSubDecorInstalled is false. Then after the createSubDecor and onSubDecorInstalled methods are executed, Set onSubDecorInstalled to true. To facilitate reading the source code, let’s extract the valid code:

if (! mSubDecorInstalled) { mSubDecor = createSubDecor(); applyFixedSizeWindow(); onSubDecorInstalled(mSubDecor); mSubDecorInstalled = true; }Copy the code

So, next, let’s take a look at createSubDecor and onSubDecorInstalled.

createSubDecor

private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
​
    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
        throw new IllegalStateException(
                "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
    }
​
    if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
        // Don't allow an action bar if there is no title.
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
        requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
    }
    if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
        requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();
​
    // Now let's make sure that the Window has installed its decor by retrieving it
    ensureWindow();
    mWindow.getDecorView();
​
    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;
​
​
    if (!mWindowNoTitle) {
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);
​
            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
             * This needs some explanation. As we can not use the android:theme attribute
             * pre-L, we emulate it by manually creating a LayoutInflater using a
             * ContextThemeWrapper pointing to actionBarTheme.
             */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
​
            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }
​
            // Now inflate the view using the themed context and set it as the content view
            subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                    .inflate(R.layout.abc_screen_toolbar, null);
​
            mDecorContentParent = (DecorContentParent) subDecor
                    .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());
​
            /**
             * Propagate features to DecorContentParent
             */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
        if (mOverlayActionMode) {
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_screen_simple_overlay_action_mode, null);
        } else {
            subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
        }
​
        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                    new OnApplyWindowInsetsListener() {
                        @Override
                        public WindowInsetsCompat onApplyWindowInsets(View v,
                                WindowInsetsCompat insets) {
                            final int top = insets.getSystemWindowInsetTop();
                            final int newTop = updateStatusGuard(top);
​
                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(
                                        insets.getSystemWindowInsetLeft(),
                                        newTop,
                                        insets.getSystemWindowInsetRight(),
                                        insets.getSystemWindowInsetBottom());
                            }
​
                            // Now apply the insets on our view
                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                    new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                        @Override
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = updateStatusGuard(insets.top);
                        }
                    });
        }
    }
​
    if (subDecor == null) {
        throw new IllegalArgumentException(
                "AppCompat does not support the current theme features: { "
                        + "windowActionBar: " + mHasActionBar
                        + ", windowActionBarOverlay: "+ mOverlayActionBar
                        + ", android:windowIsFloating: " + mIsFloating
                        + ", windowActionModeOverlay: " + mOverlayActionMode
                        + ", windowNoTitle: " + mWindowNoTitle
                        + " }");
    }
​
    if (mDecorContentParent == null) {
        mTitleView = (TextView) subDecor.findViewById(R.id.title);
    }
​
    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);
​
    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);
​
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
    if (windowContentView != null) {
        // There might be Views already added to the Window's content view so we need to
        // migrate them to our content view
        while (windowContentView.getChildCount() > 0) {
            final View child = windowContentView.getChildAt(0);
            windowContentView.removeViewAt(0);
            contentView.addView(child);
        }
​
        // Change our content FrameLayout to use the android.R.id.content id.
        // Useful for fragments.
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);
​
        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }
​
    // Now set the Window's content view with the decor
    mWindow.setContentView(subDecor);
​
    contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
        @Override
        public void onAttachedFromWindow() {}
​
        @Override
        public void onDetachedFromWindow() {
            dismissPopups();
        }
    });
​
    return subDecor;
}
Copy the code

From line 1 through 23, does this look familiar? We’ll see that a lot when we’re getting custom properties in custom views, regardless of our main thread, so I’ll skip it. Lines 26 and 27 have a special comment: Make sure Windows has decor installed

Line 33 and up to line 119 is a big block of if~else code that does only one thing, assignment to subDecor. At line 121, subDecor was nulled. If it was, an exception was thrown and our logic came to a screeching halt.

On line 137, there’s a comment: Make sure window matches decor.

In line 132 and 139, two views were obtained by subDecor, one TextView (R.I.D.watch) and one ContentFrameLayout (R.I.D.action_bar_activity_content).

Line 142, get windowContentView (R.i.C.ontent) from window

Here we go! Starting at line 143, determine if windowContentView has any sub-views. If so, cut and paste all the sub-views onto the Content of the subDecor. Then change the layout ID of the content to R.I.D.C. tent. After this step, there are no child views on the Content of the window.

Line 165, add subdecor to the window.

Finally, return to subdecor.

To simplify this method, the effective code looks like this:

private ViewGroup createSubDecor() { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); // omit minor code a.ricycle (); // Make sure the window has decor ensureWindow() installed; mWindow.getDecorView(); final LayoutInflater inflater = LayoutInflater.from(mContext); ViewGroup subDecor = null; If (mDecorContentParent == null) {mTitleView = (TextView) subDecor. FindViewById (R.i.Tate); } / / make sure subDecor with Windows unifies ViewUtils makeOptionalFitsSystemWindows (subDecor); final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById( R.id.action_bar_activity_content); final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content); if (windowContentView ! = null) { while (windowContentView.getChildCount() > 0) { final View child = windowContentView.getChildAt(0); windowContentView.removeViewAt(0); contentView.addView(child); } windowContentView.setId(View.NO_ID); contentView.setId(android.R.id.content); } mWindow.setContentView(subDecor); return subDecor; }Copy the code

applyFixedSizeWindow()

The method is relatively simple, get R.I.C.tent, then set the inner margin, then rearrange the requestLayout, this part is not related to our main line, press no table.

private void applyFixedSizeWindow() {
    ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
​
    // This is a bit weird. In the framework, the window sizing attributes control
    // the decor view's size, meaning that any padding is inset for the min/max widths below.
    // We don't control measurement at that level, so we need to workaround it by making sure
    // that the decor view's padding is taken into account.
    final View windowDecor = mWindow.getDecorView();
    cfl.setDecorPadding(windowDecor.getPaddingLeft(),
            windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
            windowDecor.getPaddingBottom());
​
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
    a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
    a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
​
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
                cfl.getFixedWidthMajor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
                cfl.getFixedWidthMinor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
                cfl.getFixedHeightMajor());
    }
    if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
        a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
                cfl.getFixedHeightMinor());
    }
    a.recycle();
​
    cfl.requestLayout();
}
Copy the code

onSubDecorInstalled()

void onSubDecorInstalled(ViewGroup subDecor) {}
Copy the code

An empty method, so this is a surprise ah!

The words in the back

SetContentView is important as an entry point to an Activity. The relationship between Actvity, Window and DecorView is often asked in interviews. Understanding setContentView brings you a step closer to understanding Activity.

Inflate (resId, contentParent) See you in the next article!