「这是我参与2022首次更文挑战的第19天,活动详情查看:2022首次更文挑战」。
在Activity的初始化过程中,经常会用到一个函数setContentView,来设置Activity所需要绘制的layout,今天我们就来分析一下,这个函数的具体是做了什么 首先查看这个函数的代码
Activity.java
public void setContentView(@LayoutRes int layoutResID) {
// 调用getWindow函数的setContentView函数
// getWindow()会返回Activity的全局参数mWindow,从该类的attach函数中可知,这边是一个PhoneWindow对象
getWindow().setContentView(layoutResID);
// 根据Activity的相应条件,确认是否需要初始化一个ActionBar对象
// 若需要,则直接初始化WindowDecorActionBar对象
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
可以看到,在这个setContentView函数(在Activity类中有好几个重载函数,此处我们只需要分析其中的一个即可,剩余的函数均是一样的分析)中,
- 首先会调用getWindow函数的setContentView函数,而getWindow函数主要是返回一个mWindow全局参数,mWindow的初始化在Activity.attach函数中,为一个PhoneWindow对象,而这个函数的调用是当系统初始化Activity的时候,会调用ActivityThread对象的attach函数,从而会调用Activity.attach函数
- 调用initWindowDecorActionBar函数,根据Activity的相应条件,确认是否需要初始化一个ActionBar对象,若需要,则直接初始化WindowDecorActionBar对象 先附上PhoneWindow的类图
classDiagram
Window <|-- PhoneWindow
MenuBuilder *-- Callback
Callback <|-- PhoneWindow
<<interface>> Callback
<<abstract>> Window
class Window {
+setContentView(@LayoutRes int)
+setContentView(View)
+setContentView(View, ViewGroup.LayoutParams)
+addContentView(View, ViewGroup.LayoutParams)
+clearContentView()
+getLayoutInflater()
+getDecorView()
+setCallback(Callback callback)
}
class PhoneWindow {
+setContentView(int layoutResID)
+setContentView(View, ViewGroup.LayoutParams)
+addContentView(View, ViewGroup.LayoutParams)
}
值得注意的是,Window是一个抽象类,PhoneWindow是其唯一的子类,它的实例一般被添加到WindowManager中进行管理。
PhoneWindow的setContentView函数
查看其实现
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函数初始化一个Decor View
installDecor();
}
// ...... 本篇不关注代码省略
{
mLayoutInflater.inflate(layoutResID, mContentParent);
}
// ......
mContentParentExplicitlySet = true;
}
可以看到,在这个函数中,调用installDecor函数来初始化Decor View
installDecor函数初始化DecorView
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 1. 生成和配置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) {
// 2. 初始化mContentParent对象
mContentParent = generateLayout(mDecor);
// ...... 与此篇分析无关代码暂时省略
}
}
可以看到,在这个函数中,首先通过generateDecor函数初始化一个DecorView,此后调用generateLayout来初始化mContentParent
generateDecor函数初始化一个DecorView
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, this);
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
super(context);
mFeatureId = featureId;
mShowInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.linear_out_slow_in);
mHideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.fast_out_linear_in);
mBarEnterExitDuration = context.getResources().getInteger(
R.integer.dock_enter_exit_duration);
mForceWindowDrawsBarBackgrounds = context.getResources().getBoolean(
R.bool.config_forceWindowDrawsStatusBarBackground)
&& context.getApplicationInfo().targetSdkVersion >= N;
mSemiTransparentBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);
updateAvailableWidth();
setWindow(window);
updateLogTag(params);
mResizeShadowSize = context.getResources().getDimensionPixelSize(
R.dimen.resize_shadow_size);
initResizingPaints();
mLegacyNavigationBarBackgroundPaint.setColor(Color.BLACK);
}
此处会初始化一个DecorView对象,从这个对象的类图来看
classDiagram
FrameLayout <|-- DecorView
RootViewSurfaceTaker <|-- DecorView
WindowCallbacks <|-- DecorView
<<interface>> RootViewSurfaceTaker
<<interface>> WindowCallbacks
DecorView继承自FrameLayout
generateLayout来初始化mContentParent
此后调用generateLayout函数来初始化mContentParent参数
protected ViewGroup generateLayout(DecorView decor) {
// ...... 获取和设置Window属性
WindowManager.LayoutParams params = getAttributes();
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// 根据feature来获取当前Activity的对应的DecorView的layout
// ...... 此处我们不做过多的分析,仅获取默认的layout分析,其他情景下的layout可以自行去分析下
{
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// 将上述layout资源对应的View存放到mDecor中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
// 此处获取对应的layout中的id为content对应的ViewGroup对象
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// ......
return contentParent;
}
screen_sample.xml
<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>
从上述代码分析,无论是feature对应的是哪一种layout,其必定需要包含一个id为content的ViewGroup,否则此处会直接抛出异常,最终generateLayout函数返回的是一个id为content对应的ViewGroup对象,而从上述的xml文件中可以看到,此处是一个FrameLayout对象。
总结
同时,在上述的代码中也可以明确地看出,在这个过程中,
- 首先mDecor是一个DecorView对象,其继承自FrameLayout,它是通过generateDecor函数来生成的
- 在generateLayout函数中,通过Activity对象的属性值选取对应的,来选取对应类型的layout,然后将这整个的Layout添加到上述生成的DecorView中,当然这个过程是直接添加还是间接添加由系统来决定
- 初始化mContentParent参数为上述获取的layout中的id为content对应的ViewGroup对象,由此也可知,无论Activity的属性如何,在其对应的layout中必有一个id为content对应的ViewGroup对象
设置用户layout
从上述的代码中可以看到,在调用installDecor函数初始化DecorView和mContentParent参数后,就会通过调用LayoutInflater对象的inflate函数来将用户layout添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
此处的不再做详细分析,待后续有机会仔细分析下该流程的具体实现
时序图
按照惯例,附上上述流程的时序图