Android Learning Road (12) Detailed explanation of setContentView

1. Introduction

We often see setContentView in Activity. Its function is to display our layout file in Activity. Let’s analyze how setContentView is done based on the source code.

2. Source code analysis

1. Two kinds of setContentView

Note that there are some differences between Activity's setContentView and AppCompatActivity's setContentView, so we have to analyze two setsContentView. Let's first analyze Activity's

2.Activity的setContentView

(1). Start with the setContentView method of Activity

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

You can see the first sentence getWindow().setContentView(layoutResID). This getWindow is to get the Window of the current Activity. The implementation class of Window in Android is phoneWindow, so we have to look at setContentView of phoneWindow.

By the way, the creation time of Activity's window is in the Activity's attach method:

(2).Continue to track setContentView of phoneWindow

    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; //⭐这个flag记一下
    }

I have marked three key points in the above code. Let’s continue to analyze what these three key points do. Let’s first analyze the first installDecor()

(2.1). Analyze the first key point of setContentView of phoneWindow, installDecor()

The main focus of installDecor is what I marked with the red box. Let's first analyze the generateDecor(-1) method:

(2.1.1). Analyze the generateDecor(-1) method in the installDecor() method

    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, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());//⭐重点
    }

After creating a DecorView and returning it, it is assigned to mDecor. Let's first take a look at what this DecorVIew is:

It is obviously a FrameLayout. Now we know that a DecorView of FrameLayout type is created and then assigned to the mDecor variable. Let’s continue to analyze the second key point of installDecor: generateLayout(mDecor)

(2.1.2). Analyze the second key point of installDecor: generateLayout(mDecor)

    protected ViewGroup generateLayout(DecorView decor) {
    ...
else {
            // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple; 
            // System.out.println("Simple!");
        }
 
        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //⭐重点下面的方法
 
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
    ...
        return contentParent;
    }
 
    //⭐DecorView的onResourcesLoaded方法
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        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();
    }

The function of this method is to select a layout file that comes with the system through the style or requestWindowFuture we set. The default is R.layout.screen_simple. After selecting the layout file, call mDecor.onResourcesLoaded(mLayoutInflater, layoutResource) ;After the inflate method comes out, add it to DecorView. Let's take a detailed look at the layout file 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>

It is this layout file that is inflated to DecorView and then passed

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);

This sentence obtains the FrameLayout that is the content, and then returns the contentParent.

The installDecor method has been analyzed here. To summarize, the installDecor method mainly creates a DecorView, then adds the selected layout file to the DecorView, and then uses findViewbyId to find the content of type FrameLayout and assign it to the contentParent. In fact, there is another step: The combination of DecorView and phoneWindow will not be discussed in detail here. We will discuss the FrameWorker source code analysis later.

(2.2). Continue to analyze the second key process focus of phoneWindow’s setContentView

mLayoutInflater.inflate(layoutResID, mContentParent);

This sentence is very obvious. layoutResID is a self-written layout file such as our activity_main.layout. Inflate it into mContentParent to give everyone a clearer sense through pictures:

(2.3). Continue to analyze the third key process focus of phoneWindow’s setContentView

mContentParentExplicitlySet = true;

The function of this flag First let's look at a piece of code:

public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
    }
}

This code will report an error when running:

requestFeature() must be called before adding content

Why is this error reported? Find it from the code:

    public final boolean requestWindowFeature(int featureId) {
        return getWindow().requestFeature(featureId);
    }
 
    //我们已经知道,getWindow其实获取的是PhoneWindow所以调用的是PhoneWindow的requestFeature
    @Override
    public boolean requestFeature(int featureId) {
        if (mContentParentExplicitlySet) { //⭐这就是报错的根源
            throw new AndroidRuntimeException("requestFeature() must be called before adding content"); 
        }
        final int features = getFeatures();
        final int newFeatures = features | (1 << featureId);
        if ((newFeatures & (1 << FEATURE_CUSTOM_TITLE)) != 0 &&
                (newFeatures & ~CUSTOM_TITLE_COMPATIBLE_FEATURES) != 0) {
            // Another feature is enabled and the user is trying to enable the custom title feature
            // or custom title feature is enabled and the user is trying to enable another feature
            throw new AndroidRuntimeException(
                    "You cannot combine custom titles with other title features");
        }
        if ((features & (1 << FEATURE_NO_TITLE)) != 0 && featureId == FEATURE_ACTION_BAR) {
            return false; // Ignore. No title dominates.
        }
        if ((features & (1 << FEATURE_ACTION_BAR)) != 0 && featureId == FEATURE_NO_TITLE) {
            // Remove the action bar feature if we have no title. No title dominates.
            removeFeature(FEATURE_ACTION_BAR);
        }
 
        if (featureId == FEATURE_INDETERMINATE_PROGRESS &&
                getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
            throw new AndroidRuntimeException("You cannot use indeterminate progress on a watch.");
        }
        return super.requestFeature(featureId);
    }

