我们都知道在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来看:
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
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" />
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" />
将得到的布局文件加载到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的源码分析请参考: