Activity setContentView流程解析
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的添加与替换接管的操作。