I saw the source of the error. It was actually the mContentParentExplicitlySet flag. It was set to true after setContentView was executed, so the requestWindowFeature(Window.FEATURE_NO_TITLE); method must be called before setContentView, otherwise an exception will be thrown. Finally, analyze it from a design perspective. , why should we design such a flag, or why must we execute requestFeature before setContentView, because in setContentView we need to select a layout file through the set requestWindowFeature flags and then add it to DecorView. If it is set after setContentView, it will start. It didn't work, so I came up with this design.

3.AppCompatActivity的setContentView

(1).AppCompatActivity’s setContentView source code

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

(1.1).getDelegate()

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

In fact, the implementation class is AppCompatDelegate, and the setContentView of getDelegate().setContentView(layoutResID); is actually the setContentView method of AppCompatDelegate.

(2).setContentView method of AppCompatDelegate

    @Override
    public void setContentView(int resId) {
        ensureSubDecor(); //⭐重点主线
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

(2.1).Analysis of ensureSubDecor() method

    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)) {
                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;//⭐这个flag参数
 
            // 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);
            }
        }
    }

Main first sentence:

mSubDecor = createSubDecor();

mSubDecor is an object of ViewGroup type. Below we analyze createSubDecor()

(2.1.1) createSubDecor() method in .ensureSubDecor() method

    private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        
        //⭐这个错误是不是曾经见到过,如果用的Theme不是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.");
        }
......
 
        // 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) {
......
        } else {
......
           subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
......
        }
......
 
        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);
......
 
        return subDecor;
    }

What is shown above is basically very important. Let’s analyze it sentence by sentence:

(2.1.1.1).ensureWindow();

    private void ensureWindow() {
        // We lazily fetch the Window for Activities, to allow DayNight to apply in
        // attachBaseContext
        if (mWindow == null && mHost instanceof Activity) {
            attachToWindow(((Activity) mHost).getWindow());
        }
        if (mWindow == null) {
            throw new IllegalStateException("We have not been given a Window");
        }
    }

First of all, we need to make it clear that AppCompatActivity inherits from Activity, so the window is also created in the attach method. In AppCompatDelegateImpl, a Window type variable is also maintained, mWindow, which is assigned after checking through the ensureWindow method. To put it bluntly, the ensureWindow method assigns the Window object in AppCompatActivity to the AppCompatDelegateImpl object. Of course, the callBack set for the window is also replaced by the one in AppCompatDelegateImpl.

(2.1.1.2).mWindow.getDecorView() method
Window’s implementation class, so we have to look at the getDecorView() method of PhoneWindow:

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

You can see that installDecor is called, which has the same parts as Activity. Let's briefly recall what installDecor does. First, we create DecorView, and then select the appropriate system built-in layout file by parsing our style settings and add it. to DecorView, and returned a FrameLayout with the id com.android.internal.R.id.content (in the future we will add our activity_mai's layout file to this content).

(2.1.1.3). The bunch of code to be analyzed later is the main difference between Activity and AppCompatActivity’s setContentView

....
 subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
....
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);
 
......
        }
 
        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);
......
 
        return subDecor;
    }

Select a layout file through the style we set. This step seems to have been done in installDecor. It seems to be repeated. Why is there such a repetition? There is such a repetition in order not to affect the original code and at the same time, part of the The logic of style processing is transferred to AppCompatDelegateImpl, such as hiding and displaying windowTitle. Here you can see the designer's design. You can slowly understand it through the following study. First, take a look at this layout file:

