Android's setContentView loading view process

The setContentView in AppCompatActivity will call the setContentView method of the AppCompatDelegate subclass AppCompatDelegateImpl to complete the logic of loading the view. The source code of the setContentView method of the AppCompatDelegateImpl class:

AppCompatDelegateImpl类的setContentView(View v) 方法源码:
    @Override
    public void setContentView(View v) {
        ensureSubDecor(); //会调用createSubDecor()来生成ViewGroup类型的属性mSubDecor
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

This setContentView method loads the layout mainly in two steps:

  • Create an mSubDecor instance, and call the PhoneWindow's setContentView(subDecor) method to complete the loading of the layout view. The ensureSubDecor() method in the method will call the createSubDecor() method to create an mSubDecor instance (declared as a ViewGroup type, and the actual loaded layout file instance is one of FitWindowsLinearLayout, FitWindowsFrameLayout, ActionBarOverlayLayout), this mSubDecor layout generally contains two section, a header section, and a content layout with an id of content (usually a FrameLayout type);
  • Add our layout to the content layout in the mSubDecor layout through the addView(v) ​​method.

Simplified analysis entry: https://blog.csdn.net/hnjcxy/article/details/124127855 icon-default.png?t=M276https://blog.csdn.net/hnjcxy/article/details/124127855

Next, analyze the layout loading process of the first step in detail, the simplified source code of the createSubDecor() method of the AppCompatDelegateImpl class:

AppCompatDelegateImpl类的createSubDecor()方法简化后的源码:
    private ViewGroup createSubDecor() {
        //ensureWindow方法会获取Activity类中的属性mWindow(声明为Window类型,实例是new PhoneWindow(this, window, activityConfigCallback)实例对象,并赋值给本类AppCompatDelegateImpl的属性mWindow(Window类型),后面加载view需要用到mWindow。
        // Now let's make sure that the Window has installed its decor by retrieving it
        ensureWindow(); 
        //这个方法里面会调PhoneWindow类的installDecor()方法生成PhoneWindow类的属性mDecor实例(DecorView类型,就是一个FrameLayout子类);在PhoneWindow类的installDecor()方法中还会给属性mContentParent初始化。
        mWindow.getDecorView();
        
        //下面根据不同情况加载四种布局文件中的一种
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                //布局一,根布局:FitWindowsLinearLayout,包含:一个内容容器;一个id为title的TextView
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
            } else if (mHasActionBar) {
                //布局二,根布局:ActionBarOverlayLayout,包含:一个内容容器;一个action_bar模块(另外三个布局都提供了title,这个布局提供的是actionbar)
                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());
            }
        } else {
            if (mOverlayActionMode) {
                //布局三,根布局:FitWindowsFrameLayout,包含:一个内容容器;在引入的布局文件abc_action_mode_bar.xml中的ActionBarContextView内提供了垂直布局的两个title
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                //布局四,根布局:FitWindowsLinearLayout,包含:一个内容容器;与第三个布局类似,引入的ActionBarContextView内提供了垂直布局的两个title
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }
        
        //获取subDecor内的内容容器布局(例如布局一中abc_dialog_title_material.xml中就是一个包含一个id为title的TextView和一个id为action_bar_activity_content的ContentFrameLayout)
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //获取DecorView内的内容容器布局
        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);
            }
        }
        
        //在这里把设置布局的处理交给PhoneWindow,并且把上面四种布局之一的subDecor布局实例传进去了,所以最终布局操作其实是PhoneWindow完成的
        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        return subDecor;
    }

