Android programmers know that an Activity calls the setContentView by loading an XML layout file into the Activity. So: how exactly do you load an XML layout file into an Activity after calling setContentVIew? What is the difference between an Activity layout that inherits AppCompatActivity and an Activity that inherits? Then analyze the logic behind setContentView from the perspective of source code.

Because of the source code analysis, the following may cause deep discomfort.


Vomiting blood

Read the source code: read the source code with a purpose. For example: how exactly do YOU load an XML layout file into an Activity after calling setContentVIew? Then look for the answer to that question. It’s hard to look at the source code without a purpose. There is really very tired to see the source code. In writing this article, the source code is really read again and again. So stick to it.

1. Start with the Activity setContentVIew

AppCompatActivity appears after API22. All activities inherit it by default, making a lot of compatibility on appcompatActivities using the proxy mode, and adding some new features. But the base class for AppCompatActivity is still an Activity. Look at the setContentVIew of an Activity and then check to see what compatibility an Appactivity can make.

1.1 setContentView

Create a new MainActivity inheriting Activity:

public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}Copy the code

Jump to Activiy to see setContentView:

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
Copy the code

Look at this note:

    * Set the activity content from a layout resource.  The resource will be
    * inflated, adding all top-level views to the activity.
Copy the code

The meaning of this note:

Calling this method loads the XML layout file from the resource file into the Activity’s top-level View.

Load the layout file from the resource file into your Activity. Notice, there’s a concept of a top-level View, and we’ll talk about that in a little bit.

The setContentView call loads the layout file from the resource file into the Activity. (It’s a bit blunt to jump to conclusions from comments, but look at the code to see how calling setContentView loads the resource file into the Activity.)


1.2. Top layer Window — PhoneWindow

Let’s keep looking at the code. The setContentView() method of getWindow() is called in the Activity setContentView().

 getWindow().setContentView(view);
Copy the code

In the Activity, jump to getWindow() and look at this method:

    /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }
Copy the code

You can see that the getWindow() method returns a Window. Jump to the Window class and see what a Window is:

/** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as  the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * * <p>The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */ public abstract class Window { ... }Copy the code

Take a closer look at the following notes:

* Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the  top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc.Copy the code

The meaning of this note:

Abstract base class for top-level window appearance and behavior policies. An instance of this class should be added to the window manager as a top-level view. It provides standard UI policies, such as backgrounds, header areas, default key handling, and so on. It has only one implementation class: Android.view.phoneWindow.

Window is the abstract base class of the topmost Window. It provides policies for the Window’s appearance, behavior, and so on. It has only one implementation of PhoneWindow.

Note: the topmost View is mentioned above, and the topmost Window is mentioned here. The two are very different, and we will talk about their relationship later.

Since Windows only has one implementation of the PhoneWindow class, the Activity:

  getWindow().setContentView(layoutResID);
Copy the code

The PhoneWindow setContentView method is actually called!


1.3、PhoneWindow的setContentView

Jump to the PhoneWindow setContentView method:

@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }Copy the code

First take a look at the code in this method, starting with the mContentParent initialization logic. The following code loads the layout of the resource file with Id layoutResID into mContentParent. Because there’s a very important code inside: mLayOutinflater.inflate (layoutResID, mContentParent);

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
Copy the code

Mlayoutinflater.inflate is no stranger to any Android programmer. There is also a check if (hasFeature(FEATURE_CONTENT_TRANSITIONS)), and if true, transitionTo(newScene) is called at the end. This method also ends up loading the layout of the resource file with Id layoutResID into mContentParent. Start with transitionTo and proceed to the Scene class’s Enter method to find the answer:

public void enter() { // Apply layout change, if any if (mLayoutId > 0 || mLayout ! = null) { // empty out parent container before adding to it getSceneRoot().removeAllViews(); {if (mLayoutId > 0) {// Important code layoutinflater.from (mContext).inflate(mLayoutId, mSceneRoot); } else { mSceneRoot.addView(mLayout); }} / /... Some code is omitted here. }Copy the code

The answer will be found in the initialization of mLayoutId, mSceneRoot, which is not explained here.

PhoneWindow’s setContentView method loads the XML resource file into the mContentParent.

So what is mContentParent? How is it initialized?


1.4. PhoneWindow mContentParent and DecorView

Let’s look at what mContentParent is. MContentParent can be found in PhoneWindow:

    // This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;
Copy the code

What this note means is:

This is a View that puts the content of the Window. It’s not mDecor. It’s a sub-view of mDecor. (Regardless of mDecor, I’ll get to that later.)

Let’s keep looking at the code. Look at this judgment in the PhoneWindow setContentView:

if (mContentParent == null) { installDecor(); } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }Copy the code

Since this is a non-empty judgment of mContentParent, the installDecor() method must be the way to initialize mContentParent.

Skip to installDecor() to see how mContentParent is initialized:


1.4.1 PhoneWindow initializes the DecorView

InstallDecor () initializes the DecorView before corrupting the mContentParent. So let’s look at the initialization of the DecorView and the relationship between the DecorView and the Window.

private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } else { mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor); / / /... Omit some code}}Copy the code

The first is the initialization of mDecor. GenerateDecor is a way to initialize mDecor.

   if (mDecor == null) {
           mDecor = generateDecor(-1);
   }
Copy the code

In PhoneWindow, you can see the definition of mDecor:

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
Copy the code

The meaning of this note is:

This is the Window’s top View, which contains the Window decorations.

Jump to DecorView and see the code in DecorView:

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks { //... Leave out a lot of code}Copy the code

You can see what the DecorView does:

DecorView is a FrameLayout, which is the top-level View. The decorations that contain the Window (such as its size, transparency, etc.) are displayed on the View. Here is a general idea of the relationship between a top-level Window (PhoneWindow) and a top-level View (DecorView) : A top-level Window (PhoneWindow) contains state properties that are displayed in a top-level View (DecorView), such as Window size, background, etc. (Of course, the following code also shows this.)

Back to PhoneWindow, look at the DecorView initialization method generateDecor() in PhoneWindow:

protected DecorView generateDecor(int featureId) { // System process doesn't have application context and in that case we need to directly use // the context we have. Otherwise we want the application context, so we don't cling to the // activity. Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext().getResources()); // Set the theme if (mTheme! = -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorVIew (context, featureId, this, getAttributes()); }Copy the code

Finally, there is code like this:

  new DecorView(context, featureId, this, getAttributes());
Copy the code

So generateDecor() initializes the DecorView. The third parameter is PhoneWindow. The PhoneWindow is passed in when the DecorView is initialized. Take a look at the full if judgment:

  if (mDecor == null) {
      mDecor = generateDecor(-1);
  } else {
      mDecor.setWindow(this);
  }

Copy the code

If the DecorView is not empty, setWindow() is called. This passed in is also a PhoneWindow. Jump to the DecorVIew at the same time and see code similar to the following:

public void setWindowBackground(Drawable drawable) { if (getBackground() ! = drawable) { setBackgroundDrawable(drawable); if (drawable ! = null) { mResizingBackgroundDrawable = enforceNonTranslucentBackground(drawable, mWindow.isTranslucent() || mWindow.isShowingWallpaper()); } else { mResizingBackgroundDrawable = getResizingBackgroundDrawable( getContext(), 0, mWindow.mBackgroundFallbackResource, mWindow.isTranslucent() || mWindow.isShowingWallpaper()); } if (mResizingBackgroundDrawable ! = null) { mResizingBackgroundDrawable.getPadding(mBackgroundPadding); } else { mBackgroundPadding.setEmpty(); } drawableChanged(); }}Copy the code

Or something like this:

    final WindowManager.LayoutParams attrs = mWindow.getAttributes();
    if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
        if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
        }
    }
Copy the code