<androidx.appcompat.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">
 
    <androidx.appcompat.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" />
 
</androidx.appcompat.widget.FitWindowsLinearLayout>

Layout file of abc_screen_content_include:

<merge xmlns:android="http://schemas.android.com/apk/res/android">
 
    <androidx.appcompat.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>

Inflate this layout file into a view and assign it to subDecor. subDecor is a ViewGroup:

Here’s the key point:

final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
        R.id.action_bar_activity_content);

This sentence is to obtain the ContentFrameLayout type View whose id is action_bar_activity_content in subDecor and assign it to contentView.

final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);

windowContentView is to get the FrameLayout whose id is android.R.id.content of the layout selected in installDecor.

while (windowContentView.getChildCount() > 0) {
    final View child = windowContentView.getChildAt(0);
    windowContentView.removeViewAt(0);
    contentView.addView(child);
}

If there are sub-Views in windowContentView, transfer them all to contentView.

windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);

Then set the id of windowContentView to NO_ID and the id of contentView to android.R.id.content. This is to replace the content of the layout file in the previous installDecor method with the content of the system's own layout file selected in AppCompatDelegateImple. From now on, the layout file of our activity_main will be loaded on the replaced content.

mWindow.setContentView(subDecor);

This sentence calls the setContentView method of phoneWindow

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
 
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params); //⭐把contentView添加到mContentParent上
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

Let’s take a look at what mContentParent is:

mContentParent = generateLayout(mDecor);

It is the previous content where we canceled the ID. That is to say, add the system's built-in layout file (subDecor) selected by AppCompatDelegateImpl to the previous content, and finally return subDecor. Note that this subDecor is inflated from the system's built-in layout. Then Let’s deepen our understanding through a picture:

(2.1.2) A boolean value mSubDecorInstalled in the .ensureSubDecor() method.
This mSubDecorInstalled is the same as mContentParentExplicitlySet in Activity. Its function is to prevent calling after setContentView.

supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

, we found that it is different from Activity. Let’s think about it, why call supportRequestWindowFeature instead of requestWindowFeature?

Activity handles these styles in PhoneWIndow's installDecor, and AppCompatActivity handles some styles in AppCompatDelegateImpl. requestWindowFeature is the affected installDecor, supportRequestWindowFeature is the affected AppCompatDelegateImpl, take the title of the window as an example, look at the two pictures shown above. Figure, the title of Activity is displayed and hidden in the installDecor method, and the title of AppCompatActivity is displayed and hidden in AppCompatDelegateImpl. When we use AppCompatActivity, we must perform some operations in AppCompatDelegateImpl instead of in installDecor. Perform operations.

Let’s take a look at the supportRequestWindowFeature calling logic:

    public boolean supportRequestWindowFeature(int featureId) {
        return getDelegate().requestWindowFeature(featureId);
    }
 
    @Override
    public boolean requestWindowFeature(int featureId) {
        featureId = sanitizeWindowFeatureId(featureId);
 
        if (mWindowNoTitle && featureId == FEATURE_SUPPORT_ACTION_BAR) {
            return false; // Ignore. No title dominates.
        }
        if (mHasActionBar && featureId == Window.FEATURE_NO_TITLE) {
            // Remove the action bar feature if we have no title. No title dominates.
            mHasActionBar = false;
        }
 
        switch (featureId) {
            case FEATURE_SUPPORT_ACTION_BAR:
                throwFeatureRequestIfSubDecorInstalled();
                mHasActionBar = true;
                return true;
            case FEATURE_SUPPORT_ACTION_BAR_OVERLAY:
                throwFeatureRequestIfSubDecorInstalled();
                mOverlayActionBar = true;
                return true;
            case FEATURE_ACTION_MODE_OVERLAY:
                throwFeatureRequestIfSubDecorInstalled();
                mOverlayActionMode = true;
                return true;
            case Window.FEATURE_PROGRESS:
                throwFeatureRequestIfSubDecorInstalled();
                mFeatureProgress = true;
                return true;
            case Window.FEATURE_INDETERMINATE_PROGRESS:
                throwFeatureRequestIfSubDecorInstalled();
                mFeatureIndeterminateProgress = true;
                return true;
            case Window.FEATURE_NO_TITLE:
                throwFeatureRequestIfSubDecorInstalled();
                mWindowNoTitle = true;
                return true;
        }
 
        return mWindow.requestFeature(featureId);
    }
 
    //⭐mSubDecorInstalled 这参数眼熟吧
    private void throwFeatureRequestIfSubDecorInstalled() {
        if (mSubDecorInstalled) {
            throw new AndroidRuntimeException(
                    "Window feature must be requested before adding content");
        }
    }

