1. 当MainActivity直接继承自Activity时
1.1 方法调用流程
-
在
onCreate方法中调用:- 在
MainActivity的onCreate方法中,通常会首先调用setContentView方法来设置该 Activity 要显示的内容视图。例如:
import android.app.Activity; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } - 在
-
传入布局资源引用或视图对象:
-
setContentView方法可以接受两种类型的参数:-
布局资源引用(如上述代码中的
R.layout.activity_main),这是通过 Android 系统自动生成的资源索引来指向一个预先定义好的 XML 布局文件; -
视图对象(比如通过代码动态创建的一个
LinearLayout视图并添加了一些子视图后的对象)。
-
-
1.2 基于布局资源引用执行情况
-
资源索引与布局文件对应:
- 当传入布局资源引用(如
R.layout.activity_main)时,Android 系统会根据这个资源索引找到对应的 XML 布局文件(这里假设是activity_main.xml)。这个 XML 布局文件位于项目的src/main/res/layout文件夹下,它定义了 Activity 的布局结构,包括各种视图组件(如按钮、文本框、列表等)的排列方式、属性设置等。
- 当传入布局资源引用(如
-
布局解析与视图实例化:
- Android 系统会对找到的 XML 布局文件进行解析,将其中定义的各种视图组件按照 XML 中的标签和属性信息进行实例化。例如,如果
activity_main.xml中有一个<TextView>标签定义了一个文本视图,系统会根据这个标签创建一个实际的TextView实例,并根据标签中的属性(如text属性设置文本内容、textColor属性设置文本颜色等)为这个实例设置相应的属性值。
- Android 系统会对找到的 XML 布局文件进行解析,将其中定义的各种视图组件按照 XML 中的标签和属性信息进行实例化。例如,如果
-
添加到窗口视图体系:
- 经过解析和实例化后的所有视图组件会被添加到
PhoneWindows中的mContentParent容器中(mContentParent是PhoneWindows中用于容纳 Activity 主要内容视图的容器)。具体来说,系统会将这些视图组件按照 XML 布局文件中的布局方式(如线性布局、相对布局等)进行排列和调整,然后添加到mContentParent,以便在屏幕上正确显示 Activity 的内容。
- 经过解析和实例化后的所有视图组件会被添加到
1.3 基于视图对象执行情况
-
视图对象创建与准备:
- 如果传入的是视图对象(如通过代码动态创建的视图),那么在调用
setContentView之前,开发者需要先自行创建并准备好这个视图对象。例如,可以创建一个LinearLayout视图对象,并向其添加一些子视图(如TextView、Button等),设置好它们的属性(如文本内容、点击事件监听器等)。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 创建一个线性布局对象 LinearLayout linearLayout = new LinearLayout(this); linearLayout.setOrientation(LinearLayout.VERTICAL); // 创建一个文本视图对象并添加到线性布局中 TextView textView = new TextView(this); textView.setText("这是通过代码设置的视图内容"); linearLayout.addView(textView); setContentView(linearLayout); } } - 如果传入的是视图对象(如通过代码动态创建的视图),那么在调用
-
添加到窗口视图体系:
- 当调用
setContentView方法传入这个准备好的视图对象时,系统会直接将这个视图对象添加到PhoneWindows中的mContentParent容器中,以便在屏幕上显示出这个由开发者自行创建和设置的视图内容。
- 当调用
1.4 源码分析
看一下Activity-setContentView方法:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
内部调用了 getWindows.setContentView(xxx)
Windows表示一个窗口的概念,Android 中不管是Activity,dialog,还是 Toast 它们的视图都是附加在 Windows 上,因此可以称 windows 是View的直接管理者。而Windows有实现类,即PhoneWindows
我们接着去看 PhoneWindows 的 setContentView()
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor(); //关注点
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
}
内部执行了一个判断,然后调用 **installDecor() ** 方法。
我们接着去看 **installDecor()**方法:
//方法虽然很长,但是其实主要就分成两个部分,重点是generateDecor和generateLayout
private void installDecor() {
mForceDecorInstall = false;
//第一个部分开始
//干了两件事情,1创建mDecor 2将mDecor和Window绑定
if (mDecor == null) {
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) {
mContentParent = generateLayout(mDecor);//根据mDecor创建mContentParent
//下面基本就是根据各种FLAG和style设置页面的各种属性了
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
... ...忽略
}
//第二个部分结束
}
mDecor 是
windows唯一视图,也就是我们mContentParent的父视图。简称 DecorViewmContentParent 即放置我们自己布局的容器,你可以理解为,它是我们的根容器
我们接着去看 generateDecor() 方法
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
直接new了一个 DecorView,返回回去看 installDecor() 中的关注点2-generateLayout().
进入 generateLayout() 方法:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
/*
... 根据各种flag和属性设置页面的样式
*/
// Inflate the window decor.
int layoutResource;
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!");
}
//上面一大串,根据各种属性获取到了一个layoutResource,这个layoutResource是系统中已经定义好的,这样就能看出我们平时设置的各种style 和 flag都是怎么作用的了。
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //将layoutResource加载到了mDecor的最底层。
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//获取了id是ID_ANDROID_CONTENT的view
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks(contentParent);
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
final Drawable background;
if (mBackgroundResource != 0) {
background = getContext().getDrawable(mBackgroundResource);
} else {
background = mBackgroundDrawable;
}
mDecor.setWindowBackground(background);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
上述代码简化后
protected ViewGroup generateLayout(DecorView decor) {
//1
TypedArray a = getWindowStyle();
if(xx)else if(xxx)
else {
layoutResource = R.layout.screen_simple;
}
//2
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
..
//3
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
-
显示获取当前主题,然后开始判断究竟要用那个布局
-
将其加载到 DecorView 中
-
通过 findViewById(内部就是DecorView.findViewById)获取 R.id.content,并返回此viewGroup。
等等,这个 R.layout.screen_simple 是什么
就是我们DecorView中加载的布局,具体如下。
如上图所示,我们的布局最终会被添加到这个根布局content中。
1.5 总结
我们接下来将上面的分析整体走一遍:
-
当我们调用Activity的 setContentView 时,内部其实是执行了
PhoneWindows(windows的唯一实例)的 setContenView() -
而
PhoneWindows的 setContentView() 内部会先判断当前有没有布局容器contentParent,也即就是有没有DecorView,如果没有,执行 installDecor() 去初始化我们的DecorView与contentParent -
在 installDecor() 方法里面,会先判断有没有
DecorView,如果没有,先new一个出来,然后判断有没有contentParent(承载我们自己布局的ViewGroup),没有的话,就去根据当前主题,选择一个布局,并将其当做我们的根布局添加到DecorView中,再将其中的子view,即R.id.content这个view赋值给我们的contentParent -
最后 PhoneWindows-setContentView() 方法接下来就可以将我们自己的布局 inflate 进这个根布局的
contentParent里了
2. 当MainActivity继承AppCompatActivity
2.1 前言
在 Android 应用开发的世界里,setContentView 几乎是每个开发者都会接触到的方法。它的作用至关重要——负责将视图(View)或布局(Layout)展示在屏幕上。尽管这看起来是一个简单直接的操作,但其背后实际上隐藏着 Android 系统中复杂而精妙的窗口管理和视图渲染机制。
我们一起梳理并深入探讨 Android 的窗口管理和视图展示原理,希望能够为大家带来新的理解和启发。
2.2 AppCompatActivity
一般而言大家都会如此使用setConentView:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mContentLayoutId);
}
当然你也可以在这么使用:
class TestActivity : AppCompatActivity(R.layout.activity_test)
我们看看AppCompatActivity的setContentView藏着什么:
public void setContentView(@LayoutRes int layoutResID) {
this.initViewTreeOwners();
this.getDelegate().setContentView(layoutResID);
}
可以看到在AppCompatActivity中,使用的getDelegate,这看起来像是委托啊!点下去看看:
public AppCompatDelegate getDelegate() {
if (this.mDelegate == null) {
this.mDelegate = AppCompatDelegate.create(this, this);
}
return this.mDelegate;
}
我们找到了全新的AppCompatDelegate!官方说明:
此类表示一个委托,您可以使用该委托将 AppCompat 的支持 扩展到任何 Activity
只能 Activity 与一个 AppCompatDelegate 实例链接,因此应保留从 create(Activity, AppCompatCallback) 返回的实例,直到 Activity 被销毁。
我们可以在这里看到setContentView
但是AppCompatDelegate是一个抽象类,我们可以很轻松的找到它的实现类AppCompatDelegateImpl。
public void setContentView(int resId) {
this.ensureSubDecor();
ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(16908290);
contentParent.removeAllViews();
LayoutInflater.from(this.mContext).inflate(resId, contentParent);
this.mAppCompatWindowCallback.bypassOnContentChanged(this.mWindow.getCallback());
}
分析ensureSubDecor
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); // ⭐重点主线流程
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true;//⭐这个flag参数
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
mSubDecor是一个ViewGroup类型的对象,等待后面分析createSubDecor()的时候就能见分晓
分析contentParent
可以看到,我们提供的LayoutId,最后会添加到contentParent这个View上
那么mSubDecor是从哪儿来的?看到调用方法的名字,ensure sub decor ,子装饰视图,想必在这里。我们继续往下看:
private void ensureSubDecor() {
if (!this.mSubDecorInstalled) {
this.mSubDecor = this.createSubDecor();
CharSequence title = this.getTitle();
//......略
}
看样子藏在createSubDecor中
分析createSubDecor
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
//**省略 获取当前上下文的主题属性,设置对应的样式
a.recycle();
// 确保窗口已安装其装饰
ensureWindow();
mWindow.getDecorView();
// 获取布局填充器
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 根据是否有标题和是否为浮动窗口来决定使用哪个布局
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);
//**
}
}
//**
// 将窗口的内容视图设置为subDecor
mWindow.setContentView(subDecor);
//**
return subDecor;
}
可以看到这个方法返回一个配置好的 subDecor,subDecor使用的是系统的布局,根据配置的不同,使用了不同的xml。
返回给我们用于添加LayoutId,但是它是如何显示的还是不清楚,但是注意到mWindow.setContentView(subDecor);。
我们点下去一看,发现 来到Window类:
//Window
public abstract void setContentView(View view);
Window的实现类-PhoneWindow
ensureWindow()
private void ensureWindow() {
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
if (mWindow == null && mHost instanceof Activity) {
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
首先我们要明确AppCompatActivity是继承自Activity的,所以window也是在attach方法中创建的,在AppCompatDelegateImpl中也维护了一个Window类型的变量是mWindow,就是通过这个ensureWindow方法经过检查后赋值过来的。
说白了ensureWindow方法就是把AppCompatActivity中的Window对象赋值到AppCompatDelegateImpl对象中,当然对window设置的callBack啥的也换成AppCompatDelegateImpl中的。
2.3 PhoneWindow
我们找到PhoneWindow:
@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;
}
我们看看如何初始化mContentParent,走进installDecor()的内部
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 创建窗口装饰视图
mDecor = generateDecor(-1);
//**
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成并设置内容布局
mContentParent = generateLayout(mDecor);
//**
} else {
//**
}
// 省略涉及到过渡管理器和动画的配置
}
在这里我们可以看到两个,generateDecor,但是在createSubDecor中我们已经创建过了:
protected ViewGroup generateLayout(DecorView decor) {
// ... 省略了一部分属性设置代码 ...
// 根据窗口特性选择布局资源
int layoutResource;
int features = getLocalFeatures();
// 根据不同的特性标志选择不同的布局资源
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) !=
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
if (mIsFloating) {
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
if (mIsFloating) {
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 {
layoutResource = R.layout.screen_simple;
}
// 装饰视图开始变化
mDecor.startChanging();
// 使用LayoutInflater加载布局资源
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 获取内容父视图
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ... 省略了其他设置代码 ...
// 装饰视图完成变化
mDecor.finishChanging();
return contentParent;
}
在这个方法中,会根据不同的特性标志选择不同的布局资源,但是这些布局都有一个显著的特点。他们都有一个id为content的FrameLayout。就是那个常用的android.R.id.content,表示窗口的 内容区域
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" /
2.4 流程总结
至此,我们知道了PhoneWindow的setContentView中的contentParent来自哪里:
那我们也是知道了AppCompatDelegate中的setContentView的contentParent来自哪里:
-
在活动的
onCreate方法中调用setContentView,传入布局资源ID或者直接传入一个视图(View)对象。 -
从
AppCompatDelegate中调用Window.setContentView。 -
PhoneWindow对象负责创建和管理顶层视图容器,DecorView。如果DecorView还未创建,Window会通过调用generateDecor方法来创建它。DecorView中一定有一个ID为android.R.id.content的FrameLayout。 -
AppCompatDelegateImpl将视图添加到android.R.id.content。
2.5 与Activity的setContentView对比
Activity的setContentView 调用PhoneWindow 再调用到decorView里会又一个screen_simple.xml,这个xml有viewStub和fragmentLayout组成,我们的布局就会加载到这个fragment上。
主要区别对比
流程方面的区别:
AppCompatActivity前面流程基本和Activity一致,唯一的区别就是AppCompatActivity会在fragment上再嵌套一层,然后将原来view一个个删掉,然后添加到新的布局上面,然后就会去解析xml布局,LayoutInflater这里进行加载。
代码实现方面的区别:
-
Activity.setContentView直接将视图添加到Window上
-
AppCompatActivity.setContentView()借助AppCompatActivity的Delegate代理类,将要显示的视图加入到代理层视图,代理层视图再添加到Window上;
总结
AppCompatActivity 是 Activity 的增强版,提供更广泛的兼容性和现代特性支持。
Activity 是底层实现,适合轻量、简单的场景,但在功能上不如 AppCompatActivity 丰富。
本系列【安卓基础重点知识】是刚开始学习android的时候记录的,其中部分内容来自网页,忘记记录来源了,如需添加引用,联系我即可