The above code initializes the DecorView properties by getting properties from the Window, or sets the DecorView properties from the Window properties. There is more code like this, but it makes sense that the window decorations mentioned above (such as size, transparency, etc.) are displayed in the DecorView. (This also illustrates the relationship between Window and DecorVIew.)


1.4.2 PhoneWindow initializes mContentParent

Back to the PhoneWindow class. After initializing the DecorView, proceed to the code to see how the mContentParent is initialized:

 if (mContentParent == null) {
          mContentParent = generateLayout(mDecor);
 }
Copy the code

As you can see, the generateLayout() method initializes the mContentParent. GenerateLayout () is too long. A few key snippets of code:

Protected ViewGroup generateLayout(DecorView decor) {// Apply data from current theme. // Take window setting Style TypedArray a = getWindowStyle(); MIsFloating = a.floating (r.style.window_WINDOwisfloating, false); int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR) & (~getForcedWindowFlags()); if (mIsFloating) { setLayout(WRAP_CONTENT, WRAP_CONTENT); setFlags(0, flagsToUpdate); } else { setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate); } / /... . Omit some code. }Copy the code

The first line of code is:

    TypedArray a = getWindowStyle();
Copy the code

TypedArray, if you have written custom controls, is no stranger to getting custom properties. Take a look at the implementation of getWindowStyle() :

/** * Return the {@link android.R.styleable#Window} attributes from this * window's theme. */ public final TypedArray getWindowStyle() { synchronized (this) { if (mWindowStyle == null) { mWindowStyle = mContext.obtainStyledAttributes( com.android.internal.R.styleable.Window); } return mWindowStyle; }}Copy the code

The getWindowStyle() method gets the window properties and returns them. If you have written a custom control, when writing a custom control, after obtaining the custom property, it is generally to assign the initial value to the member variable of the custom View. To determine the value of the custom View property. So that’s one idea for writing a custom View.

Of course! PhoneWindow does the same thing. Continue with the code below. Here’s some representative, slightly familiar code to see what happens when you get window style.

TypedArray a = getWindowStyle(); TypedArray a = getWindowStyle(); MIsFloating = a.floating (r.style.window_WINDOwisfloating, false); If (a.gottboolean (r.tyleable.window_windownotitle, False)) {} // is there any new type of lucent that is new to themis lucent = a.getBoolean(r.style.window_WINDOwistranslucent, false);Copy the code

The above code gets some values from TypedArray to determine:

  • Window is a window that can float. Dialog, etc.;
  • Check whether Title is set;
  • Check whether the window is transparent.
  • . .

These values are then copied to the PhoneWindow’s member variables. This is exactly the same logic used to obtain custom properties and assign initial values to a custom View.

What do I do after I get the Window property?

Continue to look at the code:

int layoutResource; // Get the Settings property and load the different XML. int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) ! = 0) { layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if (){// omit some code... . } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!" ); } // Load loaded layout files into the DecorView. MDecor. OnResourcesLoaded (mLayoutInflater, layoutResource);Copy the code

The above code first defines a layoutResource and then loads different layout files based on features’ values (features also get values based on properties set by Window). Then assign the layout file ID to layoutResource. The next code loads the layoutResource into the DecorView.

Note that although this is about initializing the mContentView, the layout is actually loaded into the DecorView.

Take a look at this code:

// Load the loaded layout file into the DecorView. MDecor. OnResourcesLoaded (mLayoutInflater, layoutResource);Copy the code

Jump to the DecorView to see an implementation of onResourcesLoaded:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { mStackId = getStackId(); if (mBackdropFrameRenderer ! = null) { loadBackgroundDrawablesIfNeeded(); mBackdropFrameRenderer.onResourcesLoaded( this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable, mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState), getCurrentColor(mNavigationColorViewState)); } mDecorCaptionView = createDecorCaptionView(inflater); final View root = inflater.inflate(layoutResource, null); if (mDecorCaptionView ! = null) { if (mDecorCaptionView.getParent() == null) { addView(mDecorCaptionView, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mDecorCaptionView.addView(root, new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT)); } else { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }Copy the code

MLayoutInflater again! When you look at layoutInflaters again, you know they have something to do with loading layouts. Take a look at the idea of onResourcesLoaded() : first load a View based on layoutResource:

final View root = inflater.inflate(layoutResource, null);
Copy the code

Then call addView() to add the View to the DecorView!

addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
Copy the code

This code side also has a DecorCaptionView judgment. So what is a DecorCaptionView? Find a way to initialize the DecorCaptionView.

// Free floating overlapping windows require a caption. private DecorCaptionView createDecorCaptionView(LayoutInflater inflater){ // ... Omit some code}Copy the code

What this note means is:

Free-floating overlapping Windows require a title.

Free-floating Windows? I don’t use this much in development. Maybe Dialog? So I’m not thinking about that for now. Only normal Activity loads are considered. So what layout is loaded into the DecorView? Go back to the PhoneWindow class generateLayout() to see the simplest layout of r.layout.screen_simple

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
Copy the code

This layout is a LinearLayout, which has a ViewStub and FrameLayout in it. Android :id=”@android:id/content” I’m sure someone has used findViewById(Android.r.i. D.c ontent) to get the root layout of the Activity content.

(If you have a copy of Android Art Exploration open it up to page 176, which also has a brief description of Android.R.I.D.C tent). Ps: This one just came to me.

If you haven’t used this ID before. Keep that in mind, I’ll use it in a second.

Continue with the generateLayout code:

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } / /... . Return contentParent; return contentParent;Copy the code

Look at this Id: ID_ANDROID_CONTENT

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
Copy the code

That’s right! This Id is the FrameLayout Id we talked about earlier. We then get a ViewGroup using the findById method. This ViewGroup is mContentParent. And then finally return this ViewGroup.


1.4.3 Summary of installDecor() method

InstallDecor () contains a lot of logic. Initialize the DecorView. Then initialize the mContentParent. When initializing mContentParent, you see the logic that gets the properties of the window and assigns them to the PhoneWindow. See the logic to load a different layout based on the window’s properties into the DecorView.

Finally, we get the mContentParent using the findViewById method and return it.


1.4.4 Back to PhoneWindow setContentView

This logic was also written down earlier. Let’s revisit the logic of PhoneWindow’s setContentView.

@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); RemoviewAllViews} else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); If (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else {// Add the resource file to the mContentParent mLayOutInflater.inflate (layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }Copy the code

Key code:

Mlayoutinflater.inflate (layoutResID, mContentParent); // Add the resource file to mContentParent.Copy the code

The logic for initializing the mContentParent was described earlier, where the layout file was loaded into the mContentParent.


1.4.5 Process Review

Step 1: Initialize the DecorView

 generateDecor(-1);
Copy the code

Step 2: Initialize mContentParent

generateLayout(mDecor);
Copy the code

While initializing the mContentParent, the PhoneWindow property is initialized and a LinearLayout is loaded into the Decoview. Step 3: Load the layout into mContentParent

 public void setContentView(int layoutResID) {
       mLayoutInflater.inflate(layoutResID, mContentParent);
 }
Copy the code

Draw a diagram to show the layout of an inherited Activity:


The layout structure when inheriting an Activity

So let’s go back a little bit

If the above content is already vomited, indeed I wrote this thing vomited. But you have to keep writing because there is a setContentView that inherits AppCompatActivity. If you can follow the above blog to finish reading, and can understand, I express very grateful. Don’t read the rest just yet. Make yourself a cup of hot tea or coffee and walk around and look out the window. Recall the flow after setContentView(). Recall how the DecorView, mContentPerant is initialized. Knowing this makes it easier to understand the process of inheriting setContentView from AppCompatActivity.


AppCompatActivity setContentVIew

Next look at the setContentView that inherits AppCompatActivity.

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }}Copy the code