(2.2).SetContentView(View v) in AppCompatDelegateImpl and let’s talk about the rest

ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content); //⭐重点主线
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//⭐重点主线

Get the contentview, pay attention to the difference between this contentView and the Activity's contentView. Look at the picture above, clear all the views, and then load our own layout such as the layout of our activity_main.

3. Analysis LayoutInflater.from(mContext).inflate(resId, contentParent); method

(1). A very classic interview question, what are the functions of the three parameters of inflate?
Or what are the differences between these three ways of writing:

Let’s look at the source code first, and then summarize:

    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                  + Integer.toHexString(resource) + ")");
        }
 
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource); //⭐把布局文件用xml解析器解析 
        try {
            return inflate(parser, root, attachToRoot); //调用inflate重载的方法
        } finally {
            parser.close();
        }
    }

Continuing to look at the overloaded method of inflate, this method contains the answer to this interview question:

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
 
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root; //⭐重点
 
            try {
                advanceToRootNode(parser); //⭐确保下面的代码是首先解析的根布局标签
                final String name = parser.getName();
 
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
 
                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }
 
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else { //⭐重点 else单独分析
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs); 
 
                    ViewGroup.LayoutParams params = null;
 
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
 
                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
 
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
 
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
 
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
 
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
 
            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(
                        getParserStateDescription(inflaterContext, attrs)
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
 
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
 
            return result;
        }
    }

There is too much code above. I will take out the main code logic for this question:

(1.1).advanceToRootNode() This method is to ensure that the first one parsed is the root tag of the layout.

    private void advanceToRootNode(XmlPullParser parser)
        throws InflateException, IOException, XmlPullParserException {
        // Look for the root node.
        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG &&
            type != XmlPullParser.END_DOCUMENT) {
            // Empty
        }
 
        if (type != XmlPullParser.START_TAG) {
            throw new InflateException(parser.getPositionDescription()
                + ": No start tag found!");
        }
    }

(1.2) Separate logical analysis of .else

else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
 
                    ViewGroup.LayoutParams params = null;
 
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            // Set the layout params for temp if we are not
                            // attaching. (If we are, we use addView, below)
                            temp.setLayoutParams(params);
                        }
                    }
 
                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
 
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);
 
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
 
                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
 
                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

The following is a sentence-by-sentence analysis:

(1.2.1)

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

This sentence is to create a view through name. The following will analyze in detail how to create the view. This temp is the root view of the layout file.

(1.2.2)

if (root != null) {
    if (DEBUG) {
        System.out.println("Creating params from root: " +
                root);
    }
    // Create layout params that match root, if supplied
    params = root.generateLayoutParams(attrs);
    if (!attachToRoot) {
        // Set the layout params for temp if we are not
        // attaching. (If we are, we use addView, below)
        temp.setLayoutParams(params);
    }
}

This is if the second parameter root of the inflate method is not null, then this code is executed:

params = root.generateLayoutParams(attrs);

What this code means is: we pass the root layout parameters of the new view to root. Let the root be converted into layout parameters suitable for its own layout (because different layouts have different characteristics, such as LinearLayout and FrameLayout, we need to put the root temp View of the layout file into the root, and we must make temp the original layout parameters Convert to parameters suitable for the layout of the root ViewGroup). If the third parameter of inflate, attachToRoot, is false, set the root view of the layout file to the converted params.

(1.2.3)

rInflateChildren(parser, temp, attrs, true);

