Android development students are certainly familiar with setContentView, every Activity, can not do without this function, today we will take a look at its internal implementation!

Note: This article is based on Android 8.1.0.

Compatactivity can be distinguished from AppCompatActivity

A Project built on older Versions of the Android SDK inherits activities by default, whereas after 5.0 it inherits AppCompatActivity by default. The difference between the two can be seen in the comments on AppCompatActivity.


/**
 * Base class for activities that use the
 * <a href="{@docRoot}tools/extras/support-library.html">support library</a> action bar features.
 *
 * <p>You can add an {@link android.support.v7.app.ActionBar} to your activity when running on API level 7 or higher
 * by extending this class for your activity and setting the activity theme to
 * {@link android.support.v7.appcompat.R.style#Theme_AppCompat Theme.AppCompat} or a similar theme.
 *
 * <div class="special reference">
 * <h3>Developer Guides</h3>
 *
 * <p>For information about how to use the action bar, including how to add action items, navigation
 * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
 * Bar</a> API guide.</p>
 * </div>
 */
 
Copy the code

AppCompatActivity is a parent class of any Activity that uses the ActionBar feature in the Support package.

The relationship can be described as AppCompatActivity————>FragmentActivity————>Activity.

2, the setContentView

The setContentView in AppCompatActivity is also very concise and you can see that you need to go to the proxy class to continue looking at the code.


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

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        returnmDelegate; Private static AppCompatDelegate create(Context Context, Window Window, appcallback 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 {
            returnnew AppCompatDelegateImplV9(context, window, callback); }}Copy the code

The proxy class implements setContentView on AppCompatDelegateImplV9:


    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }
    
Copy the code

3, createSubDecor

The first step in setContentView is to ensure that SubDecor is installed, as noted in the source code below

// SubDecor is a private ViewGroupcreateSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        if(! a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) { a.recycle(); This Exception will be reported if you use AppCompatActivity but don't set a Theme.AppCompat Theme. throw new IllegalStateException("You need to use a Theme.AppCompat theme (or descendant) with this activity."); } // This brings us to setting some Window properties, as we'll see belowif (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) { requestWindowFeature(Window.FEATURE_NO_TITLE); // Set no title}else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title. requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR); } if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) { requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY); } if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) { requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY); } mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false); a.recycle(); // Now let's make sure that the Window has installed its decor by retrieving it mWindow.getDecorView(); DecorView final LayoutInflater inflater = LayoutInflater. From (mContext); ViewGroup subDecor = null; // Determine which layout is inflate according to the tagif(! 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) { ...... } } else { } if (subDecor == null) { throw new IllegalArgumentException( "AppCompat does not support the current theme features: { " + "windowActionBar: " + mHasActionBar + ", windowActionBarOverlay: "+ mOverlayActionBar + ", android:windowIsFloating: " + mIsFloating + ", windowActionModeOverlay: " + mOverlayActionMode + ", windowNoTitle: " + mWindowNoTitle + " }"); } if (mDecorContentParent == null) { mTitleView = (TextView) subDecor.findViewById(R.id.title); } // Make the decor optionally fit system windows, like the window's decor ViewUtils.makeOptionalFitsSystemWindows(subDecor); final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById( R.id.action_bar_activity_content); // Get the PhoneWindow's content layout object 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); // Set the id of the contentView to Android.r.d.c. ontent ContentView.setid (Android.r.D.C. ontent); // The decorContent may have a foreground drawable set (windowContentOverlay). // Remove this as we handle it ourselves if (windowContentView instanceof FrameLayout) { ((FrameLayout) windowContentView).setForeground(null); } } // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() { dismissPopups(); }});return subDecor;
    }
    
Copy the code

3.1 requestWindowFeature


    @Override
    public boolean requestWindowFeature(int featureId) {
        ......
        switch (featureId) {
            case FEATURE_SUPPORT_ACTION_BAR:
                throwFeatureRequestIfSubDecorInstalled();
                mHasActionBar = true; // Just assign to the variablereturn true; . }returnmWindow.requestFeature(featureId); } // This explains another reason if we are insetSetting the requestWindowFeature again after the ContentView throws an Exception. private voidthrowFeatureRequestIfSubDecorInstalled() {
        if (mSubDecorInstalled) {
            throw new AndroidRuntimeException(
                    "Window feature must be requested before adding content"); }}Copy the code

3.2 mWindow. GetDecorView ()

As you probably know, the Windows implementation subclass is PhoneWindow, so let’s look at getDecorView directly from PhoneWindow. And you’ll end up here. Notice the next two highlights


    private void installDecor() {
        mForceDecorInstall = false;
        if(mDecor == null) { mDecor = generateDecor(-1); / / key 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); / / the key... } // generateDecor finally just new a DecorView protected DecorView generateDecor(int featureId) {......returnnew DecorView(context, featureId, this, getAttributes()); /** @hide */ public class implements FrameLayout RootViewSurfaceTaker, WindowCallbacks { }Copy the code

GenerateLayout too many functions, not to post the code here, value only analysis of the process:

  1. Set some Window properties;
  2. Select a layoutResource based on the Window property. These layoutResources have a common layout of @Android: ID /content. Because this content is used in the createSubDecor function of AppCompatDelegateImplV9;
  3. Selecting layoutResource leads to a key line of code: mDecor. OnResourcesLoaded (mLayoutInflater, layoutResource); The layoutResource is inflate out and added to the DecorView. Note that LayoutParams used when adding views is MATCH_PARENT;
    
    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); / / inflate the Viewif(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

3.3 Back to createSubDecor

This is the time to create the actual subDecor, which also has four optional layouts that you can select based on the properties you set earlier and then de-inflate out.

Final ContentFrameLayout contentView = (ContentFrameLayout) SubDecor. FindViewById ( R.id.action_bar_activity_content); // The content in this case is the content in PhoneWindow, Final ViewGroup windowContentView = (ViewGroup) mwindow.findViewById (Android.r.icontent);if(windowContentView ! = null) { // There might be Views already added to the Window'Content view so we need to // Migrate them to our content view // Merge PhoneWindow view to SubDecor content 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.i.C.ontent 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

3.4 mWindow. The setContentView

Start setting up the PhoneWindow contentView and then cut the code into the PhoneWindow


    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set inthe process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. // Does mContentParent look familiar? This is the return value of the generateLayout functionif (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); // Use Scene}else{// note that mContentParent was @android:id/content, now view.no_id; // Now @android:id/content is action_bar_activity_content mContentParent. AddView (view, params); } mContentParent.requestApplyInsets(); final Callback cb = getCallback();if(cb ! = null && ! isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet =true;
    }

Copy the code

Note: At this point, SubDecor has been added to the PhoneWindow, and @Android: ID/Content is action_bar_activity_content in SubDecor. The next step is to set up the details.

4. Go back to setContentView


    @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

At this point we can see that the most complex code in the setContentView is ensureSubDecor, and the rest of the code just uses the content in the SubDecor, takes the Layout inflate that we passed in and adds it.

5, summary

The process of setContentView is to create a DecorView through the PhoneWindow, then create a SubDecor, and finally add the layout passed in.

This makes it easier to see why there are more levels and numbers of layouts in performance analysis tools than in our own layouts, and it is easier to see why the function that sets the View for the Activity is called setContentView.

AD time

Toutiao Android client team recruitment is in hot progress, all levels and fresh interns are needed, business growth is fast, daily work is high, the challenge is big, the treatment is powerful, you don’t miss it!

Bachelor degree or above, infrequent job-hopping (such as two jumps in two years), welcome to add my wechat details: KOBE8242011