01 - [开胃菜] - Activity.setContentView 涉及到的类及相关概念
02 - [正菜] - Activity.setContentView流程
03 - [甜汤] - AppCompatActivity.setContentView 流程
04 - [完结] - setContentView 流程总结
2. 正菜 - Activity.setContentView
目前我们使用的所有 Activity 默认都继承自 AppCompatActivity, AppCompatActivity 中做了什么, 我们先不用去了解, 因为后面会说到.
AppCompatActivity 基类 仍然是 Activity, 所以我们先从 Activity 下手.
下面开始跟着我一步一步的分析.
2.1 Activity
我们直接进入 Activity.java 中, 搜索 setContentView 找到参数为 int 类型的.
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
注释翻译: 通过资源文件设置 Activity 内容, 把所有顶级 View 添加到 Activity 中
( 顶级 View 的概念, 在 01 - 开胃菜中, 已经说过 )
在第6行调用了 getWindow().setContentView(layoutResID);
并传入了资源 ID.
先跳转到 getWindow()
看一下.
@UnsupportedAppUsage
private Window mWindow;
/**
* Retrieve the current {@link android.view.Window} for the activity.
* This can be used to directly access parts of the Window API that
* are not available through Activity/Screen.
*
* @return Window The current window, or null if the activity is not
* visual.
*/
public Window getWindow() {
return mWindow;
}
注释翻译: 得到当前的 Activity , 可以用来直接访问部分无法通过 Activiy/Screen 访问的 Window API
在 01 中已经说过, Window 是一个抽象基类, 提供了顶级窗口的外观和行为策略. 有一个唯一的实现类就是 PhoneWindow
. 那么上面的调用 getWindow().setContentView(layoutResID);
其实就是实现类调用的. 我们进入继续跳转到 PhneWindow
的 setContentView(layoutResID)
中
2.2 PhonwWindow
phoneWindow的 setContentView. PhoneWindow: 423行
ViewGroup mContentParent;
@Override
public void setContentView(int layoutResID) {
//1
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//2
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;
}
mContentParent 是一个放置窗口内容的视图, 它不是 mDecor 本身, 就是一个mDecor 的子View
这个方法其实我们需要关注的地方就两个部分,
-
判断
mContentParent
是否为 null-
为 null 就执行初始化逻辑,
-
不为 null 就移除所有容器内的View.
-
-
是否使用了过度动画
-
没有就直接把我们传入的资源 ID 加载到 父容器
mContentParent
中 -
有就调用
transitionTo(Scene scene)
-
transitionTo(Scene scene)
最后也是将我们传入的资源ID, 加载到mContentParent
中, 此处不在表述.
-
-
先来看初始化逻辑 installDecor()
PhoneWindow.java 2681 行
private void installDecor() {
...
if (mDecor == null) {
//初始化 DecorView
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
//传入 PhoneWindow 本身
mDecor.setWindow(this);
}
if (mContentParent == null) {
//初始化 mContentParent
mContentParent = generateLayout(mDecor);
...
...
}
}
有同学会问了, 这不是初始化 mContentParent
的吗, 为什么要先初始化了一个叫 mDecor
的 ? 别着急, 慢慢往下看.
2.2.1 PhoneWindow 初始化 DecorView.
DecorView 是Window的顶层View,包含了Window的装饰。它继承了 FrameLayout.
( 顶层View 与 顶层Window 的关系在 01 中已经说过. )
PhoneWindow.java 2315行
进入到 generateDecor(int featureId)
中.
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
我们发现, 这个方法就是创了一个 DecorView
对象并返回. ( 注意第三个参数, 传入的也是 PhoneWindow 本身. ) , 在 DecorView
的构造方法内, 也调用了setWindow(window);
进入到 DecorView
中会发现很多从我们传入的Window
中来获取属性来初始化 DecorView
的属性. 01 - 开胃菜中所说的两者之间的关系, 在这里就能充分的说明了. (DecorView
包含Window
的装饰,例如,大小, 是否透明等属性).
到这里 mDecor
就初始化完毕了, 继续回到installDecor()
中开始看 generateLayout(DecorView decor)
方法怎么初始化 mContentParent
2.2.2 PhoneWindow 初始化 mContentParent.
PhoneWindow.java 2336 行
这个方法内有太多太多内容, 但是有很多是我们现在不需要关注的, 我都以 ... 代替了, 保留了一些我们现在需要关注的代码.
修改后如下所示
protected ViewGroup generateLayout(DecorView decor) {
//-------------------------------1----------------------------
//获取window属性
TypedArray a = getWindowStyle();
...
//是不是浮动窗口.
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
...
if (mIsFloating) {
...
} else {
...
}
//是不是设置了notitle
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
...
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
...
}
...
//是不是设置了透明
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
...
//-------------------------------2----------------------------
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
if (...) {
layoutResource = R.layout.screen_swipe_dismiss;
...
} else if (...) {
if (mIsFloating) {
...
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
...
} else if (...) {
layoutResource = R.layout.screen_progress;
} else if (...) {
if (mIsFloating) {
...
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
...
} else if (...) {
if (mIsFloating) {
...
layoutResource = res.resourceId;
} else if (...) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if (...) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
//2.1
layoutResource = R.layout.screen_simple;
}
//-------------------------------3----------------------------
...
//3.1将布局文件加载到 DecorView 中.
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//3.2
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
我将这个方法大致分为三个步骤
- 获取
Window
的属性, 然后赋值给PhoneWindow
的成员变量. - 根据
features
的值去加载不同的布局文件, (features
也是根据Window
设置的属性获取到的值) , 然后把布局文件ID 赋值给变量layoutResource
- 将
layoutResource
加载到mDecor
中. 最后根据指定的ViewID
获取mDecor
中的View. 并返回.
重点代码也是在第三步,
我们先看 3.1 onResourcesLoaded(mLayoutInflater, layoutResource)
DecorView.java 2074行
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
...
} else {
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
省去无用代码, 在第3行, 看到了熟悉的 inflater.inflate
, 加载传入的 layoutResource
布局. 在第7行, 把这个 View 添加到 DecorView 中去.
那么加载的这个View 到底是什么样的呢, 我们在generateLayout()
中随便找一个最简单的布局文件来看. 2.1
//2.1
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>
FrameLayout 的这个ID , content
需要记住, 因为下面就会说到这个ID.
接着看generateLayout()
中的3.2.
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
ID_ANDROID_CONTENT
这个ID 是什么 ? 跟进去看一下
Window.java 257行
/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
官方注释翻译
这个 ID 是主布局 xml 文件中应该具有的.
这个 ID 就是刚才添加到 DecorView
布局中 FrameLayout
的 ID , 等于说是 mContentParent
是 DecorView
中的一个ViewGroup
, 是一个FrameLayout
, 最后返回这个 ViewGroup
我们可以猜想, 根据 Window 不同属性加载的不同布局中, 应该都会有这样一个ViewGroup, 并且ID 为 com.android.internal.R.id.content;
总结
现在来梳理一下整体流程.
-
Activity
调用setContentView .
- 执行
PhonwWindow.setContentView
-
PhonwWindow.setContentView
中调用installDecor
初始化DecorView
(installDecor
中调用generateDecor()
) - 初始化
mContentParent
(installDecor
调用generateLayout()
)- 在初始化
mContentParent
的同时, 又根据Window
不同属性加载不同的布局, 然后添加到DecorView
中. - 最后获取
DecorView
中的一个ViewGroup
作为mContentParent
并返回.
- 在初始化
- 在
PhoneWindow.setContentView
中, 调用mLayoutInflater.inflate(layoutResID, mContentParent);
把我们要设置的布局文件ID, 加载到mContentParent
中. - 流程结束
用图形来表示一下他们之间的相互关系
相互之间的关系
网友评论