After that, we will continue to parse the layout file and create the View through reflection. We will analyze it in detail later on how to parse and create it.

(1.2.4)

if (root != null && attachToRoot) {
    root.addView(temp, params);
}

If root is not null and attachToRoot is true, add the temp view generated by the root of the layout file to root. Note that this params is still the one converted above.

(1.2.5)

if (root == null || !attachToRoot) {
    result = temp;
}

If root is null or attachToRoot is false, the final return value of the method is the View generated by this layout file.

(1.2.6)

Here we know:

1. When root is not null and attachTORoot is true, directly add the View generated by our layout to root (this root is the second parameter of the inflate method parameter), and the final return result of the method is root. If attachTORoot is false and directly returns the View generated by our layout file. Note that the layoutParams of this generated View have been set, so you can say:

LayoutInflater.from(this).inflate(R.layout.activity_main,root,true);

Equivalent to

View view = LayoutInflater.from(this).inflate(R.layout.activity_main,root,false);
root.addView(view);

2. When root is null, directly return the view generated by our layout file. Note that this generated View does not have layoutParams.

, and once root is null, the subsequent attachToRoot parameter will be invalid.

(2). Analyze how the inflate method parses and creates View
(2.1). Pay attention to the merge tag when inflating the root tag of the layout file

if (TAG_MERGE.equals(name)) {
    if (root == null || !attachToRoot) {
        throw new InflateException("<merge /> can be used only with a valid "
                + "ViewGroup root and attachToRoot=true");
    }

    rInflate(parser, root, inflaterContext, attrs, false);
}

When the root tag of a layout file is merged, an error will be reported if root is null or attachToRoot is false, indicating that when the layout file and tag are merged, a View cannot be directly generated and must be attached to other Views.

(2.2).Resolve layouts that are not root tags

    void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
 
        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;
 
        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
 
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
 
            final String name = parser.getName();
 
            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }
 
        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }
 
        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

(2.2.1). When parsing is not the root layout, pay attention to the processing of merge and include tags.

When dealing with the merge tag, an error will be reported directly, because the merge tag can only be used for the root tag. For the processing of the include tag, if it is judged that the include is a tag, an error will be reported, because the include tag cannot be used as the root tag.

(2.2.2). Other view tags will create views, then generate corresponding layout parameters LayoutParams through the parent layout, and then add them to the sub-layout.

else {
    final View view = createViewFromTag(parent, name, context, attrs);
    final ViewGroup viewGroup = (ViewGroup) parent;
    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
    rInflateChildren(parser, view, attrs, true);
    viewGroup.addView(view, params);
}

In this paragraph, the createViewFromTag method is the most important, followed by the logic of generating LayoutParams and adding it to the parent layout. Finally, we analyze the createViewFromTag method.

(2.2.3).createViewFromTag creates a view. Note that there is a difference between this Activity and AppCompatActivity.

a. First analyze the process of creating view by Activity's createViewFromTag

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
 
    @UnsupportedAppUsage
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
 
        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
 
        try {
            View view = tryCreateView(parent, name, context, attrs);
 
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) { //⭐重点
                        view = onCreateView(context, parent, name, attrs); //⭐重点
                    } else {
                        view = createView(context, name, null, attrs); //⭐重点
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
 
            return view;
        } catch (InflateException e) {
            throw e;
 
        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
 
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }

Key points of our analysis:

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = context;
    try {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(context, parent, name, attrs);
        } else {
            view = createView(context, name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

return view;

if (-1 == name.indexOf('.')) This judgment is whether the label is a custom View. The one with "." is a custom View. If it is not a custom View, create the View through onCreateView. If it is a custom View View, create View through createView

So let’s compare why the custom View is not created through onCreateView but the custom VIew is created with createView.

Let me talk about a knowledge point first. The implementation class of LayoutInflater is PhoneLayoutInflater, and the onCreateView method has also been rewritten:

    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };   
 
 @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }
 
        return super.onCreateView(name, attrs);
    }
 
最后还调用了super:
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

