夜夜使用的setContentView()里面到底做了什么?
一、写在前面
我们都知道继承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的。
所以我们可以得到个大概的布局关系图:
三、继承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中的,
所以我们可以得到个大概的布局关系图:
四、继承Activity和继承AppCompatActivity的区别
从上面的分析可以看出来,继承AppCompatActivity的时候,布局中多了个mSubDecor
和contentView
,之前我们的布局是添加到mContentParent
的,现在是添加到contentView
了,其实换汤不换药,只是在中间插入了一层view。为什么要这样呢?因为在API28的时候官方大力推荐MaterialDesign
,在中间插入了一层mSubDecor就可以为所欲为的加入MaterialDesign的东西了,应该是酱紫吧。
五、写在最后
其实看源码也没有那么难嘛,一开始看可能会觉得超复杂,跳来跳去的,谷歌什么破工程师,写出来的代码乱七八糟。但是当你看的多了,这里的多包括看的次数多和看的内容多,看的次数多了你会对这段源码印象更深刻,下次再回来看的时候不会再那么陌生了,而看的内容多的话,你会发现越来越清晰了,各个点都是有关系的,之前那个点可能没看懂,看到这个点的时候忽然明白了上个点,都是有联系的。
PS. 标题里面的夜夜并没有说假话,现在是凌晨4点、