美文网首页
Android Activity setContentView流

Android Activity setContentView流

作者: Bfmall | 来源:发表于2023-09-25 18:23 被阅读0次

    Activity setContentView流程解析

    参考图解:

    自主生码.jpg

    1.当MainActivity直接继承自Activity时

    此时会执行Activity类的setContentView方法:

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

    主要的逻辑在getWindow().setContentView(layoutResID)中,下面将以实现类PhoneWindow的setContentView方法进行讲解
    在该方法的开头,首先会对mContentParent进行判空检查,为空时将调用installDecor()进行DecorView的初始化:

        if (mContentParent == null) {
            installDecor();
        } 
    

    进入installDecor方法,首先当decorView为空时,会调用generateDecor方法进行DecorView的创建:

        if (mDecor == null) {
            //核心方法
            mDecor = generateDecor(-1);
            //......
        } else {
            mDecor.setWindow(this);
        }
    
    

    进入generateDecor方法,该方法比较简短,在完成context的创建后就创建出一个DecorView并返回:

        protected DecorView generateDecor(int featureId) {
            Context context;
            //根据不同情况对context进行初始化
            //......
            return new DecorView(context, featureId, this, getAttributes());
        }
    
    

    至此完成了对mDecor变量的赋值。
    回到installDecor方法,此时将进入contentParent的创建逻辑,核心方法是generateLayout,该方法将对mContentParent进行赋值(这个contentParent就将作为MainActivity布局的父容器):

        if (mContentParent == null) {
            //核心方法
            mContentParent = generateLayout(mDecor);
            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);
            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                //......
            } 
            //...... 
        }    
    
    

    进入generateLayout方法,

        protected ViewGroup generateLayout(DecorView decor) {
            //......
            //上面省略的代码根据Flags、Feature等数据完成了对layoutResource的赋值
            mDecor.startChanging();
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
            //ID_ANDROID_CONTENT值为com.android.internal.R.id.content
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
            //......
            mDecor.finishChanging();
            return contentParent;    
        }
    
    

    进入onResourcesLoaded方法,此方法主要的工作就是将layoutResource对应的xml文件解析并添加到decorView中:

        void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
            //......
            mDecorCaptionView = createDecorCaptionView(inflater);
            final View root = inflater.inflate(layoutResource, null);
            if (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 {
                addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mContentRoot = (ViewGroup) root;
            initializeElevation();
        }
    
    

    此时完成了对decorView的xml布局文件的加载。
    回到generateLayout方法,此时将通过findViewById获取到com.android.internal.R.id.content这个ViewGroup,上述decorView加载的xml文件里就有一个控件id与之对应,这个控件就是我们加载并添加MainActivity的布局文件的地方。
    generateLayout方法完成了对mContentParent的赋值,也就是说,DecorView中放置MainActivity内容的父容器已经准备完毕。
    至此,installDecor方法的核心逻辑介绍完毕,接下来将MainActivity的布局文件加载到decorView的content中,即结束了整个加载流程:

        public void setContentView(int layoutResID) {
            //......
            mLayoutInflater.inflate(layoutResID, mContentParent);
            //......
        }
    
    

    2.当MainActivity直接继承自AppCompatActivity时

    首先,进入AppCompatActivity的setContentView方法:

        public void setContentView(@LayoutRes int layoutResID) {
            getDelegate().setContentView(layoutResID);
        }
    
    

    此处的getDelegate方法会创建一个AppCompatDelegate对象,由AppCompatDelegateImpl类实现,
    进入AppCompatDelegateImpl的setContentView方法:

        public void setContentView(int resId) {
            ensureSubDecor();
            //......
        }
    
    

    首先,进入ensureSubDecor方法:

       private void ensureSubDecor() {
            if (!mSubDecorInstalled) {
                //核心方法
                mSubDecor = createSubDecor();
               //......
            }
        }
    

    进入createSubDecor方法:

       private ViewGroup createSubDecor() {
            //ensureWindow方法完成Delegate与Window的绑定,确保mWindow存在
            ensureWindow();
            mWindow.getDecorView();
            //......
        }
    
    

    首先,进入PhoneWindow的getDecorView方法:

        public final @NonNull View getDecorView() {
            if (mDecor == null || mForceDecorInstall) {
                installDecor();
            }
            return mDecor;
        }
    
    

    可以看到,此处也会与第一种情况一样调到installDecor方法,该方法会将到PhoneWindow内部的DecorView的xml文件的加载解析为止的操作全部完成
    回到createSubDecor方法:

       private ViewGroup createSubDecor() {
            //......
            final LayoutInflater inflater = LayoutInflater.from(mContext);
            ViewGroup subDecor = null;
            //......
            //完成subDecor的布局解析与加载
    
            final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                    R.id.action_bar_activity_content);
    
            final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
            if (windowContentView != null) {
                while (windowContentView.getChildCount() > 0) {
                    final View child = windowContentView.getChildAt(0);
                    windowContentView.removeViewAt(0);
                    contentView.addView(child);
                }
    
                windowContentView.setId(View.NO_ID);
                contentView.setId(android.R.id.content);
            }
            mWindow.setContentView(subDecor);
            return subDecor;
        }
    

    windowContentView为android.R.id.content对应的View,contentView为R.id.action_bar_activity_content对应的View,与第一种情况不同的是:它会先将android.R.id.content的子View全部迁移到R.id.action_bar_activity_content上,之后将R.id.action_bar_activity_content对应的View的id替换为android.R.id.content,之后将subDecor添加到PhoneWindow上,进入PhoneWindow的getDecorView方法(此处直接列出了最终会执行到的方法):

       public void setContentView(View view, ViewGroup.LayoutParams params) {
            if (mContentParent == null) {
                installDecor();
            } 
            //......
            mContentParent.addView(view, params);
            //......
        }
    
    

    由于之前已经完成了mContentParent的创建,所以不会再执行installDecor,将直接进行addView。
    至此,将decorView添加到了PhoneWindow上,回到最初的AppCompatDelegateImpl的setContentView方法完成对MainActivity xml布局文件的加载:

        public void setContentView(int resId) {
            ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
            contentParent.removeAllViews();
            LayoutInflater.from(mContext).inflate(resId, contentParent);
            //......
        }
    

    3.流程总结

    1.Activity
    核心就是PhoneWindow的setContentView方法,其主要干了两件事:
    1.完成DecorView的创建与加载
    2.将MainActivity的布局加载到DecorView内的一个ViewGroup中

    创建DecorView,即installDecor方法,其内部用到了两个核心的方法:
    1.generateDecor方法创建出DecorView对象
    2.generateLayout方法完成这个DecorView对象的布局加载,并完成了MainActivity的父容器的赋值(即contentParent变量)

    2.AppCompatActivity
    核心就是AppCompatDelegateImpl的setContentView方法,它主要干了两件事:
    1.准备好subDecor
    2.将MainActivity的布局加载到subDecor内的一个ViewGroup中

    准备subDecor,即ensureSubDecor方法,用于确保subDecor已经完成创建,内部的核心逻辑在createSubDecor方法中

    createSubDecor主要干了几件事:
    1.确保Delegate获取到了MainActivity的PhoneWindow实例(ensureWindow方法)
    2.完成PhoneWindow内部的DecorView的创建与准备(也就是第一种情况的installDecor方法)
    3.创建subDecorView,并让subDecor接管R.id.content(原先属于PhoneWindow内部的DecorView)
    4.清空PhoneWindow内部的DecorView的内容,并将subDecor添加到该DecorView中
    5.把MaiActivity的布局加载到subDecor中(即R.id.content)

    4.差异梳理

    第一种情况直接走了Activity的setContentView方法,加载用户布局也是直接用的Activity的PhoneWindow里的DecorView
    第二种情况走了AppCompatDelegateImpl的setContentView方法,其PhoneWindow是从MainActivity获取的。使用了一个中介的subDecorView,PhoneWindow自身也有一个DecorView,并且完成了至DecorView的xml文件加载为止的所有操作,之后,subDecorView将被添加到该DecorView中,而原先往R.id.content的布局内容添加将全部由subDecorView进行接管,至此,方完成第二种情况正式加载MainActivity资源操作前的全部工作,而第一种情况则在PhoneWindow的DecorView xml文件加载工作完成时就已告结束。
    综上所述,两者最显著的区别就是第二种情况多了一层subDecorView的添加与替换接管的操作。

    ————————————————
    版权声明:本文为CSDN博主「浮生一落英」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_42700685/article/details/128617358

    相关文章

      网友评论

          本文标题:Android Activity setContentView流

          本文链接:https://www.haomeiwen.com/subject/garcbdtx.html