You can see that onCreateView finally calls createVIew, so the main difference between onCreateView and createView is the prefix. It is not necessary for custom View to have a prefix. Think about the full class name of LinearLayout is "android.widget.LinearLayout", this I know that if these SDKs do not come with custom View tags, they will complete the full class name here. Finally, take a look at createView.

(2.2.4).createView method

 static final Class<?>[] mConstructorSignature = new Class[] {
            Context.class, AttributeSet.class}; //View的两个参数
   
 public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Objects.requireNonNull(viewContext);
        Objects.requireNonNull(name);
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;
 
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
 
            if (constructor == null) {//⭐主要就是通过反射去创建view
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
 
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
                constructor = clazz.getConstructor(mConstructorSignature);//通过反射获取两个参数的构造方法,两个参数分别是context与AttributeSet(XML的参数集合)
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor); //用map缓存起来
            } else { //constructor不为null说明map里面有直接用map里面的
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                                mContext.getClassLoader()).asSubclass(View.class);
 
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, viewContext, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, viewContext, attrs);
                    }
                }
            }
 
            Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = viewContext;
            Object[] args = mConstructorArgs;
            args[1] = attrs;
 
            try {
                final View view = constructor.newInstance(args);
                if (view instanceof ViewStub) {
                    // Use the same context when inflating ViewStub later.
                    final ViewStub viewStub = (ViewStub) view;
                    viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
                }
                return view;
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        } catch (NoSuchMethodException e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(viewContext, attrs)
                    + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
 
        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            final InflateException ie = new InflateException(
                    getParserStateDescription(viewContext, attrs)
                    + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(viewContext, attrs) + ": Error inflating class "
                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

Such a lot of code actually creates View through reflection. Pay attention to the comments inside.

b. Analyze the process of creating view by createViewFromTag of AppCompatActivity

    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }
 
    @UnsupportedAppUsage
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
 
        // Apply a theme wrapper, if allowed and one is specified.
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }
 
        try {
            View view = tryCreateView(parent, name, context, attrs);//⭐重点
 
            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) { 
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs); 
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
 
            return view;
        } catch (InflateException e) {
            throw e;
 
        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
 
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    getParserStateDescription(context, attrs)
                    + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }

If this tryCreatView method fails to try to create a View, it will be the Activity's turn to create it. Let's take a look at the tryCreateView method:

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
 
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
 
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
 
        return view;
    }

I found that there are several variables I don’t recognize, mFactory2, mFactory and mPrivateFactory. Our AppCompatActivity uses mFactory2. Look at the onCreate method of AppCompatActivity:

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory(); // ⭐
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

We continue to follow this method:

    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else {
            if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
                Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                        + " so we can not install AppCompat's");
            }
        }
    }
 
//Factory2是一个接口
    public interface Factory2 extends Factory {
        @Nullable
        View onCreateView(@Nullable View parent, @NonNull String name,
                @NonNull Context context, @NonNull AttributeSet attrs);
    }

//LayoutInflater实现了这个接口
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 

Now I see that the installViewFactory method is called in the oncreate method of AppCompatActivity and the layoutInflater object is obtained. AppCompatDelegateImpl implements the Factory2 interface.

LayoutInflaterCompat.setFactory2(layoutInflater, this);

 public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        inflater.setFactory2(factory); // ⭐
 
        if (Build.VERSION.SDK_INT < 21) {
            final LayoutInflater.Factory f = inflater.getFactory();
            if (f instanceof LayoutInflater.Factory2) {
                // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
                // We will now try and force set the merged factory to mFactory2
                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
            } else {
                // Else, we will force set the original wrapped Factory2
                forceSetFactory2(inflater, factory);
            }
        }
    }
 
 
//LayoutInflater类中的方法
    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }

You can see that Factory2 is passed into infalter, so Factory2 in the Inflater object is not null.

Factory2 is no longer null. When executing the tryCreateView that creates the createViewFromTag method:

    public final View tryCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }
 
        View view;
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }
 
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
 
        return view;
    }

If mFactory2 is not null, view = mFactory2.onCreateView(parent, name, context, attrs); will be executed.