Enter the setContentView of the AppCompatActivity:

 @Override
 public void setContentView(@LayoutRes int layoutResID) {
     getDelegate().setContentView(layoutResID);
 }
Copy the code

You can see that from here on, the setContentView logic of the inherited Activity is different. So what is getDelegate()? From delegate we can assume that this should be a proxy pattern. There is no research on proxy mode.


2.1 AppCompatDelegate for AppCompat

Jump to the getDelegate method:

    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
Copy the code

You can see that this is initializing the AppCompatDelegate. Jump to AppCompatDelegate, which has a line comment:

This class represents a delegate which you can use to extend AppCompat's support to any
Copy the code

This annotation means that this class represents a proxy that you can use to extend AppCompat’s support. So what does AppCompatDelegate do? Let’s continue with the code for the create method:

private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) { if (Build.VERSION.SDK_INT >= 24) { return new AppCompatDelegateImplN(context, window, callback); } else if (Build.VERSION.SDK_INT >= 23) { return new AppCompatDelegateImplV23(context, window, callback); } else if (Build.VERSION.SDK_INT >= 14) { return new AppCompatDelegateImplV14(context, window, callback); } else if (Build.VERSION.SDK_INT >= 11) { return new AppCompatDelegateImplV11(context, window, callback); } else { return new AppCompatDelegateImplV9(context, window, callback); }}Copy the code

