前言
在上篇文章中,知道了Activity的onCreate方法的调用,那么这边文章,来看看setContentView是怎么把layout布局文件加载的。
以MainActivity为例,一般会让它继承Activity、FragmentActivity、AppCompatActivity。其中AppCompatActivity,从Android 21之后引入Material Design的设计方式,为了支持Material Color 、调色板、toolbar等各种新特性,而产生的。
本章以继承Activity和AppCompatActivity为主,进行布局文件的加载。至于FragmentActivity,感兴趣的小伙伴可以自己去研究一下。
文中会出现大量的View和ViewGroup,可能会有小伙伴有点难以区分,为了方便小伙伴们的理解,先补充下View和ViewGroup相关知识。
这里稍微解释下View是所有可视化控件的父类,ViewGroup是View类的子类,可以拥有子控件,可以看作是一个存放View的容器。
Activity中setContentView方法
##Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //1
initWindowDecorActionBar();
}
注释1中,getWindow()获取到的是Window的子类PhoneWindow,PhoneWindow对象在Activity的attach()方法中被创建。可以发现布局文件的加载实际是交给PhoneWindow来处理。
##PhoneWindow
public void setContentView(int layoutResID) {
...
if (mContentParent == null) {
installDecor(); //1
} 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(ViewGroup)布局容器中。
##PhoneWindow
private void installDecor() {
...
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
//若不为null, 则直接与当前的 Window 对象绑定
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// 获取AndroidManifest.xml中指定的themes主题
TypedArray a = getWindowStyle(); //1
...
//设置当前窗口是否有标题
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
//请求指定Activity窗口的风格类型
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);
}
//设置是否添加ActionBar
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
requestFeature(FEATURE_SWIPE_TO_DISMISS);
}
//设置窗口是否全屏
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
false)) {
setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
& (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowOverscan, false)) {
setFlags(FLAG_LAYOUT_IN_OVERSCAN, FLAG_LAYOUT_IN_OVERSCAN&(~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB)) {
setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
}
a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
+ ", major: " + mMinWidthMajor.coerceToString());
if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMajor,
mFixedWidthMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMinor,
mFixedWidthMinor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMajor,
mFixedHeightMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMinor,
mFixedHeightMinor);
}
if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
requestFeature(FEATURE_CONTENT_TRANSITIONS);
}
if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
//是否透明
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
...
//设置StatusBar颜色
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
0x00000000);
}
...
//根据上面设置的窗口属性Features, 设置相应的修饰布局文件layoutResource
int layoutResource; //2
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
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;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
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;
}
// System.out.println("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;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //3
//获取布局中id为content的View的ViewGroup对象
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); //4
...
return contentParent;
}
1.注释1 获取主题样式属性
这里的样式属性是用来描述窗口,而窗口实质上也是View。通常控件的属性是在layout中设置,而窗口的属性是在AndroidManifest.xml中配置的,通过getWindowStyle()获取当前Window在theme中定义的属性,window支持的属性可以参考\frameworks\base\core\res\res\values\attrs.xml 中的<declare-styleable name="Window"> 。获取到属性值之后有与大堆代码是调用setFlags()和requestFeature()给当前window设置属性值,这就是为什么一般在Activity的onCreate()中设置全屏等属性需要在setContentView()之前设置,因为setContentView()之后installDecor()方法已经执行完毕,所以设置是没有效的。
2.注释2 设置布局文件
##screen_simple.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>
##screen_simple_overlay_action_mode.xml
<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>
根据上面设置的窗口属性Features, 设置相应的修饰布局文件layoutResource,这些xml文件位于frameworks/base/core/res/res/layout下。无一例外, 无论看几个其中都有一个id为content 的 FrameLayout。用于添加自己的布局。
3.注释3 将注释2中的布局文件解析为View树
##DecorView *****注释3
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
//Decor的标题View是个DecorCaptionView, 用于控制自由格式窗口的特殊屏幕元素环境
mDecorCaptionView = createDecorCaptionView(inflater); //3-1
//通过 LayoutInflate 来创建这个layoutResource 的 View 实例对象 root
final View root = inflater.inflate(layoutResource, null); //3-2
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
// 将 mDecorCaptionView 添加进 DecorView
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
// 将从系统的资源文件获取的 root 加到这个 mDecorCaptionView 中
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
//将 root 加到 DecorView 中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
//强转成 ViewGroup, 传递给 mContentRoot
mContentRoot = (ViewGroup) root; //3-3
initializeElevation();
}
##DecorView *****注释3-1
private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
DecorCaptionView decorCaptionView = null;
...
decorCaptionView = inflateDecorCaptionView(inflater); //3-1-1
...
return decorCaptionView;
}
##DecorView *****注释3-1-1
private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
final Context context = getContext();
// We make a copy of the inflater, so it has the right context associated with it.
inflater = inflater.from(context);
final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
null);
setDecorCaptionShade(context, view);
return view;
}
##decor_caption.xml *****注释3-1-1
<com.android.internal.widget.DecorCaptionView
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:descendantFocusability="beforeDescendants" >
<LinearLayout
android:id="@+id/caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:background="@drawable/decor_caption_title"
android:focusable="false"
android:descendantFocusability="blocksDescendants" >
<Button
android:id="@+id/maximize_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/maximize_button_text"
android:background="@drawable/decor_maximize_button_dark" />
<Button
android:id="@+id/close_window"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_margin="5dp"
android:padding="4dp"
android:layout_gravity="center_vertical|end"
android:contentDescription="@string/close_button_text"
android:background="@drawable/decor_close_button_dark" />
</LinearLayout>
</com.android.internal.widget.DecorCaptionView>
将注释2选定的布局文件inflate为View树,这也是整个窗口的内容(包括title、content等等),将整个窗口内容添加到根DecorView中,将整个窗口内容赋值给mContentRoot(ViewGroup)。
4.注释4 返回ViewGroup对象,用于添加自己的布局
##Window
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
这个ID_ANDROID_CONTENT即DecorView中mContentRoot中的FrameLayout的Id,
也就是setContentView方法布局文件最终加载进去的地方。
方便小伙伴们理解,补充一张图。
mContentParent、mContentRoot、及DecorView都可以理解成一个ViewGroup对象。
AppCompatActivity中setContentView方法
##AppCompatActivity
public void setContentView(@LayoutRes int layoutResID) {
this.getDelegate().setContentView(layoutResID);
}
this.getDelegate()获取到AppCompatDelegate对象,这里实际上是获取到AppCompatDelegate的子类AppCompatDelegateImpl,跟踪getDelegate()方法,可以看到。而setContentView()方法,是在AppCompatDelegateImpl具体的实现。
##AppCompatDelegateImpl
public void setContentView(int resId) {
this.ensureSubDecor(); //1
//获取到id为16908290的容器
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
//通过布局加载器解析自己写的xml布局文件转换为View树,并把解析出来的View树添加到contentParent(ViewGroup)布局容器中
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mOriginalWindowCallback.onContentChanged();
}
1.注释1创建DecorView和ViewGroup对象。
##AppCompatDelegateImpl
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor(); //1
//设置Title
CharSequence title = this.getTitle();
if (!TextUtils.isEmpty(title)) {
if (this.mDecorContentParent != null) {
this.mDecorContentParent.setWindowTitle(title);
} else if (this.peekSupportActionBar() != null) {
this.peekSupportActionBar().setWindowTitle(title);
} else if (this.mTitleView != null) {
this.mTitleView.setText(title);
}
}
...
}
}
##AppCompatDelegateImpl
private ViewGroup createSubDecor() {
...
this.mWindow.getDecorView(); //1-1
LayoutInflater inflater = LayoutInflater.from(this.mContext);
//根据不同的主题属性特性,加载ViewGroup
ViewGroup subDecor = null; //1-2
if (!this.mWindowNoTitle) { //3根据主题特性,加载不同的布局
if (this.mIsFloating) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_dialog_title_material, (ViewGroup)null);
this.mHasActionBar = this.mOverlayActionBar = false;
} else if (this.mHasActionBar) {
...
subDecor = (ViewGroup)LayoutInflater.from((Context)themedContext).inflate(layout.abc_screen_toolbar, (ViewGroup)null);
...
}
} else {
if (this.mOverlayActionMode) {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple_overlay_action_mode, (ViewGroup)null);
} else {
subDecor = (ViewGroup)inflater.inflate(layout.abc_screen_simple, (ViewGroup)null);
}
...
}
if (subDecor == null) {
throw new IllegalArgumentException("AppCompat does not support the current theme features: { windowActionBar: " + this.mHasActionBar + ", windowActionBarOverlay: " + this.mOverlayActionBar + ", android:windowIsFloating: " + this.mIsFloating + ", windowActionModeOverlay: " + this.mOverlayActionMode + ", windowNoTitle: " + this.mWindowNoTitle + " }");
} else {
if (this.mDecorContentParent == null) {
this.mTitleView = (TextView)subDecor.findViewById(id.title);
}
//1-3
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
ContentFrameLayout contentView = (ContentFrameLayout)subDecor.findViewById(id.action_bar_activity_content);
ViewGroup windowContentView = (ViewGroup)this.mWindow.findViewById(16908290);
if (windowContentView != null) {
while(windowContentView.getChildCount() > 0) {
View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(-1);
contentView.setId(16908290);
if (windowContentView instanceof FrameLayout) {
((FrameLayout)windowContentView).setForeground((Drawable)null);
}
}
this.mWindow.setContentView(subDecor); //1-4
...
return subDecor;
}
}
}
1.注释1-1
##PhoneWindow
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
getDecorView方法中先去判断DecorView有没有创建,没有创建其实都是调用installDecor方法(上面有叙述,这里就不在讲解)。
2.注释1-2
##abc_dialog_title_material.xml
<android.support.v7.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<TextView
android:id="@+id/title"
style="?android:attr/windowTitleStyle"
android:singleLine="true"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:paddingLeft="?attr/dialogPreferredPadding"
android:paddingRight="?attr/dialogPreferredPadding"
android:paddingTop="@dimen/abc_dialog_padding_top_material"/>
<include
layout="@layout/abc_screen_content_include"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</android.support.v7.widget.FitWindowsLinearLayout>
##abc_screen_content_include.xml
<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>
根据设置的主题属性, 设置相应的布局文件,通过解析器的解析,强转成一个subDecor(ViewGroup)。每一个布局文件中都会有一个include标签,存在一个id为action_bar_activity_content的ContentFrameLayout。
3.注释1-3
判断DecorView是否有存在id=16908290的ViewGroup,16908290这个数值,它是系统R文件生成的,指的是android.R.id.content(上半章节screen_simple.xml中FrameLayout布局),就把所有一个容器(16908290)中所有的子View移动到注释2中的ContentFrameLayout,先把screen_simple.xml中FrameLayoutid改为-1,再把ContentFrameLayout的id修改为16908290,简单点理解就是把DecorView存放自己布局的容器,转移到subDecor(ViewGroup)容器中。
4.注释1-4 subDecor(ViewGroup)添加到DecorView
##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) {
// 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.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
view.setLayoutParams(params);
final Scene newScene = new Scene(mContentParent, view);
transitionTo(newScene);
} else {
mContentParent.addView(view, params); //1-4-1
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
注释1-4-1mContentParent是DecorView中的容器。在上半章节中,mContentParent的作用是存放自己的xml布局,而在这里mContentParent是存放subDecor(ViewGroup),那么subDecor(ViewGroup)巧妙的就变成存放自己的xml布局的容器。
方便小伙伴们理解,补充一张图。
备注:文中Android源码版本9.0
作者:Alan
原创博客,请注明转载处....