We know that this mFactory2 is actually an instance object of AppCompatDelegateImpl. This design is quite clever and similar to the ensureDecor above. It adds some logic to transfer the view creation logic to AppCompatDelegateImpl, so let's look at the onCreateView method of AppCompatDelegateImpl:

    @Override
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        return createView(parent, name, context, attrs);
    }
 
    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
            if ((viewInflaterClassName == null)
                    || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
                // Either default class name or set explicitly to null. In both cases
                // create the base inflater (no reflection)
                mAppCompatViewInflater = new AppCompatViewInflater();
            } else {
                try {
                    Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();
                } catch (Throwable t) {
                    Log.i(TAG, "Failed to instantiate custom view inflater "
                            + viewInflaterClassName + ". Falling back to default.", t);
                    mAppCompatViewInflater = new AppCompatViewInflater();
                }
            }
        }
 
        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = (attrs instanceof XmlPullParser)
                    // If we have a XmlPullParser, we can detect where we are in the layout
                    ? ((XmlPullParser) attrs).getDepth() > 1
                    // Otherwise we have to use the old heuristic
                    : shouldInheritContext((ViewParent) parent);
        }
        //⭐
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

You can see that a mAppCompatViewInflater is created, and finally the createView method of mAppCompatViewInflater is called:

    final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;
 
        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }
 
        View view = null;
 
        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }
 
        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }
 
        if (view != null) {
            // If we have created a view, check its android:onClick
            checkOnClickListener(view, attrs);
        }
 
        return view;
    }

You can see that there is a switch. If the View is one of the cases, then pass in the layout parameters to recreate an AppCompat View. If it is not one of the cases, enter default. The createView method directly returns null.

protected View createView(Context context, String name, AttributeSet attrs) { return null; } Then do the following sentence:


if (view == null && originalContext != context) {
    // If the original context does not equal our themed context, then we need to manually
    // inflate it using the name so that android:theme takes effect.
    view = createViewFromTag(context, name, attrs);
}
    private View createViewFromTag(Context context, String name, AttributeSet attrs) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
 
        try {
            mConstructorArgs[0] = context;
            mConstructorArgs[1] = attrs;
 
            if (-1 == name.indexOf('.')) {
                for (int i = 0; i < sClassPrefixList.length; i++) {
                    final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
                    if (view != null) {
                        return view;
                    }
                }
                return null;
            } else {
                return createViewByPrefix(context, name, null);
            }
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        } finally {
            // Don't retain references on context.
            mConstructorArgs[0] = null;
            mConstructorArgs[1] = null;
        }
    }
 
    private View createViewByPrefix(Context context, String name, String prefix)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
 
        try {
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                Class<? extends View> clazz = Class.forName(
                        prefix != null ? (prefix + name) : name,
                        false,
                        context.getClassLoader()).asSubclass(View.class);
 
                constructor = clazz.getConstructor(sConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            constructor.setAccessible(true);
            return constructor.newInstance(mConstructorArgs);
        } catch (Exception e) {
            // We do not want to catch these, lets return null and let the actual LayoutInflater
            // try
            return null;
        }
    }

This method is consistent with the logic of Activity

To summarize: When AppCompatActivity parses and creates a View through LayoutInfater, it will intercept the logic of the original Activity through setFactory2 to execute the logic of AppCompatDelegateImpl. When the View is parsed and created, it will be checked first. If it is a newly designed View by AppCompat, it is in the case. For those piles, convert this View into the newly designed View by AppCompat. If not, follow the previous logic.

3. Summary

1. Summary of setContentView

In general, setContentView is to create a DecorView. DecorView is a FrameLayout, and then select the layout file that comes with the system according to the style. (For example, if there is a title, let me say that the root layout of this layout file is linearLayout. If there is a title, there is a viewStub. And two FrameLayouts: title and content (if there is no title, it is a viewstub and a content FrameLayout), add them to the DecorView, and finally add our own activity_mian layout to the content FrameLyout.

Note that there are some differences between Activity and AppCompatActivity, but the overall logic remains the same.

2. Summary of inflate

It summarizes the role of the three parameters of inflate, the points to note when parsing include, merge and other tags, and the important processes of how to parse xml files and how to create Views.

Guess you like

Origin blog.csdn.net/qq_32907491/article/details/132526072