As you can see, different implementations of the AppCompatDelegate are returned depending on the version. These AppCompatDelegateImpl all inherit from AppCompatDelegateImplV9. And the setContentView of AppCompatDelegateImplV9 is called to jump directly to AppCompatDelegateImplV9 to look at the setContentView method

    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
Copy the code

What exactly does an AppCompatDelegate do? AppCompatDelegate is a class that can be put into any Activity and call back the corresponding life cycle, which can provide a consistent theme and color for compatibility between different versions of widgets without using AppCompatActivity. Specific look at the two blog: www.jcodecraeer.com/a/anzhuokai… www.jianshu.com/p/66fa79ec0…


2.2 Initializing the SubDecorView

Take a look at the first method: ensureSubDecor()

private void ensureSubDecor() { if (! mSubDecorInstalled) { mSubDecor = createSubDecor(); // If a title was set before we installed the decor, propagate it now CharSequence title = getTitle(); if (! TextUtils.isEmpty(title)) { onTitleChanged(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 (! isDestroyed() && (st == null || st.menu == null)) { invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR); }}}Copy the code

From the above code, you can see that mSubDecor is initialized here. Check out the places that define mSubDecor:

    // true if we have installed a window sub-decor layout.
    private boolean mSubDecorInstalled;
    private ViewGroup mSubDecor;
Copy the code

This comment means:

If mSubDecorInstalled is true, we accidently formatted a window's child decorator layout.Copy the code

Window sub-decoration layout? Whatever it is, just remember him. The createSubDecor() method initializes mSubDecor. Jump in and have a look:

private ViewGroup createSubDecor() { TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme); / /... . Omit a lot of code if (al-qeada etBoolean (R.s tyleable AppCompatTheme_windowActionModeOverlay, false)) { requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY); } mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false); / /... . // Now let's make sure that the Window has installed its decor by retrieving it mwindow.getDecorview (); }Copy the code

Well, you’ve seen TypedArray again, but this time it’s fetching an AppCompatTheme. This will fetch the style of the AppCompatTheme. This time is not assigned to PhoneWindow, this time it was assigned to AppCompatDelegateImplBase member variables. Is actually AppCompatDelegateImplV9 member variables, because AppCompatDelegateImplV9 inherited from AppCompatDelegateImplBase. If an Activity inherits from AppCompatActivity, you don’t have an AppCompat Theme for that Activity. It collapses. That’s why.

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."); }Copy the code

If you inherit AppCompatActivity, there is no Theme set. The exception thrown must be:

 "You need to use a Theme.AppCompat theme (or descendant) with this activity.
Copy the code

2.2.1 Initialize the DecorView and mContentParent before initializing SubDecorView

Initializing the DecorView and mContentParent was seen earlier.

