setContentView 方法如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow() , 實際上就是PhoneWindow , 繼續(xù)查看PhoneWindow類中的setContentView方法
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
/// 這里初始化 decor
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 {
// 這里是不是很眼熟箱歧,可以猜測是加載布局文件巧婶,
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
繼續(xù)查看如何初始化 decor
private void installDecor() {
if (mDecor == null) {
// 初始化decor
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
// 設置DecorView的window 為PhoneWindow對象
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成內(nèi)容根節(jié)點
mContentParent = generateLayout(mDecor);
}
// ...其他代碼忽略
}
先查看 generateDecor
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
創(chuàng)建DecorView 對象
在看看 generateLayout(decorView)
protected ViewGroup generateLayout(DecorView decor) {
// .....
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
// 這里的代碼比較眼熟封断,設置主體的時候會用到
requestFeature(FEATURE_ACTION_BAR);
}
/// ...
// Inflate the window decor.
int layoutResource;
// 這里獲取一個features , 根據(jù)這個值獲取一個布局文件
int features = getLocalFeatures();
// 那最后一個布局查看
if(...){...}
else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
// 這個方法也很重要犁钟, 通過DecorView 加載上面獲取到的布局文件
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 這里是拿到哪個id 為@android:id/content 的對象
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ...
// 最后返回這個content饵筑,那由此我們可以知道 PhoneWindow的mContentParent 對象其實就是id為@android:id/content 的FrameLayout對象
return contentParent;
}
screen_simple布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
這里就是一個普通的LinearLayout , 重點是里面有個FrameLayout而且id為@android:id/content 阻塑, 這個就是我們自定義開發(fā)布局都會放在這個FrameLayout 里面
現(xiàn)在再回過頭來看看setContentView 方法里面的 mLayoutInflater.inflate(layoutResID, mContentParent);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 這個根據(jù)名字可以猜出乳乌,是預加載布局文件
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 這個是獲取xml資源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 重要的是這個方法
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
繼續(xù)查看 inflate(parser, root, attachToRoot);
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
// 拿到所有的屬性
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
// 這個result 就算 decorView
View result = root;
if (TAG_MERGE.equals(name)) {
// 這里判斷了是否是 <merge>解點袱蜡, 不能是最外層的根節(jié)點
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 繼續(xù)通過遞歸調(diào)用的方式,加載出所有的子節(jié)點互亮,并放在root節(jié)點犁享,即decorView 下面
rInflate(parser, root, inflaterContext, attrs, false);
}
}
}
/// finishInflate 是否停止解析
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
// 解析include 標簽,不能是根節(jié)點標簽
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 再次遞歸調(diào)用豹休,解析子節(jié)點
rInflateChildren(parser, view, attrs, true);
// 把解析出來的子節(jié)點炊昆,放在上級節(jié)點view[]里面
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
// 停止解析
if (finishInflate) {
parent.onFinishInflate();
}
}
通過上面一層一層的解析,就能把我們自己定義的布局威根,解析出來凤巨, 并放在mContentParent里面, 這個就是@android:id/content的FrameLayout洛搀。
到這一步敢茁,好像還缺點啥,還沒看到 mContentParent , 是如何放進DecorView 里面的留美。再回頭看看PhoneWindow類里面的 generateLayout方法彰檬,剛才說的有一個加載資源的方法,我們漏看了
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
這個方法谎砾,從表面上看逢倍, 是拿到了某個主題的資源文件,并解析
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// ...
mDecorCaptionView = createDecorCaptionView(inflater);
// 這一步就是解析系統(tǒng)主題中的某個帶有@android:id/content的哪個布局
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 {
// Put it below the color views.
// 在這一步景图,把mContentParent 添加到 decorView 的第一個元素里面了
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
到這一步较雕,基本上都清晰了