Activity setContentView流程解析
參考圖解:

1.當MainActivity直接繼承自Activity時
此時會執(zhí)行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的創(chuàng)建:
if (mDecor == null) {
//核心方法
mDecor = generateDecor(-1);
//......
} else {
mDecor.setWindow(this);
}
進入generateDecor方法,該方法比較簡短,在完成context的創(chuàng)建后就創(chuàng)建出一個DecorView并返回:
protected DecorView generateDecor(int featureId) {
Context context;
//根據不同情況對context進行初始化
//......
return new DecorView(context, featureId, this, getAttributes());
}
至此完成了對mDecor變量的賦值。
回到installDecor方法,此時將進入contentParent的創(chuàng)建邏輯,核心方法是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方法會創(chuàng)建一個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方法(此處直接列出了最終會執(zhí)行到的方法):
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}
//......
mContentParent.addView(view, params);
//......
}
由于之前已經完成了mContentParent的創(chuàng)建,所以不會再執(zhí)行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的創(chuàng)建與加載
2.將MainActivity的布局加載到DecorView內的一個ViewGroup中
創(chuàng)建DecorView,即installDecor方法,其內部用到了兩個核心的方法:
1.generateDecor方法創(chuàng)建出DecorView對象
2.generateLayout方法完成這個DecorView對象的布局加載,并完成了MainActivity的父容器的賦值(即contentParent變量)
2.AppCompatActivity
核心就是AppCompatDelegateImpl的setContentView方法,它主要干了兩件事:
1.準備好subDecor
2.將MainActivity的布局加載到subDecor內的一個ViewGroup中
準備subDecor,即ensureSubDecor方法,用于確保subDecor已經完成創(chuàng)建,內部的核心邏輯在createSubDecor方法中
createSubDecor主要干了幾件事:
1.確保Delegate獲取到了MainActivity的PhoneWindow實例(ensureWindow方法)
2.完成PhoneWindow內部的DecorView的創(chuàng)建與準備(也就是第一種情況的installDecor方法)
3.創(chuàng)建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文件加載工作完成時就已告結束。
綜上所述,兩者最顯著的區(qū)別就是第二種情況多了一層subDecorView的添加與替換接管的操作。
————————————————
版權聲明:本文為CSDN博主「浮生一落英」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_42700685/article/details/128617358