Next, take a look at the very important code:

    // Now let's make sure that the Window has installed its decor by retrieving it
    mWindow.getDecorView();
Copy the code

MWindow from the definition, AppCompatDelegateImplBase PhoneWindow can know it is. Jump to PhoneWindow’s getDecorView() method.

    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }  
Copy the code

Okay, installDecor() again. The installDecor() method does a lot of things.

Initialize the DecorView, accidentalize the mContentParent and load a LinearLayout. So getDecorView initializes the DecorView.

Read on:

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) { } }Copy the code

This logic is the same as initializing the mContentParent and loading a LinearLayout.

<android.support.v7.widget.FitWindowsLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/action_bar_root" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:fitsSystemWindows="true"> <android.support.v7.widget.ViewStubCompat android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/abc_action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" />  <include layout="@layout/abc_screen_content_include" /> </android.support.v7.widget.FitWindowsLinearLayout>Copy the code
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <android.support.v7.widget.ContentFrameLayout
            android:id="@id/action_bar_activity_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:foregroundGravity="fill_horizontal|top"
            android:foreground="?android:attr/windowContentOverlay" />

</merge>
Copy the code

You can see that this is similar to loading the LinearLayout when initializing the mContentParent. Remember ContentFrameLayout this View id: Android :id=”@id/action_bar_activity_content”


2.2.2 Substitution of ContentFrameLayout and mContentParent

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);Copy the code

The logic of this code: First get ContentFrameLayout from subDecor via the findViewById method. The FrameLayout in the LinearLayout in the DecorView is then retrieved using the PhoneWindow findViewById method. Then iterate through the FrameLayout in the DecorView to add its child View to the ContentFrameLayout. FrameLayout in the DecorView has no child VIew.

// 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);

Copy the code

Then set the FrameLayout Id in the DecorView to NO_ID and the ContentFrameLayout to Android.r.i.D.C. tent. Here is how ContentFrameLayout replaces FrameLayout in a DecorView.

   mWindow.setContentView(subDecor);
Copy the code

Then call mwindow.setContentView (subDecor); Load the SubDecorVIew into the PhoneWIndow’s mContentParent. This is done by inheriting the Activity of AppCompatActivity and calling setContentView.

As mentioned earlier, SubDecorVIew is the child decorator layout of the window, and this does load the SubDecorVIew into the DecorView.


2.3 Draw a diagram to show the process of inheriting setContentView of AppCompatActivity.

SetContentView (), which inherits AppCompatActivity, finally calls createSubDecor(). The logic for the Acitvity loading layout is all in this method: First step: Initialize the DecorView and mContentParent with the code:

        // Now let's make sure that the Window has installed its decor by retrieving it
        mWindow.getDecorView();
Copy the code


Initialize the DecorView and mContentParent

Step 2: Initialize the SubDecorView as follows:

final LayoutInflater inflater = LayoutInflater.from(mContext); ViewGroup subDecor = null; / /... . Omit some code subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, NULL);Copy the code


Initialize SubDecorView

Step 3: Replace SubDecorView with mContentParent in DecorView

 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);
            }
        }

Copy the code


SubDecorView and mContentParent replacement in DecorView

Step 4: Load the SubDecorView into the DecorView as follows:

        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);
Copy the code


Load the SubDecorView into the DecorView

Step 5: Add the layout from the resource file to ContentFrameLayout:

 @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
Copy the code

Finally, inherit the layout structure of AppCompatActivity:




Inherits the layout structure of AppCompatActivity

Don’t be surprised to see this level of layout, it’s just the way it is.

3, summarize

That’s the logic and layout of inheriting an Activity and inheriting an AppCompatActivity. If you’re already confused, take a look at the content of the drawing, that’s the essence of the summary.

Some words are written at the end

If at the end of the day someone finishes this blog and gets a sense of what it’s about, thank you very much indeed. Looking at the source code is really a very painful process for me now. In order to try to write a good source of this article read again and again. After writing it is really very relaxed, because I did learn a lot of dry goods.