Proceso de vista de carga setContentView de Android

setContentView en AppCompatActivity llamará al método setContentView de la subclase AppCompatDelegate AppCompatDelegateImpl para completar la lógica de carga de la vista. El código fuente del método setContentView de la clase AppCompatDelegateImpl:

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

Este método setContentView carga el diseño principalmente en dos pasos:

  • Cree una instancia de mSubDecor y llame al método setContentView(subDecor) de PhoneWindow para completar la carga de la vista de diseño. El método sureSubDecor() en el método llamará al método createSubDecor() para crear una instancia de mSubDecor (declarada como un tipo ViewGroup, y la instancia del archivo de diseño cargado real es una de FitWindowsLinearLayout, FitWindowsFrameLayout, ActionBarOverlayLayout), este diseño de mSubDecor generalmente contiene dos sección, una sección de encabezado y un diseño de contenido con una identificación de contenido (generalmente un tipo FrameLayout);
  • Agregue nuestro diseño al diseño del contenido en el diseño mSubDecor mediante el método addView (v).

Entrada de análisis simplificada: https://blog.csdn.net/hnjcxy/article/details/124127855 icono-default.png?t=M276https://blog.csdn.net/hnjcxy/article/details/124127855

A continuación, analice en detalle el proceso de carga del diseño del primer paso, el código fuente simplificado del método createSubDecor() de la clase AppCompatDelegateImpl:

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

La descripción del método createSubDecor() de la clase AppCompatDelegateImpl:

  1. Procesa el objeto mWindow de la clase AppCompatDelegateImpl (no creado porque es una instancia obtenida de Activity). El método sureWindow utilizará el método adjuntoToWindow para obtener la instancia del atributo mWindow en la clase Actividad (declarada como tipo Ventana, la instancia se crea en el método adjunto de la Actividad  new PhoneWindow(this, window, ActivityConfigCallback)  , donde el parámetro de ventana es nulo , puede ver que ActivityThread llama al método Activity.attach en el método performLaunchActivity y lo asigna al atributo mWindow (tipo de ventana) de esta clase AppCompatDelegateImpl, que es conveniente para usar mWindow en la clase AppCompatDelegateImpl más adelante.
  2. Cree una instancia de mDecor (tipo DecorView, diseño raíz de PhoneWindow) y mContentParent (declarado como ViewGroup, instancia real de FrameLayout) mediante el método mWindow.getDecorView(). El método getDecorView() llamará al método installDecor() de la clase PhoneWindow. El método installDecor() hace principalmente dos cosas: primero, llamar al método generateDecor para crear una instancia del atributo mDecor de la clase PhoneWindow (tipo DecorView, que es, FrameLayout, porque DecorView hereda de FrameLayout); en segundo lugar, ajuste el método generateLayout, primero seleccione diferentes ID de archivos de recursos de diseño de acuerdo con diferentes situaciones (los nodos raíz de estos archivos de diseño son todos LinearLayout) y luego ajuste el método mDecor.onResourcesLoaded para agregar el diseño correspondiente a la identificación del archivo de diseño seleccionado En el diseño raíz de PhoneWindow, obtenga el objeto de diseño cuya identificación es R.id.content y asígnelo a la instancia del atributo mContentParent de la clase PhoneWindow (declarada como ViewGroup, y FrameLayout en el archivo de diseño real).
    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.  Cargue uno de los cuatro archivos de diseño según las diferentes situaciones y asígnelo a la variable local ViewGroup subDecor. Los cuatro diseños incluyen: R.layout.abc_dialog_title_material, R.layout.abc_screen_toolbar, R.layout.abc_screen_simple_overlay_action_mode, R.layout.abc_screen_simple. Estos cuatro diseños tienen una cosa en común: todos introducen el diseño abc_screen_content_include.xml a través de inclusión, y solo hay un diseño ContentFrameLayout cuya identificación es R.id.action_bar_activity_content en el archivo de diseño importado. El contenido de estos diseños es el siguiente:
    布局四: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. Obtenga el diseño de contenido contentView del diseño subDecor, obtenga el diseño de contenido windowContentView del diseño mDecor (diseño raíz de PhoneWindow) y agregue todas las subvistas en windowContentView a contentView. De acuerdo con el objeto subDecor de diseño cargado anteriormente, obtenga la instancia de diseño (tipo ContentFrameLayout) cuyo ID es R.id.action_bar_activity_content y asígnela a la variable local ContentFrameLayout contentView; luego vaya al diseño raíz de PhoneWindow (es decir, mDecor) para busque la identificación como R.id. La instancia de diseño del contenido y asígnela a la variable local ViewGroup windowContentView (como se mencionó en el segundo elemento anterior, esta variable de diseño se declara como ViewGroup y el archivo de diseño real es FrameLayout); luego si windowContentView no está vacía, la subvista en windowContentView se agregará a contentView y eliminará su propia subvista, luego establecerá la identificación de windowContentView en -1 y la identificación de contentView en android.R.id.content.
  5. Finalmente, llame al método mWindow.setContentView(subDecor) para agregar el diseño de subDecor al diseño de contenido de PhoneWindow. En el método setContentView en la clase PhoneWindow, agregue el objeto de diseño subDecor entrante al atributo mContentParent en la clase PhoneWindow, es decir, agregue el diseño subDecor a la posición de diseño de contenido del diseño raíz de PhoneWindow 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;
    }

Supongo que te gusta

Origin blog.csdn.net/hnjcxy/article/details/124105437
Recomendado
Clasificación