1、Activity的setContentView()方法
当我们在Activity中的onCreate()方法中调用setContentView(R.layout.draw_activity) 时,我们会调用Activity的setContentView()方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);// 2、phoneWindow的setContentView()方法
initWindowDecorActionBar();
}
可以看见他们都先调运了getWindow()的setContentView方法,我们这里的getWindow()为一个phoneWindow
2、phoneWindow的setContentView()方法
@Override
public void setContentView(int layoutResID) {
// 如果mContentParent为null,则初始化DecorView
// 这里的mContentParent为我们的view外面的布局,就是id为content的FrameLayout
if (mContentParent == null) {
// 初始化DecorView
installDecor();// 3、PhoneWindow类的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 {
// 将我们的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
3、PhoneWindow类的installDecor()方法
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 初始化mDecor
mDecor = generateDecor(-1); // 4、PhoneWindow类的generateDecor()方法
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//根据窗口的风格修饰,选择对应的修饰布局文件,
//并且将id为content的FrameLayout赋值给mContentParent
mContentParent = generateLayout(mDecor); // 5、PhoneWindow类的generateLayout()方法
//......
//初始化一堆属性值
}
}
4、PhoneWindow类的generateDecor()方法
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();
}
// 直接new一个DecorView并返回
return new DecorView(context, featureId, this, getAttributes());
}
5、PhoneWindow类的generateLayout()方法
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
//......
//依据主题style设置一堆值进行设置
// Inflate the window decor.
// 资源id,根据设定好的features值选择不同的窗口修饰布局文件,得到layoutResource值
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
// ... 不同的判断,选择不同的布局文件
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 将布局文件加载到mDecor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);// 6、DecorView类的onResourcesLoaded()方法
// 从mDecor中获取id值为com.android.internal.R.id.content的布局
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
//......
//继续一堆属性设置,完事返回contentParent
// 返回上面获取的contentParent
return contentParent;
}
6、DecorView类的onResourcesLoaded()方法
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
mStackId = getStackId();
// ...
mDecorCaptionView = createDecorCaptionView(inflater);
// 创建view
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 将创建的view添加到DecorView中
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// 将创建的view添加到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
setContentView源码分析总结
可以看出来setContentView整个过程主要是如何把Activity的布局文件或者java的View添加至窗口里,上面的过程可以重点概括为:
1、创建一个DecorView的对象mDecor,该mDecor对象将作为整个应用窗口的根视图。
2、依据Feature等style theme创建不同的窗口修饰布局文件,并且通过findViewById获取Activity布局文件该存放的地方(窗口修饰布局文件中id为content的FrameLayout)。
3、将Activity的布局文件添加至id为content的FrameLayout内。
至此整个setContentView的主要流程就分析完毕
下面我们来做一个示例验证一下
1、AndroidManifest.xml文件
android:theme="@android:style/Theme.Black.NoTitleBar"
2、主界面布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
3、上面我们将主题设置为NoTitleBar,所以在generateLayout方法中的layoutResource变量值为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>
布局中,一般会包含ActionBar,Title,和一个id为content的FrameLayout,这个布局是NoTitle的。
4、我们来看下上面这个demo的hierarchyviewer图谱