启动一个Activity
performLaunchActivity()
ActivityThread.performLaunchActivity() 方法是 Android 系统中负责启动一个 Activity 的关键方法。
当调用startActivity()
方法启动一个 Activity 时,ActivityThread
对象会接收到该请求,并调用 performLaunchActivity()
方法。
在 performLaunchActivity() 方法内部,首先会创建一个新的 ActivityRecord 对象
,用于表示将要启动的 Activity。
接下来,会调用 activity.attach()
方法,该方法用于将 Activity 对象与 ActivityRecord 关联起来,并初始化
相应的属性。
在 activity.attach() 方法中,首先会创建一个PhoneWindow
对象。PhoneWindow 是与 Activity 关联的窗口,提供了窗口管理的功能。
然后,会调用mInstrumentation.callActivityOnCreate()
方法来触发 Activity 的生命周期回调方法 onCreate()
。
mInstrumentation
是用于进行 Activity 生命周期调度和事件派发的工具类。它可以调用各种生命周期回调方法,如 onCreate()、onStart()、onResume() 等。
callActivityOnCreate()
方法会在主线程中调用 Activity 的 onCreate() 方法,以便执行一些初始化操作。
onCreate()
是一个重要的生命周期方法,用于在 Activity 创建时进行必要的初始化,如设置布局、获取传递的数据等。
PhoneWindow
PhoneWindow 类在 Android 中主要负责显示界面的窗口管理,它会创建以下几种类型的窗口
Activity:
当你启动一个 Activity 时,系统会为该 Activity 创建一个关联的 PhoneWindow 对象。这个 PhoneWindow 对象负责承载 Activity 的布局和界面显示。
Dialog(对话框):
Dialog 类是以对话框形式展示的窗口,它也依赖于 PhoneWindow。当你创建一个对话框时,系统会为其创建一个独立的 PhoneWindow 对象,用于承载对话框的布局和内容。
PopupWindow(弹出窗口):
PopupWindow 是一种浮动在其他视图之上的窗口,也需要 PhoneWindow 进行创建和管理。当你创建一个 PopupWindow 实例时,系统会为其自动创建一个 PhoneWindow 对象。
Toast(消息提示):
Toast 的创建不直接依赖于 PhoneWindow,但在显示 Toast 消息时,会通过 PhoneWindow 创建一个临时的窗口用于显示内容。
当创建的Activity继承Activity时,调用setContentView
PhoneWindow.setContentView():
当在 Activity 中调用 setContentView(layoutResID)
方法时,会触发 PhoneWindow 的 setContentView()
方法。其主要目的是创建一个 DecorView
并获取到内容视图。
installDecor():
installDecor() 方法是 PhoneWindow 中的一个私有方法,它负责创建 DecorView 并获取到 mContentParent(内容视图的父容器)
。
generateDecor(-1):
在 installDecor() 方法中,会调用 generateDecor()
方法来生成一个 DecorView
对象,并将其存储在 mDecor 成员变量中。这个 DecorView 是 PhoneWindow 中承载界面内容的根视图。
generateLayout(mDecor):
generateLayout() 方法是用于生成布局的辅助方法,通过传入的 DecorView 返回一个 ViewGroup
对象作为内容视图的父容器,并将其存储在mContentParent
成员变量中。
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource):
在 generateLayout() 方法中,会调用 mDecor 的 onResourcesLoaded()
方法,该方法用于处理资源加载完成后的相关操作。
mLayoutInflater.inflate(layoutResID, mContentParent):
最后,在 PhoneWindow 中调用 mLayoutInflater.inflate(layoutResID, mContentParent)
方法,将指定的布局资源(例如 R.layout.activity_main)填充到 mContentParent
中,从而将布局渲染到界面上。
当创建的Activity继承AppCompatActivity时,调用setContentView
继承 AppCompatActivity 的流程相比于继承 Activity 会多出一些处理与兼容性相关的步骤。
AppCompatDelegate.setContentView():
当你在 AppCompatActivity 中调用 setContentView(layoutResID)
方法时,会触发 AppCompatDelegate
的 setContentView()
方法。它负责确保正确的子布局(subDecor)被创建并设置为内容视图
。
ensureSubDecor():
ensureSubDecor()
方法被调用用来确保子布局 subDecor 被正确创建
。它会调用 createSubDecor()
方法来生成 subDecor 对象。
createSubDecor():
createSubDecor()
方法主要负责创建 subDecor 对象
,即承载界面内容的根视图
。在这个方法中,会调用 ensureWindow()
来确保关联的PhoneWindow
被正确创建,然后通过 mWindow.getDecorView()
获取到 PhoneWindow 的根视图。
复制内容视图:
在createSubDecor()
方法中,会将原始的内容视图(android.R.id.content)的子视图
复制到一个新的容器视图(R.id.action_bar_activity_content)
中,并将原始的内容视图的 id 设置为 NO_ID
,将新容器视图的 id 设置为 android.R.id.content
。
设置 subDecor:
最后,通过mWindow.setContentView(subDecor)
方法将 subDecor 设置为 PhoneWindow 的内容视图
。
布局创建与解析:
在创建布局的过程中,会使用LayoutInflater
解析指定的布局资源,并通过反射创建
相应的 View 对象。如果布局标签名包含了包名,则直接通过反射创建对应的 View 对象
;否则,会通过 onCreateView()
方法来创建 View 对象。在创建完毕后,将会进行子视图的创建解析,最终得到整个布局的视图结构。
LayoutInflate参数的作用
LayoutInflater 是 Android 中的一个类,用于将布局资源文件转换为对应的视图对象。它的主要作用是根据指定的布局资源文件(XML 文件)创建对应的视图层次结构,并将其实例化为 View 对象。
通过 LayoutInflater,可以在代码中动态地加载和创建布局,而不需要使用静态的 XML 布局文件。这样可以在运行时根据需要动态地创建不同的视图,并将其添加到相应的父容器中。
LayoutInflater 的几个参数的作用如下:
layoutResID:
这是需要加载的布局资源文件的 ID。通过指定资源文件的 ID,LayoutInflater 可以根据该 ID 加载对应的布局文件。
root:
这是加载布局时所需的父容器(parent)。在加载布局时,如果需要将布局添加到一个父容器中,可以将其指定为 root。如果不需要将布局添加到父容器中,可以将 root 设置为 null。
attachToRoot:
这是一个标志位,用于指示是否将加载后的布局视图添加到 root 父容器中。如果设置为 true,则会将加载后的布局视图直接附加到 root 父容器中;如果设置为 false,则只会返回加载后的布局视图,而不会将其添加到 root 父容器中。
举例
// 方式一:将布局添加成功
View view = inflater.inflate(R.layout.inflate_layout, ll, true);
// 方式二:报错,一个View只能有一个父亲(The specified child already has a parent.)
View view = inflater.inflate(R.layout.inflate_layout, ll, true); // 已经addView
ll.addView(view);
// 方式三:布局成功,第三个参数为false
// 目的:想要 inflate_layout 的根节点的属性(宽高)有效,又不想让其处于某一个容器中
View view = inflater.inflate(R.layout.inflate_layout, ll, false);
ll.addView(view);
// 方式四:root = null,这个时候不管第三个参数是什么,显示效果一样
// inflate_layout 的根节点的属性(宽高)设置无效,只是包裹子View,
// 但是子View(Button)有效,因为Button是出于容器下的
View view = inflater.inflate(R.layout.inflate_layout, null, false);
ll.addView(view);
备注:View 没有父容器 =>>> 尺寸大小 参数的设置无效
为什么requestWindowFeature()要在setContentView()之前调用
requestWindowFeature()
方法实际上是调用了 PhoneWindow.requestFeature()
方法。这个方法会检查一个名为 mContentParentExplicitlySet
的变量是否为 true
,如果是则会报错。
在 PhoneWindow.setContentView() 方法调用之前调用 requestWindowFeature() 的目的是确保在设置布局内容之前先请求窗口功能。如果先调用 setContentView()
,那么 mContentParentExplicitlySet 变量会在 setContentView() 方法内部被设置为 true
,这样再调用 requestWindowFeature() 就会引发错误。
因此,正确的顺序是先调用 requestWindowFeature() 请求窗口功能,然后再调用 setContentView() 设置布局内容。这样可以避免错误发生,并正确启用所需的窗口功能。
为什么这么设计呢?
Android 中的窗口布局是通过 DecorView
来实现的。DecorView 是一个表示活动窗口所有内容的根视图,包括标题栏、内容区域和其他装饰元素。
requestWindowFeature()
方法的设计是为了在创建 DecorView 之前设置窗口的特征
。这样可以确保在创建活动窗口时,根据特征的设置来决定如何绘制窗口的外观和行为。
当调用 setContentView() 方法时,将会创建 DecorView 和添加内容区域。在这之前,系统需要知道要使用哪些窗口特征来决定如何绘制 DecorView。这就是为什么应该在调用 setContentView() 之前先调用 requestWindowFeature() 的原因。
通过在 requestWindowFeature() 中设置特征
,我们可以控制窗口的外观和功能,例如是否显示标题栏、进度条等。这种设计方式使得我们能够在活动加载布局之前对窗口进行必要的配置
。
为什么 requestWindowFeature(Window.FEATURE_NO_TITLE);设置无效?
requestWindowFeature(Window.FEATURE_NO_TITLE)
设置无效的原因是因为你的活动继承自 AppCompatActivity
,而不是直接继承自 Activity。在 AppCompatActivity 中,标题栏的处理方式不同于传统的 Activity。
在 AppCompatActivity 中,应该使用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
方法来请求隐藏标题栏。这是因为 AppCompatActivity 提供了对兼容性特性的支持,可以确保在不同版本的 Android 上一致地隐藏标题栏。
supportRequestWindowFeature() 方法是 AppCompatActivity 的一个方法,用于设置窗口特征。通过调用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE),可以请求在活动中隐藏标题栏。
所以,如果活动继承自 AppCompatActivity
,并且想要隐藏标题栏,应该使用 supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
方法,而不是 requestWindowFeature(Window.FEATURE_NO_TITLE)
。前者会确保正确处理标题栏的隐藏。