关于Activity需要知道的更多内容
前言
关于Activity的创建以及执行回调onCreate方法我们已经知道了(看这里:https://www.jianshu.com/p/3de730c145be
),然后我们今天想知道我们在onCreate中调用setContentView方法的时候,xml是如何加载的,所以今天就跟着源码来看看。
ps:源码版本为 android-26
一般来说我们写一个XxxActivity都会去继承Activity、FragmentActivity、AppCompatActivity这3个的其中一个,我们今天就分别对这几个父类Activity去分析
- 1、Activity的xml加载过程
- 2、FragmentActivity的xml加载过程
- 3、AppCompatActivity的xml加载过程
一、Activity的xml加载过程
进入我们setContentView方法
## Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//1、调用phoneWindow的setContentView方法
initWindowDecorActionBar();
}
这边注释的地方getWindow方法获取的一个window对象,它的实现类是PhoneWindow,它在Activity的attach中进行创建出来。
## Activity
final void attach(Context context, ...,
Window window, ActivityConfigCallback activityConfigCallback) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);//这边进行window对象的初始化
...
mWindow.setColorMode(info.colorMode);
}
我们跟进PhoneWindow,看看它里面的setContentView方法
##PhoneWindow
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();//1、加载DecorView
} 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);//2、布局加载器
}
...
}
注释1是加载DecorView,注释2通过布局加载器将xml解析成view树,并且将view树添加到mContentParent中
1、加载DecorView
### PhoneWindow
/**
* 加载DecorView
*/
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);//1、创建DecorView
...
} else {
mDecor.setWindow(this);//2、DecorView绑定到Window
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//3、根据DecorView生成Layout,得到一个mContentParent
...
}
}
/*
* 1、创建DecorView
*/
protected DecorView generateDecor(int featureId) {
... //这边去创建DecorView
return new DecorView(context, featureId, this, getAttributes());
}
/*
*2、调用DecorView的setWindow,传入PhoneWindow对象
*/
void setWindow(PhoneWindow phoneWindow) {
mWindow = phoneWindow;
...
}
/*
*3、根据DecorView生成Layout
*/
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
//1、获取当前的主题,然后设置相关数据到window上
TypedArray a = getWindowStyle();
...
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);//2、当前Window是否浮动在上面,默认为false
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);//3、传入window的宽、高
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {//4、window是否有title
requestFeature(FEATURE_NO_TITLE);//5、请求指定Window的风格,它必须在setContentView之前
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
...
//6、这边会很多的设置窗口属性的判断和方法,具体可以大家可以去看源码
...
// Inflate the window decor.
//7、根据上面设置的窗口的属性 ,设置相应的 layoutResource
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();//8、开始DecorView的变化
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);//9、根据布局文件xml加载得到View树
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//10、根据content id返回一个contentParent,是一个viewGroup
mDecor.finishChanging(); //11、结束DecorView的变化
return contentParent; //12、返回一个contentParent
}
在上面这段源码中,我们主要是做了几件事:
- 1、注释1这里的样式通常是用来描述窗口的,通常view的样式是通过layout来描述的,而窗口的样式是通过Androidmanifest.xml来配置的,所以我们这里通过调用getWindowStyle方法,然后得到一个TypedArray对象,这个类我们比较熟悉,在我们自定义View的时候,如果需要自定义style,我们都会在初始化的时候得到一个TypedArray对象,然后去设置相关的样式。
- 2、注释2返回一个boolean值,表示我们的window是否悬浮在上面,然后根据这个值去设置相关的window宽高和其他的标签,注释4和注释6的地方判断window是否有title或者是否没有ActionBar,然后通过注释5的地方调用requestFeature去设置相关属性,所以我们这边会是否能联想到我们去如果在Activity的onCreat方法里去设置全屏的时候,需要在setConentView之前去设置,因为在setContentView之后的话,installDecor方法就已经执行完毕了,那我们设置的window属性就没有作用了。
-
3、注释7的地方根据上面设置的窗口的属性,来设置相关的layoutResource,我们可以去源码中查找相应的layout,具体位置在:frameworks/base/core/res/res/layout下
image.png
##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>
##screen_simple_overlay_action_mode
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<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" />
<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>
我们能够发现这几个layout里面都包含了一个id为content的FrameLayout,它是用来添加我们自己的布局。
- 4、注释9根据我们上面得到的布局文件加载onResourcesLoaded得到view树
##DecorView
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
//创建一个DecorCaptionView,它是Decor的标题View, 它包含了标题和窗口控件按钮,它的可见性取决于工作空间和窗口类型
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);//1、用LayoutInflate来加载xml布局文件得到view
//2、 若不 null 则先添加 mDecorCaptionView, 再向 mDecorCaptionView 中添加 root
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
//将 mDecorCaptionView 添加到DecorView
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//获取的到root add到 mDecorCaptionView 中
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// 3、若mDecorCaptionView为 null, 则直接添加调用addView将 root 加到 DecorView 中
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 4、强转成 ViewGroup, 传递给 mContentRoot
mContentRoot = (ViewGroup) root;
initializeElevation();
}
private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
...
//这里将源码稍微变动下,直接调用inflateDecorCaptionView方法
DecorCaptionView decorCaptionView = inflateDecorCaptionView(inflater)
...
return decorCaptionView;
}
private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
final Context context = getContext();
inflater = inflater.from(context);
//这边通过LayoutInflater来获到DecorCaptionView
final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
null);
setDecorCaptionShade(context, view);
return view;
}
可以看到在onResourcesLoaded这里是我们做了4件事:
- 4.1、注释1是根据LayoutInflate加载xml布局文件得到View对象 root
- 4.2、注释2判断DecorCaptionView是否为null,不为null,先去添加DecorCaptionView,再去添加root(DecorCaptionView是一个标题view,这个类表示用于控制自由格式上窗口的特殊屏幕元素)
- 4.3、注释3如果DecorCaptionView为null,就直接将root添加到DecorView中
- 4.4、注释4是将root强转成一个ViewGroup并传递给mContentRoot
- 5、在注释10这里根据我们的ID_ANDROID_CONTENT通过findViewById返回一个contentParent,它是我们在步骤3中的DecorView中mContentRoot中的FrameLayout的Id,也就是我们通过setContentView将布局添加进去的地方。
##Window
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content
2、布局加载器加载xml生成View树
通过LayoutInflater布局加载器去生成View树
##LayoutInflater
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
final XmlResourceParser parser = res.getLayout(resource);//1、根据xml id 得到一个xml解析器资源
try {
return inflate(parser, root, attachToRoot);//2、传入xml解析器资源和mContentParent
} finally {
parser.close();
}
}
- 1、注释1得到一个xml解析器资源
- 2、注释3 调用inflate返回view
##LayoutInflater
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
View result = root;//1、它就是我们最终返回的view对象
try {
// Look for the root node.
int type;
...
final String name = parser.getName();//XmlPullParser解析xml,获取里面的标签name
if (TAG_MERGE.equals(name)) {//这里获取的name如果是merge,则root不能为null,否则会抛出异常
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//2、这边创建得到一个临时的view对象 temp
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
...
if (root != null && attachToRoot) {//3、这边判断如果root不为null,则将临时的view对象temp添加到root中
root.addView(temp, params);
}
if (root == null || !attachToRoot) {//4、如果root为null,将temp传递给我们的最终view
result = temp;
}
}
}
...
return result;
}
}
这里通过一系列的判断和传递最终会得到一个View对象,它就是我们根据xml去解析加载出来的View树。
附加一张window和contentView的层次结构图
二、AppCompatActivity的xml加载
跟进AppCompatActivity的setContentView方法
##AppCompatActivity
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);//调用getDelegate的setContentView
}
这边的getDelegate返回的是一个AppCompatDelegate,它是一个抽象类,这边我们跟踪发现最后是在AppCompatDelegateImplV9中调用了setContentView方法
##AppCompatDelegateImplV9
@Override
public void setContentView(int resId) {
ensureSubDecor();//1、创建DecorView
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//2、这边根据content id去返回一个contentParent
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//3、通过LayoutInflater布局加载器将xml解析得到View树,添加到contentParent中
mOriginalWindowCallback.onContentChanged();
}
1、注释1创建DecorView
##AppCompatDelegateImplV9
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();//调用createSubDecor方法
...
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//获取当前的主题,然后设置相关数据到window上
...
mWindow.getDecorView();//1、调用PhoneWindow的getDecorView方法
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;//定义一个viewgroup
//2、根据window的样式不同去加载不同的layout
if (!mWindowNoTitle) {
if (mIsFloating) {
...
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
...
} else if (mHasActionBar) {
...
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
...
}
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
...
}
...
//3、下面具体分析
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
while (windowContentView.getChildCount() > 0) {
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// Change our content FrameLayout to use the android.R.id.content id.
// Useful for fragments.
windowContentView.setId(View.NO_ID);
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
//4、调用PhoneWindow的setContentView方法
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
...
return subDecor;
}
在ensureSubDecor调用了createSubDecor方法:
- 1、注释1调用了PhoneWindow的getDecorView方法,我们可以看到这里去调用了installDecor方法,这个方法我们在本篇的Activity的xml加载过程已经分析过了。
##PhoneWindow
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
- 2、注释2根据不同的window主题样式去加载不同的layout,然后通过布局加载器将xml解析成subDecor,每一个layout中都会include一个名为abc_screen_content_include.xml的layout,这个layout包含id为action_bar_activity_content的ContentFrameLayout。
源码位置:/frameworks/support/v7/appcompat/res/layout/abc_dialog_title_material.xml
##abc_screen_simple_overlay_action_mode
<android.support.v7.widget.FitWindowsFrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="@layout/abc_screen_content_include" />
<android.support.v7.widget.ViewStubCompat
android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/abc_action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.FitWindowsFrameLayout>
##abc_screen_content_include
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<android.support.v7.widget.ContentFrameLayout
android:id="@id/action_bar_activity_content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</merge>
3、在注释3调用调用findViewById找到id为action_bar_activity_content的contentView,然后在通过PhoneWindow找到id为content的windowContentView,然后去循环遍历windowContentView,将它的的子View添加到contentView中去,然后在把windowContentView的id设置为-1,contentView的id设置为原来windowContentView的id content,这个地方可以简单的理解为就是将DecorView(windowContentView)中的view转移到subDecor(contentView)中。
4、注释4调用PhoneWindow的setContentView方法,将subDecor传递进去,然后将subDecor存放到mContentParent中,这边我们可以发现它和上一部分(Activity)的区别,在Activity中mContentParent是直接用来存放我们自定义xml布局的,在AppCompatActivity中,mContentParent会先存放一个subDecor,然后在subDecor才是真正存放我们的自定义xml布局。
##PhoneWindow
@Override
public void setContentView(View view) {
setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
一张层次结构图带你理解:
三、FragmentActivity的xml加载
关于FragmentActivity,我们可以发现它自己是没有实现setContentView的,它是直接调用了父类Activity的setContentView方法,所以我们可以看上面的Activity的xml加载过程就可以了。
但是为什么会有FragmentActivity以及它的作用呢,这就要从Fragment说起来,在Android3.0以前是没有Fragment的,为了能让3.0以前能使用Fragment我们需要引入了support包,然后去继承FragmentActivity就可以使用Fragment,在3.0以后我们可以选择直接继承Activity,就可以正常使用Fragment了。而且3.0以前获取FragmentManager的方法是getSupportFragmentManager,3.0以后直接用getFragmentManager就可以了。
关于Fragment和FragmentManager我们会在另外的文字里进行分析,这里就不多讲。
ps:关于FragmentActivity的api原文
FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity.
沿着别人走过的路,跟上去发现不一样的风景
参考链接: