Android源码分析Android源码分析Android项目源码分析

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

2019-11-17  本文已影响0人  望北8261

一、写在前面

我们都知道继承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点、

上一篇下一篇

猜你喜欢

热点阅读