我们常在Activity的onCreate方法中使用setContentView方法来加载布局,接下来我们来深入理解 setContentView?
-> Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
我们上面说了,Window的具体实现是PhoneWindow,那么我们直接看PhoneWindow的setContentView就行了。
-> PhoneWindow.java
@Override
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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// (mContentParent != null) && (!hasFeature(FEATURE_CONTENT_TRANSITIONS)
mContentParent.removeAllViews();
}
// (mContentParent != null) && (hasFeature(FEATURE_CONTENT_TRANSITIONS)
// mContentParent 在 installDecor();中进行了初始化
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;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 设置 DecorView的UI
...
}
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
TypedArray a = getWindowStyle();
/**以下这些是Activity的窗口属性特征(Feature)的设置,下面只选取了几个比较常见的*/
...
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);
}
...
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
}
....
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// 根据不同的features 加载不同的布局
// 这里有多个 if 判断语句
if (...) {
...
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
... // 设置 DecorView 的背景、标题等
mDecor.finishChanging();
return contentParent;
}
===================================================================================
-> DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
// 通过布局添加器LayoutInflater获取layoutResource布局
final View root = inflater.inflate(layoutResource, null);
// 将布局添加到decor容器里面
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();
}
查看 DecorView 就可以知道,它继承自FrameLayout。
以上的代码主要做了这些工作:
- 创建一个DecorView:
new DecorView(context, featureId, this, getAttributes())
- 根据
TypedArray a = getWindowStyle();
的属性,来设置窗口的特征(feature)。比如是否显示标题栏、状态栏颜色等等 - 根据不同的feature,设置不同的layoutResource 。比如:
layoutResource = R.layout.screen_simple;
- 把layoutResource 添加到DecorView 容器中
我们再往回看上面的代码
protected ViewGroup generateLayout(DecorView decor) {
...
layoutResource = R.layout.screen_simple;
// layoutResource 的视图添加到DecorView中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
-> 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>
generateLayout 中的findViewById(ID_ANDROID_CONTENT)
,就是从layoutResource 的布局中查找这个id,它的定义为:public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
正好对应 R.layout.screen_simple中FrameLayout的Id(其实不止是R.layout.screen_simple,generateLayout方法中如果根据窗口特征加载的是其他的layout,那么它们的布局也有一个FrameLayout,FrameLayout的id也为@android:id/content
)。
private void installDecor() {
...
mContentParent = generateLayout(mDecor);
...
}
generateLayout返回的ViewGroup赋值给mContentParent,相当于ID_ANDROID_CONTENT这个id对应的ViewGroup就是mContentParent。
public void setContentView(int layoutResID) {
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
回到最开始的地方:我们常在Activity的onCreate方法中通过setContentView来设置布局,实质上就是把布局文件添加到DecorView的FrameLayout,即mContentParent中
。LayoutInflater的inflate方法并不是本文的重点,详解可以看:从LayoutInflater分析XML布局解析成View的树形结构的过程
我们可以从用一张图来总结上面的过程:
更多DecorView的结构及使用请看:四、DecorView 的结构图
Activity的布局文件被添加到mContentParent之中后,就要通知Activity,使其可以做相应的处理。因为Activity实现了Window的Callback接口,所以回调Activity的onContentChanged方法通知Activity视图发生改变。但是Activity的onContentChanged是空实现,不过我们通常都不是直接使用Activity,而是使用它的子类,Activity的子类对onContentChanged进行了实现。
文章讲到这里,就完成了DecorView的创建和初始化,Activity的布局文件也已经成功添加到DecorView的mContentParent中
,但是这个时候DecorView还没有被WindowManager正式添加到Window中。
在 ActivityThread 中的handleResumeActivity
函数中, 先调用Activity的onResume方法,再调用Activity的makeVisible
。在makeVisible中,DecorView真正地完成了添加和显示这两个过程,到这里Activity的视图才能被用户看到。
-> ActivityThread.java
final void handleResumeActivity(...) {
...
r = performResumeActivity(token, clearHide, reason);
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
a.mDecor = decor;
...
r.activity.makeVisible();
...
}
-> Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
参考:《Android开发艺术探索》