The createSubDecor() method description of the AppCompatDelegateImpl class:

  1. Process the mWindow object of the AppCompatDelegateImpl class (not created, because it is an instance obtained from Activity). The ensureWindow method will use the attachToWindow method to obtain the mWindow attribute instance in the Activity class (declared as the Window type, the instance is created in the Activity's attach method  new PhoneWindow(this, window, activityConfigCallback)  , where the window parameter is null, you can view ActivityThread calls the activity.attach method in the performLaunchActivity method, and assigns it to the attribute mWindow (Window type) of this class AppCompatDelegateImpl, which is convenient for using mWindow in the AppCompatDelegateImpl class later.
  2. Create mDecor instance (DecorView type, root layout of PhoneWindow) and mContentParent (declared as ViewGroup, actual FrameLayout instance) through mWindow.getDecorView() method. The getDecorView() method will call the installDecor() method of the PhoneWindow class. The installDecor() method mainly does two things; first, call the generateDecor method to create an instance of the mDecor attribute of the PhoneWindow class (DecorView type, that is, FrameLayout, because DecorView inherits From FrameLayout); Second, adjust the generateLayout method, first select different layout resource file ids according to different situations (the root nodes of these layout files are all LinearLayout), and then adjust the mDecor.onResourcesLoaded method to add the layout corresponding to the selected layout file id to In the root layout of PhoneWindow, get the layout object whose id is R.id.content, and assign it to the mContentParent attribute instance of the PhoneWindow class (declared as ViewGroup, and FrameLayout in the actual layout file).
    PhoneWindow类中的getDecorView() 方法源码:
        @Override
        public final @NonNull View getDecorView() {
            if (mDecor == null || mForceDecorInstall) {
                installDecor();
            }
            return mDecor;
        }
    
        installDecor() 方法简化源码:
        private void installDecor() {
            mForceDecorInstall = false;
            if (mDecor == null) {
                //创建DecorView实例,mDecor是PhoneWindow中的根布局
                mDecor = generateDecor(-1); 
                mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                mDecor.setIsRootNamespace(true);
                if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                    mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
                }
            } else {
                mDecor.setWindow(this);
            }
            if (mContentParent == null) {
                //加载,创建mContentParent实例,内容那部分的布局
                mContentParent = generateLayout(mDecor);
            }
            //省略。。。
        }
    
        generateDecor方法简化源码:
        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();
            }
            //创建DecorView实例
            return new DecorView(context, featureId, this, getAttributes());
        }
    
        generateLayout方法简化源码:
        protected ViewGroup generateLayout(DecorView decor) {
                ...
            int layoutResource;
            //根据不同情况获取不同的布局资源文件id,下面以screen_simple布局为例并附上布局源码
            if(...){
                ...
            }else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
                layoutResource = R.layout.screen_simple_overlay_action_mode;
            } else {
            //screen_simple布局内容简单,一个LinearLayout包含一个id为R.id.action_mode_bar_stub的ActionBarContextView,和一个id为R.id.content的FrameLayout
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple; 
            }
            mDecor.startChanging();
            //根据布局资源id,把布局添加到PhoneWindow的
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //获取布局中id为content的布局实例,R.id.content,即布局中放内容的位置(不含标题栏的部分)
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            if (contentParent == null) {
                throw new RuntimeException("Window couldn't find content container view");
            }
            ...
            mDecor.finishChanging();
            //返回内容那部分布局的对象
            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);
            //获取布局资源文件id为layoutResource的布局对象
            final View root = inflater.inflate(layoutResource, null);
            if (mDecorCaptionView != null) {
                if (mDecorCaptionView.getParent() == null) {
                    //这里先把mDecorCaptionView添加到根布局,下一行再把layoutResource对应的布局root添加到mDecorCaptionView中,也就是把root添加到PhoneWindow根布局mDecor中
                    addView(mDecorCaptionView,
                            new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
                }
                mDecorCaptionView.addView(root,
                        new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
            } else {
                //把layoutResource对应的布局root添加到PhoneWindow根布局mDecor中
                // Put it below the color views.
                addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mContentRoot = (ViewGroup) root;
            initializeElevation();
        }
    
    screen_simple.xml布局源码:
    <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>
    
  3.  Load one of the four layout files according to different situations, and assign it to the local variable ViewGroup subDecor. The four layouts include: R.layout.abc_dialog_title_material, R.layout.abc_screen_toolbar, R.layout.abc_screen_simple_overlay_action_mode, R.layout.abc_screen_simple. These four layouts have one thing in common. They all introduce the abc_screen_content_include.xml layout through include, and there is only one ContentFrameLayout layout whose id is R.id.action_bar_activity_content in the imported layout file. The contents of these layouts are as follows:
    布局四:abc_screen_simple.xml
    <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>
    
    布局三:abc_screen_simple_overlay_action_mode.xml
    <androidx.appcompat.widget.FitWindowsFrameLayout
            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:fitsSystemWindows="true">
    
        <include layout="@layout/abc_screen_content_include" />
    
        <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" />
    
    </androidx.appcompat.widget.FitWindowsFrameLayout>
    
    布局二:abc_screen_toolbar.xml
    <androidx.appcompat.widget.ActionBarOverlayLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/decor_content_parent"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true">
    
        <include layout="@layout/abc_screen_content_include"/>
    
        <androidx.appcompat.widget.ActionBarContainer
                android:id="@+id/action_bar_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentTop="true"
                style="?attr/actionBarStyle"
                android:touchscreenBlocksFocus="true"
                android:gravity="top">
    
            <androidx.appcompat.widget.Toolbar
                    android:id="@+id/action_bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    app:navigationContentDescription="@string/abc_action_bar_up_description"
                    style="?attr/toolbarStyle"/>
    
            <androidx.appcompat.widget.ActionBarContextView
                    android:id="@+id/action_context_bar"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:visibility="gone"
                    android:theme="?attr/actionModeTheme"
                    style="?attr/actionModeStyle"/>
    
        </androidx.appcompat.widget.ActionBarContainer>
    
    </androidx.appcompat.widget.ActionBarOverlayLayout>
    
    布局一:abc_dialog_title_material.xml
    <androidx.appcompat.widget.FitWindowsLinearLayout
            xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_height="match_parent"
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:fitsSystemWindows="true">
    
        <TextView
                android:id="@+id/title"
                style="?android:attr/windowTitleStyle"
                android:singleLine="true"
                android:ellipsize="end"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="start"
                android:textAlignment="viewStart"
                android:paddingLeft="?attr/dialogPreferredPadding"
                android:paddingRight="?attr/dialogPreferredPadding"
                android:paddingTop="@dimen/abc_dialog_padding_top_material"/>
    
        <include
                layout="@layout/abc_screen_content_include"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"/>
    
    </androidx.appcompat.widget.FitWindowsLinearLayout>
    
    上面四个布局都包含abc_screen_content_include.xml
    <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>
  4. Get the content layout contentView from the layout subDecor, get the content layout windowContentView from the layout mDecor (PhoneWindow root layout), and add all the subviews in the windowContentView to the contentView. According to the layout subDecor object loaded above, get the layout instance (ContentFrameLayout type) whose id is R.id.action_bar_activity_content, and assign it to the local variable ContentFrameLayout contentView; then go to the PhoneWindow root layout (that is, mDecor) to find the id as R.id. The layout instance of content, and assign it to the local variable ViewGroup windowContentView (as mentioned in the second item above, this layout variable is declared as ViewGroup, and the actual layout file is FrameLayout); then if windowContentView is not empty, the subview in windowContentView will be Add them all to contentView, and remove your own subview, then set the id of windowContentView to -1, and set the id of contentView to android.R.id.content.
  5. Finally, call the mWindow.setContentView(subDecor) method to add the subDecor layout to the content layout of PhoneWindow. In the setContentView method in the PhoneWindow class, add the incoming subDecor layout object to the mContentParent attribute in the PhoneWindow class, that is, add the subDecor layout to the content layout position of the PhoneWindow root layout mDecor.

PhoneWindow类的setContentView方法源码:
    @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();
        }
        //再把传进来的subDecor布局添加到内容布局里面,如果开启场景过渡就封装场景对象
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            //将内容布局包装成场景对象,再通过TransitionManager完成场景过渡效果
            final Scene newScene = new Scene(mContentParent, view);
            //这里调TransitionManager类的transitionTo方法对内容布局做过度动画
            transitionTo(newScene);
        } else {
            //没有开启场景过渡效果,直接添加
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

Guess you like

Origin blog.csdn.net/hnjcxy/article/details/124105437