美文网首页Android源码分析Android源码分析Android项目源码分析
夜夜使用的setContentView()里面到底做了什么?

夜夜使用的setContentView()里面到底做了什么?

作者: 望北8261 | 来源:发表于2019-11-17 05:19 被阅读0次

    一、写在前面

    我们都知道继承Activity的onCreate()方法然后setContentView(R.layout.xxx)可以设置我们自己的布局文件,但是布局文件生成的View到底添加到哪里去了呢?首先在Activity这个类中我们可以看到有个mWindow变量,这个变量是在attach()方法里面创建的:mWindow = new PhoneWindow(...),然后在我们setContentView()的时候会创建一个mDecor变量,这就是我们Activity的第一个View,然后再往里面添加各种View,包括我们自己布局的View。此文就是分析下这些View都是怎么添加的。

    以前的以前我们自己的Activity都是直接继承Activity这个类的,从API21开始,我们一般都继承AppCompatActivity了,下面分别分析。

    注:以下源码版本为API28。

    二、继承Activity的时候

    // 这是我们普通的Activity,继承onCreate,添加我们的布局
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.xxx);
    }
    
    // 类:Activity
    public void setContentView(@LayoutRes int layoutResID) {
        // 跟踪getWindow()可以看到mWindow = new PhoneWindow(...),返回的是一个PhoneWindow
        // 所以这里跳到PhoneWindow的setContentView(...)
        // IDEA里面可以直接按ctrl+alt+单击弹出实现此方法的类,选择PhoneWindow的进入就行
        getWindow().setContentView(layoutResID);
        // ...
    }
    
    // 类:PhoneWindow
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        }
        // ...
        // 把我们自己的布局文件inflate后添加到mContentParent中
        // mContentParent就是我们自己的布局的父View
        mLayoutInflater.inflate(layoutResID, mContentParent);
        // ...
    }
    
    // 类:PhoneWindow
    private void installDecor() {
        // ...
        if (mDecor == null) {
            // 1、生成DecorView
            // mDecor是PhoneWindow的一个变量,同时PhoneWindow也是mDecor的一个变量
            mDecor = generateDecor(-1);
            // ...
        }
        // ...
        if (mContentParent == null) {
            // 2、生成mContentParent
            // 从前面我们知道这就是我们自己的布局的父类
            mContentParent = generateLayout(mDecor);
            // ...
        }    
    }
    
    1、生成DecorView
    // 类:PhoneWindow
    protected DecorView generateDecor(int featureId) {
        // ...
        // 直接创建了一个DecorView,点击去查看DecorView是继承了FrameLayout
        return new DecorView(context, featureId, this, getAttributes());
    }
    
    2、生成mContentParent
    // 类:PhoneWindow
    protected ViewGroup generateLayout(DecorView decor) {
        // 获取窗口属性,也就是AndroidManifest.xml中设置的主题
        // View的属性在layout.xml里面配置,Window的属性在主题里面配置
        TypedArray a = getWindowStyle();
        // 这里是一系列的主题设置
        // ...
    
        // 根据上面设置的属性,加载不同的根布局,所有布局都一定会有一个View的id是ID_ANDROID_CONTENT,
        // 也就是mContentParent,也就是我们setContentView()传入的View的父View
        // 这里随便选一个布局,比如:R.layout.screen_simple
        // 该文件在源码中的路径:frameworks/base/core/res/res/layout/screen_simple.xml
        // 下载源码方法:https://blog.csdn.net/qiantujava/article/details/102847414
        // 在线源码地址:http://aospxref.com/android-9.0.0_r45/xref/frameworks/base/core/res/res/layout/screen_simple.xml
        layoutResource = R.layout.screen_simple;
    
        // 1、解释上面选定的layout修饰布局文件
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
        // 这里findViewById其实调用的是getDecorView().findViewById(id)
        // 上面所有布局都会有ID_ANDROID_CONTENT这个id,也就是:android:id="@android:id/content"
        // 我们自己的布局文件就是添加到这个ViewGroup中的
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        // ...
        return contentParent;
    }
    
    
    1、解释修饰布局文件
    
    // 类:DecorView
    // 解释layout文件
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        // ...
        // Caption有标题的意思
        mDecorCaptionView = createDecorCaptionView(inflater);
        // 解释刚才那个布局screen_simple.xml,里面会有ID_ANDROID_CONTENT
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            // 没搞清楚mDecorCaptionView是个什么东西,先不管这个
        } else {
            // 没有mDecorCaptionView的时候,直接把root添加到DecorView中
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        // ...
    }
    

    上面有几个重点关注的变量:
    mWindow是PhoneWindow的对象,是Activity的一个变量,在Activity.attach()方法里面就创建了;
    mDecor是DecorView的对象,是PhoneWindow的一个变量;
    mContentRoot是修饰布局文件inflate得到的View,添加到了mDecor中;
    mContentParent是mContentRoot中id为ID_ANDROID_CONTENT的View;
    最后我们setContentView传入的View是添加到mContentParent的。
    所以我们可以得到个大概的布局关系图:

    image

    三、继承AppCompatActivity

    // 这是我们普通的Activity,继承onCreate,添加我们的布局
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.xxx);
    }
    
    // 类:AppCompatActivity
    public void setContentView(@LayoutRes int layoutResID) {
        // 点进去getDelegate()可以看到返回的是AppCompatDelegateImpl
        // 所以这里调用的是AppCompatDelegateImpl.setContentView()
        getDelegate().setContentView(layoutResID);
    }
    
    // 类:AppCompatDelegateImpl
    public void setContentView(int resId) {
        // 确保mSubDecor是否已经生成
        // 这里还不知道mSubDecor是个什么东西,下面再看
        ensureSubDecor();
        // 把我们自己的布局添加到contentParent
        // 其实从ensureSubDecor()里面可以知道这个android.R.id.content对应的View已经不是前面分析的mContentParent了
        // 而是mSubDecor里面的一个ContentFrameLayout
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        // ...
    }
    
    // 类:AppCompatDelegateImpl
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
            // ...
        }
    }
    
    // 类:AppCompatDelegateImpl
    private ViewGroup createSubDecor() {
        // 设置各种主题
        // ...
        // 跟踪这个mWindow,可以看到是从Activity传过来的,所以这个也是PhoneWindow
        // getDecorView()里面就一个判断,如果mDecor==null,就installDecor()
        // installDecor()在上面已经分析了,一毛一样的
        // installDecor()后就生成了mDecor和mContentParent了
        mWindow.getDecorView();
    
        // 根据不同的主题inflate不同的布局文件,然后赋值给subDecor
        // 所以,subDecor就是一个普通的ViewGroup
        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;
        // ...
        subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
        // ...
        // 上面所有的布局都会有个id=R.id.action_bar_activity_content的ContentFrameLayout
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
    
        // 上面mWindow.getDecorView()已经生成mContentParent了,id就是android.R.id.content
        // 所以这里windowContentView其实就是mContentParent
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // 把windowContentView的所有子view都添加到contentView中
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
    
            // 把windowContentView的id设为了NO_ID,把contentView的id设成了android.R.id.content
            // 此后各个view的关系还是:DecorView->mContentRoot->mContentParent(就是windowContentView)
            // 但是mContentParent里面的view已经全部移到contentView中了,同时contentView还没添加到DecorView
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
            // ...
        }
    
        // 把contentView添加到DecorView中
        mWindow.setContentView(subDecor);
        // ...
        return subDecor;
    }
    
    // 类:PhoneWindow
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 前面createSubDecor()中调了mWindow.getDecorView()已经生成了mContentParent,所以直接跳过
        if (mContentParent == null) {
            installDecor();
        }
        // ...
        // 这个view是前面传过来的subDecor
        mContentParent.addView(view, params);
        // ...
    }
    

    由上面的分析,我们同样可以得到以下总结:
    mWindow是PhoneWindow的对象,是Activity的一个变量,在Activity.attach()方法里面就创建了;
    mDecor是DecorView的对象,是PhoneWindow的一个变量;
    mContentRoot是修饰布局文件inflate得到的View,添加到了mDecor中;
    mContentParent是mContentRoot中id为ID_ANDROID_CONTENT的View,但是被设置为NO_ID了;
    mSubDecor是含有R.id.action_bar_activity_content的布局,mSubDecor会被添加到mContentParent中;
    contentView是ContentFrameLayout,是mSubDecor中id=R.id.action_bar_activity_content的view,最后会被设成id=android.R.id.content。
    最后我们setContentView()传入的view是会被添加到id=android.R.id.content的view中的,
    所以我们可以得到个大概的布局关系图:

    image

    四、继承Activity和继承AppCompatActivity的区别

    从上面的分析可以看出来,继承AppCompatActivity的时候,布局中多了个mSubDecorcontentView,之前我们的布局是添加到mContentParent的,现在是添加到contentView了,其实换汤不换药,只是在中间插入了一层view。为什么要这样呢?因为在API28的时候官方大力推荐MaterialDesign,在中间插入了一层mSubDecor就可以为所欲为的加入MaterialDesign的东西了,应该是酱紫吧。

    五、写在最后

    其实看源码也没有那么难嘛,一开始看可能会觉得超复杂,跳来跳去的,谷歌什么破工程师,写出来的代码乱七八糟。但是当你看的多了,这里的多包括看的次数多和看的内容多,看的次数多了你会对这段源码印象更深刻,下次再回来看的时候不会再那么陌生了,而看的内容多的话,你会发现越来越清晰了,各个点都是有关系的,之前那个点可能没看懂,看到这个点的时候忽然明白了上个点,都是有联系的。
    PS. 标题里面的夜夜并没有说假话,现在是凌晨4点、

    相关文章

      网友评论

        本文标题:夜夜使用的setContentView()里面到底做了什么?

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