setContentView, a familiar and unfamiliar setContentView system

Posted by sharapov on Thu, 10 Feb 2022 13:28:24 +0100

Why is setContentView familiar? Because this method is a method we have been in contact with since we started Android development. It is found in every Activity we have written. But why is setContentView strange? Because we only know how to use it in our daily development, and we don't deeply analyze how this method turns the layout resource id we passed in into a visual interface. Today, let's uncover this mysterious veil and pick up the principle (Note: the source code is based on Android 12).

Because the activities we develop everyday will eventually inherit from Android app. Activity, so first look at the setContentView method of activity class:

android.app.Activity

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

You can see that the setContentView method of getWindow() is actually called in note 1. Let's see what getWindow() does.

android.app.Activity

    public Window getWindow() {
        return mWindow;
    }

An mWindow is returned. What is mWindow? Look at its declaration Code:

android.app.Activity

    @UnsupportedAppUsage
    private Window mWindow;

mWindow is a Window class. To view the source code, mWindow is assigned in the attach method of Activity:

android.app.Activity

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        ...

After seeing this, we know that the setContentView of Activity finally calls the setContentView method of PhoneWindow. Let's continue to look at the setContentView method of PhoneWindow:

com.android.internal.policy.PhoneWindow

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();//1
        } 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;
    }

See the installDecor method at note 1:

com.android.internal.policy.PhoneWindow

private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);//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);//2
            ...

Look at the generateDecor method at note 1:

com.android.internal.policy.PhoneWindow

    protected DecorView generateDecor(int featureId) {
        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());
    }

generateDecor finally creates a new DecorView object and returns it and assigns it to the mDecor reference in the installDecor method.

Return to the installDecor method and continue to look at the generateLayout method in Note 2:

com.android.internal.policy.PhoneWindow

protected ViewGroup generateLayout(DecorView decor) {
    ...
    int layoutResource;
    ...
    layoutResource = R.layout.screen_title;
    ...
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;

Because the code of generateLayout is very long, only the key code is posted here. The main work of generateLayout is to integrate r.layout in the system screen_ Title Layout resources are added to DecorView, and findViewById(ID_ANDROID_CONTENT) is invoked in PhoneWindow to find R.layout.. screen_ id in title is id_ ANDROID_ FrameLayout of content and return it to the mContentParent reference in the installDecor method.
R.layout. screen_ The title code is as follows:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <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:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

Note that the id of the lowest FrameLayout is: content, and then look at the id in the source code_ ANDROID_ Value of content:

    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

Finally, go back to the setContentView method of PhoneWindow:

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();//1
        } 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);//2
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

The code in note 1 has been finished before, and then look at the code in Note 2. This code loads the layout resource id passed in by setContentView into mContentParent, which is the FrameLayout with id of content.

So far, the structure of an Activity is also clear, as shown in the following figure:

Activity contains a PhoneWindow, PhoneWindow contains a DecorView, and DecorView contains the system layout resource r.layout screen_ title. setContentView is to load the layout resource file passed in the activity into the FrameLayout with id content.

To learn more, please follow my personal blog: droidYu

Topics: Android