我们都知道在Activity中通过setContentView(layoutId)
来加载布局文件,使我们的布局文件能够显示在手机上,那么系统是如何将我们的布局文件加载到界面上的呢?
setContentView
// Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
通过setContentView
的源码可以发现其实是调用了Window的setContentView方法,而Window是一个抽象类,PhoneWindow是Window的实现类。
// PhoneWindow.java
@Override
public void setContentView(int layoutResID) {
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
代码中首先通过installDecor()
创建出DecorView。
之后再通过mLayoutInflater.inflate(layoutResID, mContentParent)
将我们的布局加载进内存。
DecorView
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 创建DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 将android.R.id.content解析出来
mContentParent = generateLayout(mDecor);
}
}
installDecor
主要有2个作用:
-
创建DeorView
protected DecorView generateDecor(int featureId) { Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { context = new DecorContext(applicationContext, getContext().getResources()); if (mTheme != -1) { context.setTheme(mTheme); } } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
-
将android.R.id.content解析出来
protected ViewGroup generateLayout(DecorView decor) { // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // 各种判断加载系统布局 if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else { layoutResource = R.layout.screen_simple; } mDecor.startChanging(); // 将得到的布局文件加载到DecorView中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); // 获取android.R.id.content ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); }
generateLayout
的作用就是:-
通过条件获取到系统需要加载的布局文件,这里我们以最简单的
R.layout.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>
-
将得到的布局文件加载到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 { // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation();
}
```-
获取android.R.id.content
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content; ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
至此系统的布局就加载好了,大概的结构如下:
-
inflate加载我们的布局
当系统的布局通过installDecor()
加载完成之后,就会通过mLayoutInflater.inflate(layoutResID, mContentParent)
加载我们自己设置进去的布局。
而inflate的源码分析请参考: