当用户在应用中导航、退出和返回到应用时,应用中的 Activity 实例会在其生命周期的不同状态之间转换。Activity 类提供了许多回调,这些回调可让 activity 知道状态何时发生变化,或者系统正在创建、停止或恢复某个 activity,或者正在销毁 activity 所在的进程。
在生命周期回调方法中,您可以声明当用户离开和重新进入 activity 时 activity 的行为方式。例如,如果您正在构建流式视频播放器,则当用户切换到其他应用时,您可能会暂停视频并终止网络连接。当用户返回时,您可以重新连接到网络,并允许用户从同一位置继续播放视频。
每个回调都可让您执行适合给定状态变化的特定工作。在正确的时间执行正确的工作并妥善处理转换,将使您的应用更稳健、性能更出色。例如,正确实现生命周期回调有助于您的应用避免以下问题:
- 如果用户在使用您的应用时接到电话或切换到其他应用,应用会崩溃。
- 在用户未主动使用时消耗宝贵的系统资源。
- 如果用户离开您的应用,稍后再返回,则会丢失进度。
- 在屏幕于横屏和竖屏方向之间旋转时崩溃或丢失用户进度。
本文档将详细介绍 Activity 生命周期。本文档首先介绍了生命周期范式。接下来,它会说明每个回调:在执行这些回调时内部会发生什么,以及您需要在这些回调期间实现什么。
然后,简要介绍 activity 状态与进程被系统终止的漏洞之间的关系。最后,它讨论了与 activity 状态之间的转换相关的几个主题。
如需了解如何处理生命周期(包括有关最佳实践的指南),请参阅 Jetpack Compose 中的生命周期和保存界面状态。如需了解如何结合使用 activity 和架构组件来构建达到生产质量的强大应用,请参阅应用架构指南。
Activity 生命周期概念
为了在 activity 生命周期的各个阶段之间导航转换,Activity 类提供六个核心回调:onCreate、onStart、onResume、onPause、onStop 和 onDestroy。当 activity 进入新状态时,系统会调用其中每个回调。
图 1 直观呈现了此范式。
当用户开始离开 activity 时,系统会调用方法来销毁 activity。在某些情况下,activity 只是部分拆解,仍驻留在内存中,例如当用户切换到其他应用时。在这些情况下,activity 仍可返回前台。
如果用户返回到该 activity,它会从用户离开的位置继续。除少数例外情况外,应用在后台运行时无法启动 activity。
系统终止给定进程及其所含 activity 的可能性取决于当时 activity 的状态。如需详细了解状态与被逐出内存的风险之间的关系,请参阅activity 状态和从内存中逐出部分。
根据 Activity 的复杂程度,您可能不需要实现所有生命周期方法。不过,请务必了解每种模式,并实现可让应用按用户预期运行的模式。
Compose 和生命周期
在 Compose 中,避免直接在 onStart 或 onResume 等 activity 回调中放置业务逻辑或手动设置观察器。而是使用生命周期感知型效应和状态感知型观察器,它们会自动与界面在屏幕上的显示状态保持一致。
- 生命周期感知型收集:使用
collectAsStateWithLifecycle使用ViewModel中的数据流。当界面进入“已启动”状态时,此 API 会自动开始收集数据;当界面进入后台时,此 API 会停止收集数据,从而避免不必要的资源消耗。将 flow 作为状态收集后,您可以使用LifecycleEffects在发生生命周期事件时运行代码。 - 逻辑流程:通过使用这些 API,界面会通过组合树自然地对生命周期状态做出反应,从而确保仅在用户主动与组件互动时才执行业务逻辑。
如需详细了解 Compose 和生命周期,请参阅 Jetpack Compose 中的生命周期。
生命周期回调
本部分提供了有关 activity 生命周期中使用的回调方法的概念和实现信息。
有些操作属于 activity 生命周期方法。不过,请将实现依赖组件操作的代码放在组件中,而不是放在 activity 生命周期方法中。为此,您需要使依赖组件具有生命周期感知能力。如需了解如何使依赖组件感知生命周期,请参阅 Jetpack Compose 中的生命周期。
onCreate
您必须实现此回调,它会在系统首次创建 Activity 时触发。activity 创建后,会进入“已创建”状态。在 onCreate 方法中,执行在 activity 的整个生命周期内仅发生一次的基本应用启动逻辑。
例如,您的 onCreate 实现可能会将数据绑定到列表,将 activity 与 ViewModel 相关联,并实例化一些类范围变量。此方法会接收参数 savedInstanceState,这是一个 Bundle 对象,其中包含 activity 之前保存的状态。如果该 activity 之前从未存在过,则 Bundle 对象的值为 null。
如果您有一个生命周期感知型组件已连接到 activity 的生命周期,则该组件会收到 ON_CREATE 事件。系统会调用带有 @OnLifecycleEvent 注解的方法,以便生命周期感知型组件可以执行其在已创建状态下所需的任何设置代码。
以下示例展示了如何在最简单的 activity 中集成 Text 可组合项:
class ExampleActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // In here, we can call composables! MaterialTheme { Greeting(name = "compose") } } } } @Composable fun Greeting(name: String) { Text(text = "Hello $name!") }
您的 activity 不会保持在“已创建”状态。onCreate 方法执行完毕后,activity 会进入“已启动”状态,系统会快速连续调用 onStart 和 onResume 方法。
onStart
当 activity 进入“已开始”状态时,系统会调用 onStart。此调用会使 activity 对用户可见,因为应用正准备让 activity 进入前台并成为可互动状态。例如,此方法是初始化维护界面的代码的位置。
当 activity 进入“已开始”状态时,与 activity 的生命周期相关联的所有生命周期感知型组件都将收到 ON_START 事件。
onStart 方法会快速完成,并且与“已创建”状态一样,activity 不会停留在“已启动”状态。此回调完成后,activity 会进入 Resumed 状态,系统会调用 onResume 方法。
onResume
当 activity 进入 Resumed 状态时,它会来到前台,然后系统会调用 onResume 回调。这是应用与用户互动的状态。应用会一直保持这种状态,直到发生某些事件(如设备接听来电、用户导航到另一个 activity,或设备屏幕关闭)让应用失去焦点。
当 activity 进入 Resumed 状态时,与 activity 生命周期相关联的所有生命周期感知型组件都将收到 ON_RESUME 事件。此时,生命周期组件可以启用在组件可见且在前台时需要运行的任何功能,例如启动相机预览。
当发生中断事件时,activity 会进入 Paused 状态,系统会调用 onPause 回调。
如果 activity 从“已暂停”状态返回到“已恢复”状态,系统会再次调用 onResume 方法。因此,请实现 onResume,以初始化您在 onPause 期间释放的组件,并执行每次 activity 进入“已恢复”状态时必须完成的任何其他初始化操作。
以下是一个生命周期感知型组件示例,该组件在收到 ON_RESUME 事件时访问摄像头:
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun initializeCamera() {
if (camera == null) {
getCamera()
}
}
...
}
上述代码会在 LifecycleObserver 收到 ON_RESUME 事件后初始化相机。不过,在多窗口模式下,即使 activity 处于暂停状态,也可能完全可见。例如,当应用处于多窗口模式且用户点按不包含您的 activity 的窗口时,您的 activity 会切换到“已暂停”状态。
如果您希望仅在应用处于 Resumed 状态(在前台可见且处于活跃状态)时相机才处于活跃状态,请在前面演示的 ON_RESUME 事件之后初始化相机。如果您想在 activity 处于“已暂停”但可见状态(例如在多窗口模式下)时保持相机处于活跃状态,请在 ON_START 事件之后初始化相机。
不过,在 activity 处于 PAUSED 状态时让相机保持活跃状态,可能会导致在多窗口模式下处于 RESUMED 状态的其他应用无法访问相机。有时,在 activity 处于暂停状态时,有必要让相机保持活跃状态,但这样做实际上可能会降低整体用户体验。
因此,请仔细考虑在生命周期的哪个阶段控制共享系统资源最适合多窗口模式。如需详细了解如何支持多窗口模式,请参阅支持多窗口模式。
无论您选择在哪个构建事件中执行初始化操作,都请务必使用相应的生命周期事件来释放资源。如果您在 ON_START 事件之后初始化了某些内容,请在 ON_STOP 事件之后释放或终止它。如果您在 ON_RESUME 事件之后进行初始化,请在 ON_PAUSE 事件之后释放。
上述代码段将相机初始化代码放置在生命周期感知型组件中。您也可以将此代码直接放入 activity 生命周期回调(例如 onStart 和 onStop)中,但我们不建议这样做。将此逻辑添加到独立的生命周期感知型组件中,可让您在多个 activity 之间重复使用该组件,而无需复制代码。如需了解如何创建生命周期感知型组件,请参阅 Jetpack Compose 中的生命周期。
onPause
系统会调用此方法作为用户离开您的 activity 的第一个标志,但这并不总是意味着 activity 正在被销毁。它表示 activity 不再位于前台,但如果用户处于多窗口模式,它仍然可见。activity 可能会因多种原因而进入此状态:
- 中断应用执行的事件(如
onResume回调部分所述)会暂停当前 activity。这是最常见的情况。 - 在多窗口模式下,任何时候都只有一个应用具有焦点,系统会暂停所有其他应用。
- 打开一个新的半透明 activity(例如对话框)会使它所覆盖的 activity 暂停。只要 activity 部分可见但未处于焦点,它就会保持暂停状态。
当 activity 进入“已暂停”状态时,与 activity 的生命周期相关联的所有生命周期感知型组件都将收到 ON_PAUSE 事件。此时,生命周期组件可以停止在组件未处于前台时无需运行的任何功能,例如停止相机预览。
当 Activity 处于“已暂停”状态时,使用 onPause 方法暂停或调整无法继续或可能需要适度继续的操作,并预计这些操作很快就会恢复。
您还可以使用 onPause 方法释放系统资源、传感器(如 GPS)句柄,或在 activity 处于“已暂停”状态且用户不需要它们时释放任何会影响电池续航时间的资源。
不过,如onResume相关部分中所述,如果应用处于多窗口模式,则处于“已暂停”状态的 activity 可能仍完全可见。请考虑使用 onStop(而非 onPause)来完全释放或调整与界面相关的资源和操作,以便更好地支持多窗口模式。
以下 LifecycleObserver 对 ON_PAUSE 事件做出响应的示例与前面的 ON_RESUME 事件示例相对应,它会释放在收到 ON_RESUME 事件后初始化的相机:
class CameraComponent : LifecycleObserver {
...
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun releaseCamera() {
camera?.release()
camera = null
}
...
}
此示例在 LifecycleObserver 收到 ON_PAUSE 事件后放置相机释放代码。
onPause 执行时间非常短,不一定有足够的时间来执行保存操作。因此,请勿使用 onPause 保存应用或用户数据、发出网络调用或执行数据库事务。此类工作可能无法在方法完成之前完成。
而应在 onStop 期间执行高负载关停操作。如需详细了解在 onStop 期间执行的合适操作,请参阅下一部分。如需详细了解如何保存数据,请参阅关于保存和恢复状态的部分。
onPause 方法完成并不意味着 activity 离开“已暂停”状态。相反,activity 会一直保持此状态,直到恢复或对用户完全不可见。如果 activity 恢复,系统会再次调用 onResume 回调。
如果 activity 从“已暂停”状态返回到“已恢复”状态,系统会将 Activity 实例保留在内存中,并在系统调用 onResume 时重新调用该实例。在这种情况下,您无需重新初始化在进入“已恢复”状态之前的任何回调方法期间创建的组件。如果 activity 变得完全不可见,系统会调用 onStop。
onStop
当您的 activity 不再对用户可见时,说明其已进入“已停止”状态,因此系统将调用 onStop 回调。当新启动的 activity 覆盖整个屏幕时,可能会发生这种情况。当 activity 运行完毕并即将终止时,系统也会调用 onStop。
当 activity 进入“已停止”状态时,与 activity 的生命周期相关联的所有生命周期感知型组件都将收到 ON_STOP 事件。此时,生命周期组件可以停止在组件未显示在屏幕上时无需运行的任何功能。
在 onStop 方法中,释放或调整在应用对用户不可见时不需要的资源。例如,您的应用可能会暂停动画,或从细粒度位置信息更新切换到粗粒度位置信息更新。使用 onStop 而不是 onPause 意味着,即使在用户以多窗口模式查看 activity 时,与界面相关的工作也会继续。
此外,请使用 onStop 执行相对占用 CPU 资源的关停操作。例如,如果您找不到更好的时间将信息保存到数据库,则可以在 onStop 期间执行此操作。以下示例展示了 onStop 的实现,该实现会将草稿记事的内容保存到永久性存储空间:
override fun onStop() {
super.onStop()
// Delegate the save operation to the ViewModel, which handles the
// background thread operations (e.g., using Kotlin Coroutines and Room).
noteViewModel.saveDraft()
}
当 activity 进入“已停止”状态时,Activity 对象会保留在内存中:它会维护所有状态和成员信息,但不会附加到窗口管理器。当 activity 恢复时,它会重新调用此信息。
进入“已停止”状态后,Activity 要么返回与用户互动,要么结束运行并消失。如果 activity 恢复,系统会调用 onRestart。如果 Activity 已完成运行,系统会调用 onDestroy。
onDestroy
系统会在销毁 activity 之前调用 onDestroy。系统会出于以下两个原因之一调用此回调:
- activity 正在完成,原因可能是用户完全关闭了 activity,也可能是对 activity 调用了
finish。 - 系统因配置更改(例如设备旋转或进入多窗口模式)而暂时销毁 activity。
当 activity 转换为已销毁状态时,与 activity 的生命周期相关联的任何生命周期感知型组件都会收到 ON_DESTROY 事件。在这里,生命周期组件可以在 Activity 销毁之前清理它们需要清理的所有内容。
请勿将逻辑放入 Activity 中来确定其销毁原因,而是使用 ViewModel 对象来包含 Activity 的相关视图数据。如果 Activity 因配置更改而重新创建,则 ViewModel 无需执行任何操作,因为它会被保留并提供给下一个 Activity 实例。
如果 Activity 未重新创建,则 ViewModel 会调用 onCleared 方法,以便在销毁之前清理所需的所有数据。您可以使用 isFinishing 方法来区分这两种情况。
如果 activity 正在结束,onDestroy 是 activity 收到的最后一个生命周期回调。如果调用 onDestroy 是因为配置发生更改,系统会立即创建一个新的 activity 实例,然后在新配置中对该新实例调用 onCreate。
onDestroy 回调会释放早期回调未释放的所有资源,例如 onStop。
Activity 状态和从内存中弹出
系统会在需要释放 RAM 时终止进程。系统终止给定进程的可能性取决于该进程当时的状态。而进程状态又取决于进程中运行的 activity 的状态。表 1 显示了进程状态、activity 状态以及系统终止进程的可能性之间的相关性。此表仅适用于进程未运行其他类型的应用组件的情况。
系统终止进程的可能性 |
进程状态 |
最终活动状态 |
最低 |
前台(拥有或即将获得焦点) |
已恢复 |
低 |
可见(未聚焦) |
已启动/已暂停 |
较高 |
后台(不可见) |
已停止 |
最高 |
空 |
已销毁 |
表 1. 进程生命周期与 activity 状态之间的关系。
系统永远不会直接终止 Activity 以释放内存,相反,系统会终止 activity 运行所在的进程,这不仅会销毁 activity,还会销毁在该进程中运行的所有其他内容。如需了解如何在系统发起的进程终止时保留和恢复 activity 的界面状态,请参阅保存和恢复状态部分。
用户还可以通过使用“设置”下的“应用管理器”来终止相应应用,从而终止进程。
如需详细了解进程,请参阅进程和线程概览。
保存和恢复瞬时界面状态
用户期望 Activity 的界面状态在整个配置更改(例如旋转或切换到多窗口模式)期间保持不变。但是,默认情况下,系统会在发生此类配置更改时销毁 activity,从而清除存储在 activity 实例中的任何界面状态。
同样,如果用户暂时从您的应用切换到其他应用,稍后再返回到您的应用,他们也会希望界面状态保持不变。不过,当用户离开且您的 activity 停止时,系统可能会销毁应用的进程。
当系统限制销毁 activity 时,请结合使用 ViewModel(适用于复杂的业务逻辑和屏幕状态)、Jetpack Compose rememberSaveable API(适用于轻量级界面状态)和/或本地存储空间来保留用户的临时界面状态。如需详细了解用户期望与系统行为之间的差异,以及如何在系统发起的 activity 和进程终止后最好地保留复杂的界面状态数据,请参阅保存界面状态。
rememberSaveable 会在后台捆绑状态,从而在配置更改和系统发起的进程终止后自动保留状态,无需 activity 级样板代码即可提供无缝体验。
实例状态
在某些情况下,您的 activity 会因正常的应用行为而销毁,例如当用户按下返回按钮或您的 activity 通过调用 finish 方法发出销毁信号时。
当您的 activity 因用户按“返回”按钮或 activity 自行结束而被销毁时,系统和用户对该 Activity 实例的概念将永远消失。在这些情况下,用户的预期与系统的行为一致,您无需执行任何额外工作。
但是,如果系统因系统限制(例如配置更改或内存压力)而销毁 activity,虽然实际 Activity 实例已消失,但系统会记住它曾经存在。如果用户尝试返回到该 activity,系统会使用一组描述 activity 销毁时状态的已保存数据创建该 activity 的新实例。
系统用于恢复先前状态的已保存数据称为实例状态。在后台,它是一个键值对集合。默认情况下,系统会使用实例状态来保存有关界面布局的基本信息,例如用户文本输入或滚动位置。
您可以使用 rememberSaveable 钩入此系统行为。如果您的 activity 实例被销毁并重新创建,则封装在 rememberSaveable 中的任何界面状态都会自动恢复,而无需您编写任何额外的 activity 级代码。
不过,您的 activity 可能需要恢复更复杂的状态信息,例如用户数据、网络响应或跟踪用户进度的成员变量。实例状态机制(以及 rememberSaveable)不适合保留大量数据,因为它需要在主线程上进行序列化,并且会消耗系统进程内存。
如需保留的数据量超过极少量,请结合使用永久性本地存储、ViewModel 类和 Compose 状态提升,如保存界面状态中所述。
使用 rememberSaveable 保存简单、轻量级的界面状态
当 activity 开始停止时,系统会准备将状态信息保存到实例状态 bundle 中。如需接入此系统行为,您可以直接在可组合函数中使用 rememberSaveable。rememberSaveable 会在重新创建 activity 时自动保存和恢复临时界面状态,例如用户文本输入或滚动位置。
如需保存自定义的轻量级状态信息(例如用户在游戏中的进度),请使用 rememberSaveable 声明状态。Compose 框架会在后台处理到实例状态 bundle 的序列化:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
如需保存持久性数据(例如用户偏好设置或数据库中的数据),请在 activity 位于前台时抓住合适的机会。如果未出现此类机会,请在 onStop 方法期间保存持久性数据。
使用保存的实例状态恢复 Activity 界面状态
当 activity 在之前被销毁后重新创建时,系统会自动恢复状态。使用 rememberSaveable 时,您无需编写任何显式恢复逻辑、检查 null 软件包或替换 activity 回调。初始化和保存状态的代码还会在 activity 恢复时无缝恢复状态:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
活动和导航
应用在其生命周期内可能会多次在屏幕之间切换,例如当用户点按设备的“返回”按钮或选择新目的地时。现代 Android 应用通常采用单 activity 架构。
您的应用不会为每个屏幕启动新的 Activity,而是托管单个 Activity,并使用 Navigation 组件在该 activity 中换出可组合屏幕。
如需了解如何实现现代化的 Compose 优先导航,请参阅 Jetpack Compose Navigation 3 库指南。
从一个 Activity 启动另一个 Activity
一个 activity 可能需要在某个时间点启动另一个 activity。例如,当应用需要从当前屏幕切换到新屏幕时,就会出现这种需求。
根据您的 activity 是否希望从即将启动的新 activity 中获取返回结果,您可以使用 startActivity 方法或 startActivityForResult 方法启动新 activity。无论哪种情况,您都需要传入 Intent 对象。
Intent 对象会指定您要启动的确切 activity,或描述您要执行的操作类型。系统会为您选择合适的 activity,甚至可能来自其他应用。Intent 对象还可以携带少量数据,供启动的 activity 使用。如需详细了解 Intent 类,请参阅 intent 和 intent 过滤器。
startActivity
如果新启动的 activity 不需要返回结果,当前 activity 可以通过调用 startActivity 方法来启动它。
在您自己的应用中工作时,您通常只需启动一个已知 activity。例如,以下代码段展示了如何启动名为 SignInActivity 的 activity。
val context = LocalContext.current
Button(onClick = {
val intent = Intent(context, SignInActivity::class.java)
context.startActivity(intent)
}) {
Text("Sign In")
}
启动外部 activity
虽然内部应用导航由 Navigation 处理,但您的 Activity 偶尔需要启动其他 activity。当您想利用外部应用执行特定操作(例如打开网络浏览器、发送电子邮件或拍照)时,通常会发生这种情况。
为此,您可以使用 Intent 对象来描述您要执行的操作类型,系统会从其他应用中启动相应的 activity。
例如,如果您想让用户发送电子邮件,可以创建以下 intent:
val intent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_EMAIL, recipientArray)
}
startActivity(intent)
如果您需要启动外部 activity 并获取返回的结果(例如,要求相机应用拍摄照片并返回图片),请使用现代 Activity result API,而不是已废弃的 startActivityForResult 回调。
协调 Activity
当一个 Activity 启动另一个 Activity 时,它们都会经历生命周期转换。第一个 activity 停止运行并进入“已暂停”或“已停止”状态,而另一个 activity 则被创建。如果这些 activity 共享保存到磁盘或其他位置的数据,请务必了解,第一个 activity 在第二个 activity 创建之前并未完全停止。而是启动第二个实例的过程与停止第一个实例的过程重叠。
生命周期回调的顺序有明确的定义,特别是当两个 activity 在同一个进程(即同一个应用)中,并且其中一个要启动另一个时。以下是当 Activity A 启动 Activity B 时一系列操作的发生顺序:
- Activity A 的
onPause方法执行。 - Activity B 的
onCreate、onStart和onResume方法依次执行。Activity B 现在具有用户焦点。 - 如果 Activity A 在屏幕上不再可见,则会执行其
onStop方法。
通过这一系列生命周期回调,您可以管理从一个 activity 到另一个 activity 的信息转换。
其他资源
如需详细了解 activity 生命周期,请参阅下面列出的其他资源: