上一篇說了Activity和Window關(guān)聯(lián)源碼分析祈惶,現(xiàn)順著源碼查看下Activity的setContentView的具體原理官地,了解為什么我們在Activity的布局的添加流程挫掏。
Activity的setContentView()方法瘦陈,源碼如下:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
}
本質(zhì)是調(diào)用的Activity內(nèi)部的window的setContentView()方法鳖谈,上面文章了解到岁疼,Activity內(nèi)部的mWindow對象,實際是一個PhoneWindow實例缆娃,所以activity的setContentView方法捷绒,就是調(diào)用了PhoneWindow內(nèi)部的setContentView(),PhoneWindow內(nèi)部方法如下:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
......
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
if (mTitleView != null) {
if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
View titleContainer = findViewById(com.android.internal.R.id.title_container);
if (titleContainer != null) {
titleContainer.setVisibility(View.GONE);
} else {
mTitleView.setVisibility(View.GONE);
}
if (mContentParent instanceof FrameLayout) {
((FrameLayout)mContentParent).setForeground(null);
}
} else {
mTitleView.setText(mTitle);
}
}
}
}
......
}
方法中的贯要,mContentParent暖侨,就是我們?nèi)粘V赖腶ndroid:id/content的布局,而我們?nèi)粘5膕etContentView的這個view或者是layout崇渗,父布局就是此處的mContentParent字逗。順著源碼看京郑,首先判斷mContentParent是否為空,初次調(diào)用葫掉,此處為空些举,進入installDecor()方法。
installDecor()方法挖息,首先判斷了mDecor金拒,這個mDecor是PhoneWindow的一個內(nèi)部類DecorView的實例,是一個FrameLayout套腹,acitivty對應(yīng)的布局界面的最頂層的視圖绪抛。首先判空處理,為空則調(diào)用generateDecor()方法生成一個對象电禀。
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
接下來是判斷mContentParent幢码,若為null,則調(diào)用generateLayout()方法生成mContentParent尖飞,方法源碼如下
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
......
mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
......
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {//布局設(shè)置左右圖標
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_title_icons;
} else {
layoutResource = com.android.internal.R.layout.screen_title_icons;
}
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {//布局增加進度條
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = com.android.internal.R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {//自定義title
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_custom_title;
} else {
layoutResource = com.android.internal.R.layout.screen_custom_title;
}
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {//默認情況
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
layoutResource = com.android.internal.R.layout.dialog_title;
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else {//notitle的情形
// Embedded, so no decoration is needed.
layoutResource = com.android.internal.R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
......
return contentParent;
}
根據(jù)設(shè)置的window屬性(feature和windowStyle)症副,做不同處理。默認情況下政基,features為DEFAULT_FEATURES贞铣,它的詳細定義在Window內(nèi)部,可自行查看沮明。根據(jù)不同的features辕坝,添加不同的布局,默認布局為R.layout.screen_title荐健,調(diào)用PhoneWindow的成員變量mLayoutInflater酱畅,加載布局文件并添加到根視圖mDecor中;而mContentParent江场,則是mDecor內(nèi)部的一個id為ID_ANDROID_CONTENT的viewGroup纺酸。
回到installDecor()方法中,獲取mContentParent后址否,設(shè)置title相關(guān)內(nèi)容餐蔬。如果設(shè)置的features不是FEATURE_NO_TITLE,則表示有title佑附,便會去設(shè)置title樊诺。同時根據(jù)上述代碼,可以知道帮匾,為什么activity內(nèi)部調(diào)用requestWindowFeature設(shè)置窗口屬性啄骇,需要放在setContentView前面才可以,它本質(zhì)是就是通過PhoneWindow的requestFeature()方法去實現(xiàn)的。
回到PhoneWindow的setContentView方法瘟斜,調(diào)用了installDecor()方法缸夹,可以獲取mContentParent痪寻,然后將布局或視圖加載到當前的mContentParent中(通過LayoutInflater.inflate或者是addView去實現(xiàn)),最終虽惭,布局設(shè)置完成橡类,回調(diào)當前Window.Callback對象的onContentChanged(),返回給Activity芽唇,剩下的由用戶自行處理顾画。
......
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
......
所以,我們現(xiàn)在知道了匆笤,acitivity內(nèi)部維持了一個Window對象研侣,實際是PhoneWindow實例,而activity的布局視圖炮捧,就是放在PhoneWindow內(nèi)部的一個mDecor布局中庶诡,而 我們的布局,其實是放在了mDecor內(nèi)部的一個content的FrameLayout里面咆课。