setContentView主要工作以及VIew整體布局層次
View測(cè)量布局繪制流程都是從根View(DecorView)開始的佣赖,DecorView究竟在那里創(chuàng)建熊痴?在Activity.onCreate生命周期中setContentView()主要做了哪些工作;以及Activity中View的整體布局層次微渠;帶著這些疑問(wèn)诫舅,我們來(lái)一步步尋找問(wèn)題的答案卦羡;
在Activity.onCreate中會(huì)調(diào)用setContentView(R.layout.xxx),Activity的視圖由setContentView提供,R.layout.xxx是布局文件的資源id祟滴;
public void setContentView(@LayoutRes int layoutResID) {
/* Activity的視圖由setContentView提供振惰,layoutResID是布局文件資源id;
Window是一個(gè)抽象類,具體實(shí)現(xiàn)位于Phonewindow中
*/
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow.setContentView代碼如下:
//在onResume的makeVisible方法中垄懂,DecorView會(huì)真正完成添加和顯示著兩個(gè)過(guò)程骑晶,那時(shí)Activity的視圖才能被看到
@Override
public void setContentView(int layoutResID) {
// 1.Acitivity剛啟動(dòng)時(shí),mContentParent為null埠偿,會(huì)執(zhí)行installDecor();
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 2.將Activity中的布局文件添加到DecorView的mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
在PhoneWindow.setContentView中有兩個(gè)關(guān)鍵的方法:
installDecor()和mLayoutInflater.inflate(layoutResID, mContentParent);
下面來(lái)分別看下這兩個(gè)方法主要做了哪些工作:
PhoneWindow.installDecor();
private void installDecor() {
mForceDecorInstall = false;
//第一次執(zhí)行installDecor時(shí)透罢,mDecor和mContentParent都為null榜晦;
if (mDecor == null) {
/*mDecor是DecorView實(shí)例冠蒋,DecorView的根View.
generateDecor會(huì)創(chuàng)建DecorView*/
mDecor = generateDecor(-1);
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//generateLayout主要工作:找到系統(tǒng)默認(rèn)布局,并將默認(rèn)布局文件添加到DecorView中
mContentParent = generateLayout(mDecor);
}
}
PhoneWindow.generateDecor(-1)如下乾胶,可以看出DecorView的是在PhoneWindow.installDecor()中創(chuàng)建的抖剿。
protected DecorView generateDecor(int featureId) {
//主要工作是創(chuàng)建并返回DecorView實(shí)例;
return new DecorView(context, featureId, this, getAttributes());
}
PhoneWindow.generateLayout(mDecor);
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
/*layoutResource是系統(tǒng)默認(rèn)的布局文件的資源id识窿;
以下代碼根據(jù)設(shè)置的window屬性(notitle斩郎、noactionbar等)查找對(duì)應(yīng)系統(tǒng)根布局文件;
系統(tǒng)布局文件位于/framworks/base/core/res/layout/screen_title喻频、screen_simple(默認(rèn)布局文件)缩宜;
這些系統(tǒng)布局文件中都有ID為Content的控件,并且id為content控件基本都是一個(gè)FrameLayout;
*/
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
//根據(jù)window屬性選擇對(duì)應(yīng)的默認(rèn)布局文件甥温;
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} 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) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
//根據(jù)window屬性選擇對(duì)應(yīng)的默認(rèn)布局文件锻煌;
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
//通過(guò)以上代碼可知:設(shè)置window的flag在setContentView之前才能起作用;
/*DecorView.onResourcesLoaded方法:通過(guò)layoutInfalter將系統(tǒng)默認(rèn)布局加載出來(lái)姻蚓,
然后調(diào)用decorview的addView方法宋梧,將系統(tǒng)默認(rèn)布局文件加載到DecorView中,這樣系統(tǒng)默認(rèn)布局文件
就加載到了DecorView中狰挡。
*/
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
/*通過(guò)findViewById(ID_ANDROID_CONTENT);查找ID為com.android.internal.R.id.content捂龄;布局文件。
這就是應(yīng)用布局的父view加叁。Phonewindow.findViewById()實(shí)際是獲取Decor.findViewById()方法倦沧;
因?yàn)镈ecorview已經(jīng)把系統(tǒng)默認(rèn)布局文件添加早自己的view中,默認(rèn)布局中有ID為content布局它匕,找到ID為content
FrameLayout布局之后展融,將該framelayout賦值給mContentParent;
setContentView設(shè)置布局最后是加載到mContentParent中超凳。
*/
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent;
}
相關(guān)說(shuō)明:
(1)首先根據(jù)設(shè)置的window屬性(notitle愈污、noactionbar等)查找對(duì)應(yīng)系統(tǒng)根布局文件耀态;系統(tǒng)布局文件位于/framworks/base/core/res/layout/screen_title、screen_simple(默認(rèn)布局文件)暂雹;這些系統(tǒng)布局文件中都有ID為Content的控件首装,并且id為content控件基本都是一個(gè)FrameLayout;
(2)找到系統(tǒng)默認(rèn)布局文件的資源id之后,調(diào)用DecorView.onResourcesLoaded(layoutinflater杭跪,系統(tǒng)默認(rèn)布局文件資源id)
(3)DecorView.onResourcesLoaded方法:步驟一:通過(guò)layoutInfalter將系統(tǒng)默認(rèn)布局加載出來(lái)仙逻,然后調(diào)用decorview的addView方法,將系統(tǒng)默認(rèn)布局文件加載到DecorView中涧尿,這樣系統(tǒng)默認(rèn)布局文件就加載到了DecorView中系奉;
(4)通過(guò)findViewById(ID_ANDROID_CONTENT);查找ID為com.android.internal.R.id.content;布局文件姑廉。這就是應(yīng)用布局的父view缺亮。Phonewindow.findViewById()實(shí)際是獲取Decor.findViewById()方法;因?yàn)镈ecorview已經(jīng)把系統(tǒng)默認(rèn)布局文件添加早自己的view中桥言,默認(rèn)布局中有ID為content布局萌踱,找到ID為content FrameLayout布局之后,將該framelayout賦值給mContentParent号阿;setContentView設(shè)置布局最后是加載到mContentParent中并鸵。
(5)installDecor執(zhí)行之后,回到setContentView中扔涧,繼續(xù)執(zhí)行mLayoutInflater.inflate(layoutResID,mContentParent);layoutResID:我們應(yīng)用中setContentView的布局資源id园担; mContentParent:系統(tǒng)默認(rèn)布局文件中id為content的Framelayout中;這樣setContentView工作就完成了枯夜。
(6)以上解釋了為什么設(shè)置window的屬性必須在setContentView之前才能起作用弯汰;
(7)View整體層次為:DecorView>系統(tǒng)默認(rèn)根布局文件>應(yīng)用setContentView的布局添加到系統(tǒng)默認(rèn)布局文件中Id為android.R.id.content的布局中。
(8)以上已經(jīng)完成將布局文件添加到系統(tǒng)根布局文件中,Android中所有視圖都是通過(guò)Window來(lái)呈現(xiàn)的卤档,此時(shí)DecorView還沒(méi)有被WM添加到Window中蝙泼。Window addView之后,會(huì)調(diào)用ViewRootImpl來(lái)完成界面的繪制工作劝枣;因?yàn)閂iew還沒(méi)有開始繪制汤踏,這時(shí)候是不會(huì)顯示出來(lái)的。View測(cè)量布局繪制是從ViewRootImpl的performTraversals方法中開始的舔腾,
(9)通過(guò)以上分析可以得出View的整體層次結(jié)構(gòu)如下圖所示(轉(zhuǎn)自工匠若水)