Android的setContentView加载view流程

AppCompatActivity中的setContentView会调用AppCompatDelegate子类AppCompatDelegateImpl的setContentView方法来完成加载view的逻辑。AppCompatDelegateImpl类的setContentView方法源码:

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

这个setContentView方法加载布局主要是两步:

  • 创建mSubDecor实例,并通过调用PhoneWindow的setContentView(subDecor)方法完成布局view的加载。方法中的ensureSubDecor()会调用createSubDecor()方法来创建mSubDecor实例(声明为ViewGroup类型,实际加载的布局文件实例为FitWindowsLinearLayout、FitWindowsFrameLayout、ActionBarOverlayLayout三种之一),这个mSubDecor布局一般情况下会包含两个部分,一个标题部分,一个id为content的内容布局(通常是FrameLayout类型);
  • 把我们的布局通过addView(v)方法添加到mSubDecor布局中content内容布局里面。

简化分析入口:https://blog.csdn.net/hnjcxy/article/details/124127855icon-default.png?t=M276https://blog.csdn.net/hnjcxy/article/details/124127855

接下来详细分析第一步的布局加载流程,AppCompatDelegateImpl类的createSubDecor()方法简化后的源码:

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

AppCompatDelegateImpl类的createSubDecor()方法说明:

  1. 处理AppCompatDelegateImpl类的mWindow对象(不是创建,因为是从Activity中获取的实例)。ensureWindow方法中会通过attachToWindow方法获取Activity类中的mWindow属性实例(声明为Window类型,实例是在Activity的attach方法里面 new PhoneWindow(this, window, activityConfigCallback) 创建出来的,其中window参数为null,可以查看ActivityThread在performLaunchActivity方法中调用activity.attach方法的传参),并赋值给本类AppCompatDelegateImpl的属性mWindow(Window类型),方便后面在AppCompatDelegateImpl类中使用mWindow。
  2. 通过mWindow.getDecorView()方法来创建mDecor实例(DecorView类型,即PhoneWindow的根布局)和mContentParent(声明为ViewGroup,实际FrameLayout实例)。getDecorView()这个方法里面会调PhoneWindow类的installDecor()方法,installDecor()方法主要做两件事;第一,调generateDecor方法来创建PhoneWindow类的mDecor属性实例(DecorView类型,即FrameLayout,因为DecorView继承自FrameLayout);第二,调generateLayout方法,先根据不同情况选择不同的布局资源文件id(这些布局文件根节点都是LinearLayout),再调mDecor.onResourcesLoaded方法把选择的布局文件id对应的布局添加到PhoneWindow的根布局里面,之后再获取id为R.id.content的布局对象,并赋值给PhoneWindow类的mContentParent属性实例(声明为ViewGroup,实际布局文件中是FrameLayout)。
    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.  根据不同情况加载四种布局文件中的一种,并赋值给局部变量ViewGroup subDecor。四种布局包括:R.layout.abc_dialog_title_material、R.layout.abc_screen_toolbar、R.layout.abc_screen_simple_overlay_action_mode、R.layout.abc_screen_simple。这四个布局有个共同点,都通过include 引入了abc_screen_content_include.xml布局,而这个引入的布局文件里面只有一个id为R.id.action_bar_activity_content的ContentFrameLayout布局,这几个布局的内容如下:
    布局四: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. 从布局subDecor中获取内容布局contentView,从布局mDecor(PhoneWindow根布局)中获取内容布局windowContentView,并将windowContentView里面的子view都添加到contentView里面。根据上面加载的布局subDecor对象,获取里面id为R.id.action_bar_activity_content的布局实例(ContentFrameLayout类型),并赋值给局部变量ContentFrameLayout contentView;再去PhoneWindow根布局(即mDecor)里面找到id为R.id.content的布局实例,并赋值给局部变量ViewGroup windowContentView(上面第二条中说过,这个布局变量声明为ViewGroup,实际布局文件中是FrameLayout);然后如果windowContentView不为空,则将windowContentView里面的子view都添加给contentView,并移除自己的子view,再将windowContentView的id置空为-1,将contentView的id置为android.R.id.content。
  5. 最后调用mWindow.setContentView(subDecor)方法把subDecor布局添加到PhoneWindow的内容布局里面。在PhoneWindow类中的setContentView方法中,将传进来的subDecor布局对象添加到PhoneWindow类中的mContentParent属性中,即添加subDecor布局到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;
    }

猜你喜欢

转载自blog.csdn.net/hnjcxy/article/details/124105437