多项恢复

在 Android 9(及更低版本)中,应用会在以下情况下进入 PAUSED 状态:

  • 一项新的半透明 Activity 在应用的上层启动,同时应用仍然可见(因此并未停止)。
  • 该 Activity 失去了焦点,但没有被遮挡,可以与用户进行互动。例如,在多窗口模式下,一些 Activity 处于可见状态,并可同时接收触摸输入。

这些情况的不同之处在于应用必须执行的暂停次数,但无法在应用级别进行区分。

在 Android 10 中,可见堆栈中所有可设置为焦点的顶层 Activity 都处于 RESUMED 状态。对于使用 onPause()(而非 onStop())来停止刷新界面及与用户互动的应用而言,这样可提高与多窗口模式和多屏幕 (MD) 模式的兼容性。这意味着:

  • 在分屏模式下,两个 Activity 都处于 RESUMED 状态。
  • 在自由格式的窗口模式下,所有可见的顶层 Activity 都处于 RESUMED 状态。
  • 多个屏幕上的 Activity 可以同时处于 RESUMED 状态。

图 1. 可折叠设备上的多项恢复

图 2. 在桌面模式下的多项恢复

当 activity 无法设置为焦点或被部分遮挡时(示例如下),它们可以处于 PAUSED 状态:

  • 在已最小化的分屏(启动器在侧面)模式下,顶层 Activity 不处于 RESUMED 状态,因为它无法设置为焦点。
  • 在画中画模式下,Activity 不处于 RESUMED 状态,因为它无法设置为焦点。
  • 当 Activity 被同一堆栈中的其他透明 Activity 覆盖时。

在此方式下,对应用而言,Activity 仅在 RESUMED 状态下才能接收来自用户的输入。在 Android 10 之前,Activity 也可以在 PAUSED 状态下接收输入(例如,您可以试试在搭载 Android 9 的设备上同时轻触分屏模式下的两个 Activity)。

为了保留之前 Android 版本的“已恢复”信号(并告知应用应在何时获取对独占资源或单例资源的访问权),Android 10 新增了一个回调:

Activity#onTopResumedActivityChanged(boolean onTop)

调用后,此回调将在 Activity#onResume()Activity#onPause() 之间实际得到调用。此回调并非必需且可以跳过,因此,Activity 可以从 RESUMED 变为 PAUSED 状态,而不会处于系统的最顶层,如在多窗口模式下。由于此回调并非必需,因此它不是 Activity 生命周期的一部分,应很少使用。

上一个在顶层处于 RESUMED 状态的 Activity 先接收 onTopResumedActivity(false) 并完成其执行过程,然后下一个在顶层处于 RESUMED 状态的 Activity 才会接收 onTopResumedActivity(true),除非上一个 Activity 在处理该方法调用时花费了过长时间并达到了 500ms 超时。

兼容性

如要在实现多项恢复时保持兼容性,请考虑以下解决方案。

在一个应用进程中启动多个处于已恢复状态的 Activity

  • 问题:在 Android 9 及更低版本中,系统同时只允许一个 Activity 处于 RESUMED 状态。多个 Activity 执行任何转换时,都需要先暂停一个 Activity,然后再恢复另一个 Activity。一些应用和框架(例如 Flutter,或 Android 的 LocalActivityManager)会利用此行为方式,并在单例中存储已恢复的 Activity 的相关状态。
  • 解决方案:在 Android 9 及更低版本中,如果同一进程中的两个 Activity 都要进行恢复,系统仅会恢复在 Z 轴顺序中较高的 Activity。以 Android 10 为目标平台的应用可支持同时恢复多个 Activity。

同时访问相机

  • 问题:这些问题在 Android 9 及更低版本中也存在。例如,在画中画模式下,处于 RESUMED 状态的全屏 Activity 可能会丢失相机焦点,而在顶层处于 RESUMED 状态的 Activity 则会获取相机焦点。随着多窗口和多屏幕模式的广泛采用,这种情况越来越多。
    • 由于我们对 RESUME 状态做出的变更,应用即使处于 RESUMED 状态,也可能与相机断开连接。为了解决这个问题,应用必须处理与相机断开连接的问题,避免出现崩溃。连接断开后,应用会收到断开连接的回调,并且所有对相应 API 的调用都会开始抛出 CameraAccessException
    • resizeableActivity=false 并不能保证应用获得对相机的专属访问权限,因为系统可能会在其他屏幕上打开其他用到相机的应用。
  • 解决方案:开发者应该指明应用与相机断开连接后应执行的逻辑。如果某个应用与相机断开连接,它应监视相机可用性回调,以尝试重新连接并继续使用相机。除了现有的 CameraManager#AvailabilityCallback#onCameraAvailable() 回调之外,Android 10 还添加了 CameraManager#AvailabilityCallback#onCameraAccessPrioritiesChanged(),它涵盖了焦点(以及相机优先使用权)在处于 RESUMED 状态的多个 Activity 之间切换的情况。应用开发者应同时使用这两个回调来确定尝试访问相机的适当时机。

多项恢复

在 Android 10 中,Activity 生命周期状态由可见性和 Z 轴顺序决定。为确保在 Activity 的可见性更新后具有正确的状态并评估哪个生命周期状态适用,请从不同位置调用 ActivityRecord#makeActiveIfNeeded() 方法。在 Android 10 中,“活跃”意味着应用处于 RESUMEDPAUSED 状态,且仅适用于这两种情况。

在 Android 10 中,恢复 Activity 的过程将在每个堆栈中(而不是系统中的单个位置)单独进行跟踪。这是因为多窗口模式下可以同时执行多个 Activity 转换。如需了解详情,请参阅 ActivityStack#mInResumeTopActivity

在顶层处于已恢复状态的 activity 回调

在执行可能导致顶层 activity 发生更改的操作(例如启动 activity、恢复或更改 Z 轴顺序)之后,系统将调用 ActivityStackSupervisor#updateTopResumedActivityIfNeeded()。此方法会检查在顶层处于已恢复状态的 activity 是否已更改,并在需要时执行更新。如果上一个在顶层处于已恢复状态的 activity 尚未释放这一状态,系统会向其发送一条告知其失去该状态的消息,并在服务器端安排一项超时操作 (ActivityStackSupervisor#scheduleTopResumedStateLossTimeout())。在该 activity 释放这一状态或发生超时后,系统会将该状态授予下一个 activity。用法如下:

ActivityStackSupervisor#scheduleTopResumedActivityStateIfNeeded()

Android 10 新增了一项 TopResumedActivityChangeItem 事务,该事务用于向客户端报告在顶层处于已恢复状态的更改,并利用了 Android 9 中的 ActivityLifecycler 架构。

该状态的相关信息存储在客户端,每次 Activity 转换为 RESUMEDPAUSED 时,系统还会检查是否应该调用 onTopResumedActivityChanged() 回调。这可以在服务器和客户端之间就生命周期状态和“在顶层处于 RESUMED 状态”这一状态进行通信时实现某些分离。