支持多窗口模式

多窗口模式允许多个应用同时共享同一屏幕。应用可以并排显示或上下显示(分屏模式),一个应用在小窗口中覆盖其他应用(画中画模式),或者单个应用在单独的可移动、可调整大小的窗口中显示(桌面窗口模式)。

图 1. 在分屏模式下并排显示两个应用。

有关如何在手机上访问分屏模式的用户说明,请访问在 Pixel 手机上同时查看两个应用

版本特有的多窗口功能

多窗口用户体验取决于 Android 版本和设备类型

  • Android 7.0 (API 级别 24) 在小屏幕设备上引入了分屏模式,在特定设备上引入了画中画模式。

    • 分屏模式用两个应用填充屏幕,让它们并排显示或上下显示。用户可以拖动分隔这两个应用的分隔条,以使一个应用变大,另一个应用变小。

    • 画中画模式使用户在与另一个应用互动时继续播放视频(请参阅画中画支持)。

    • 桌面窗口模式,用户可以自由调整每个 activity 的大小,可以由大屏幕设备制造商启用。

      您可以通过指定 activity 的最小允许尺寸来配置应用如何处理多窗口模式。您还可以通过设置resizeableActivity="false"来为应用停用多窗口模式,以确保系统始终全屏显示您的应用。

  • Android 8.0 (API 级别 26) 将画中画模式扩展到小屏幕设备。

  • Android 12 (API 级别 31) 使多窗口模式成为标准行为。

    • 在大屏幕上中等展开窗口尺寸类别),平台支持所有应用在多窗口模式下运行,无论应用配置如何。如果resizeableActivity="false",则在必要时将应用置于兼容模式以适应显示尺寸。

    • 在小屏幕上紧凑窗口尺寸类别),系统会检查 activity 的minWidthminHeight以确定 activity 是否可以在多窗口模式下运行。如果resizeableActivity="false",则无论最小宽度和高度如何,都无法在多窗口模式下运行该应用。

  • Android 16 (API 级别 36) 覆盖屏幕方向、宽高比和可调整大小的限制。

    • 在大屏幕上(最小宽度 >= 600dp),系统会忽略用于限制应用方向、宽高比和可调整大小的清单属性和运行时 API,从而优化所有设备尺寸的用户体验。

      要了解如何将游戏排除在 Android 16 更改之外,请参阅应用方向、宽高比和可调整大小例外情况。

分屏模式

用户通过执行以下操作激活分屏模式

  1. 打开最近应用屏幕
  2. 将应用滑动到视图中
  3. 按应用标题栏中的应用图标
  4. 选择分屏菜单选项
  5. 从最近应用屏幕中选择另一个应用,或关闭最近应用屏幕并运行另一个应用

用户通过将窗口分隔条拖到屏幕边缘(向上或向下,向左或向右)退出分屏模式。

相邻启动

如果您的应用需要通过 Intent 访问内容,您可以使用FLAG_ACTIVITY_LAUNCH_ADJACENT在相邻的分屏窗口中打开内容。

FLAG_ACTIVITY_LAUNCH_ADJACENT在 Android 7.0 (API 级别 24) 中引入,以使在分屏模式下运行的应用能够在相邻窗口中启动 activity。

Android 12L (API 级别 32) 及更高版本已扩展此标志的定义,以使全屏运行的应用能够激活分屏模式,然后在相邻窗口中启动 activity。

要启动相邻 activity,请将FLAG_ACTIVITY_LAUNCH_ADJACENTFLAG_ACTIVITY_NEW_TASK结合使用,例如:

fun openUrlInAdjacentWindow(url: String) {
    Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(url)
       addFlags(Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT or Intent.FLAG_ACTIVITY_NEW_TASK)
    }.also { intent -> startActivity(intent) }
}

多窗口模式下的 Activity 生命周期

多窗口模式不会改变activity 生命周期。但是,多个窗口中应用的恢复状态在不同版本的 Android 上有所不同。

多重恢复

Android 10 (API 级别 29) 及更高版本支持多重恢复——当设备处于多窗口模式时,所有 activity 都保持RESUMED状态。如果透明 activity 位于 activity 之上或 activity 无法获得焦点(例如,activity 处于画中画模式),则 activity 可能会暂停。在给定时间也可能没有 activity 获得焦点,例如,如果通知抽屉打开。 onStop()方法照常工作:每当 activity 从屏幕上移除时,就会调用此方法。

多重恢复在运行 Android 9 (API 级别 28) 的特定设备上也可用。要在 Android 9 设备上选择多重恢复,请添加以下清单元数据:

<meta-data android:name="android.allow_multiple_resumed_activities" android:value="true" />

要验证给定设备是否支持此清单元数据,请参阅设备规格。

Android 9

在 Android 9 (API 级别 28) 及更低版本的多窗口模式下,在给定时间只有一个用户最近与之互动的 activity 处于活动状态。此 activity 被认为是最顶层的,并且是处于RESUMED状态的唯一 activity。所有其他可见 activity 都处于STARTED状态,但未处于RESUMED状态。但是,系统会赋予这些可见但未恢复的 activity 比不可见 activity 更高的优先级。如果用户与其中一个可见 activity 互动,则该 activity 会恢复,并且之前最顶层的 activity 进入STARTED状态。

当单个活动应用进程中有多个 activity 时,z 轴顺序最高的 activity 会恢复,其他 activity 会暂停。

配置变更

当用户将应用置于多窗口模式时,系统会按照处理配置更改中的指定,向 activity 发送配置更改通知。当用户调整应用大小或将应用恢复为全屏模式时,也会发生这种情况。

本质上,此更改具有与系统通知应用设备已从纵向切换到横向时相同的 activity 生命周期影响,只是应用尺寸发生变化,而不仅仅是交换。您的 activity 可以自行处理配置更改,或者您的应用可以允许系统销毁 activity 并使用新尺寸重新创建它。

如果用户正在调整窗口大小并使其在任一维度上变大,系统会调整 activity 的大小以匹配用户操作,并根据需要发出配置更改。如果应用在绘制新暴露区域时滞后,系统会暂时用windowBackground属性或默认的windowBackgroundFallback样式属性指定的颜色填充这些区域。

独占资源访问

为了帮助支持多重恢复功能,请使用onTopResumedActivityChanged()生命周期回调。

当 activity 获得或失去最顶层恢复的 activity 位置时,会调用此回调,这在 activity 使用共享的单例资源(例如麦克风或摄像头)时很重要

override fun onTopResumedActivityChanged(topResumed: Boolean) {
    if (topResumed) {
        // Top resumed activity.
        // Can be a signal to re-acquire exclusive resources.
    } else {
        // No longer the top resumed activity.
    }
}

请注意,应用可能会因其他原因(例如共享硬件的移除)而丢失资源。

在任何情况下,应用都应优雅地处理影响可用资源的事件和状态更改。

对于使用摄像头的应用,CameraManager.AvailabilityCallback#onCameraAccessPrioritiesChanged()提供了一个提示,表明这可能是尝试获取摄像头访问权限的好时机。此方法从 Android 10 (API 级别 29) 开始可用。

请记住,resizeableActivity=false并不能保证独占摄像头访问,因为使用摄像头的其他应用可以在其他显示屏上打开。

图 2. 多窗口模式下的相机。

当应用失去焦点时,您的应用不一定必须释放相机。例如,您可能希望在用户与新获得焦点的最顶层恢复的应用互动时继续相机预览。您的应用在不是最顶层恢复的应用时继续运行相机是没问题的,但它必须正确处理断开连接的情况。当最顶层恢复的应用想要使用相机时,它可以打开相机,您的应用将失去访问权限。当您的应用重新获得焦点时,您的应用可以重新打开相机。

在应用收到CameraDevice.StateCallback#onDisconnected()回调后,对相机设备的后续调用将抛出CameraAccessException

窗口指标

Android 11 (API 级别 30) 引入了以下WindowManager方法来提供在多窗口模式下运行的应用的边界

Jetpack WindowManager 库方法computeCurrentWindowMetrics()computeMaximumWindowMetrics()分别提供了类似的功能,但具有向后兼容到 API 级别 14 的能力。

要获取除当前显示屏之外的显示屏的指标,请执行以下操作(如代码片段所示):

  • 创建显示上下文
  • 为显示器创建窗口上下文
  • 获取窗口上下文的WindowManager
  • 获取应用可用的最大显示区域的WindowMetrics

val windowMetrics = context.createDisplayContext(display)
                           .createWindowContext(WindowManager.LayoutParams.TYPE_APPLICATION, null)
                           .getSystemService(WindowManager::class.java)
                           .maximumWindowMetrics

已弃用方法

Display方法getSize()getMetrics()在 API 级别 30 中已弃用,取而代之的是新的WindowManager方法。

Android 12 (API 级别 31) 弃用了Display方法getRealSize()getRealMetrics(),并更新了它们的行为,使其更接近getMaximumWindowMetrics()的行为。

多窗口模式配置

如果您的应用面向 Android 7.0 (API 级别 24) 或更高版本,您可以配置应用的 activity 如何支持以及是否支持多窗口模式。您可以在清单中设置属性以控制大小和布局。根 activity 的属性设置适用于其任务栈中的所有 activity。例如,如果根 activity 的android:resizeableActivity="true",则任务栈中的所有 activity 都是可调整大小的。在某些大型设备(例如 Chromebook)上,即使您指定android:resizeableActivity="false",您的应用也可能在可调整大小的窗口中运行。如果这会破坏您的应用,您可以使用Google Play 上的过滤器来限制您的应用在此类设备上的可用性。

Android 12 (API 级别 31) 默认为多窗口模式。在大屏幕上(中等展开窗口尺寸类别),所有应用都在多窗口模式下运行,无论应用配置如何。在小屏幕上,系统会检查 activity 的minWidthminHeightresizeableActivity设置,以确定 activity 是否可以在多窗口模式下运行。

resizeableActivity

在清单的<activity><application>元素中设置此属性,以在 API 级别 30 及更低版本上启用或禁用多窗口模式:

<application
  android:name=".MyActivity"
  android:resizeableActivity=["true" | "false"] />;

如果此属性设置为true,则 activity 可以在分屏和桌面窗口模式下启动。如果属性设置为false,则 activity 不支持多窗口模式。如果值为 false,并且用户尝试在多窗口模式下启动 activity,则 activity 将占用整个屏幕。

如果您的应用面向 API 级别 24 或更高版本,但您未为此属性指定值,则该属性的值默认为 true。

如果您的应用面向 API 级别 31 或更高版本,则此属性在小屏幕和大屏幕上的工作方式不同:

  • 大屏幕中等展开窗口尺寸类别):所有应用都支持多窗口模式。该属性表示 activity 是否可以调整大小。如果resizeableActivity="false",则在必要时将应用置于兼容模式以符合显示尺寸。
  • 小屏幕紧凑窗口尺寸类别):如果resizeableActivity="true"且 activity 最小宽度和最小高度在多窗口要求范围内,则 activity 支持多窗口模式。如果resizeableActivity="false",则无论 activity 最小宽度和高度如何,activity 都不支持多窗口模式。

如果您的应用面向 API 级别 36 或更高版本,则在最小宽度 >= 600dp 的显示屏上,此属性将被忽略。但是,应用完全遵守用户选择的宽高比(请参阅用户每应用覆盖)。

如果您正在开发游戏,请参阅应用方向、宽高比和可调整大小,了解如何将您的游戏排除在 Android 16 (API 级别 36) 的更改之外。

supportsPictureInPicture

在清单的<activity>节点中设置此属性,以指示 activity 是否支持画中画模式。

<activity
  android:name=".MyActivity"
  android:supportsPictureInPicture=["true" | "false"] />

configChanges

要自行处理多窗口配置更改(例如当用户调整窗口大小时),请将android:configChanges属性添加到您的应用清单<activity>节点,并至少包含以下值:

<activity
  android:name=".MyActivity"
  android:configChanges="screenSize | smallestScreenSize
      | screenLayout | orientation" />

添加android:configChanges后,您的 activity 和 fragment 将收到对onConfigurationChanged()的回调,而不是被销毁和重新创建。然后,您可以根据需要手动更新视图、重新加载资源并执行其他操作。

<layout>

在 Android 7.0 (API 级别 24) 及更高版本上,<layout>清单元素支持影响 activity 在多窗口模式下行为的多个属性:

  • android:defaultHeightandroid:defaultWidth:在桌面窗口模式下启动时 activity 的默认高度和宽度。

  • android:gravity:在桌面窗口模式下启动时 activity 的初始位置。请参阅Gravity类以获取合适的值。

  • android:minHeightandroid:minWidth:在分屏和桌面窗口模式下 activity 的最小高度和最小宽度。如果用户在分屏模式下移动分隔条使 activity 小于指定的最小值,系统会将 activity 裁剪到用户请求的大小。

以下代码显示了如何在桌面窗口模式下显示 activity 时,指定 activity 的默认大小和位置以及其最小大小:

<activity android:name=".MyActivity">
    <layout android:defaultHeight="500dp"
          android:defaultWidth="600dp"
          android:gravity="top|end|..."
          android:minHeight="450dp"
          android:minWidth="300dp" />
</activity>

运行时多窗口模式

从 Android 7.0 开始,系统提供了支持可以在多窗口模式下运行的应用程序的功能。

多窗口模式下禁用的功能

在多窗口模式下,Android 可能会禁用或忽略不适用于与其他 activity 或应用共享设备屏幕的 activity 的功能。

此外,一些系统界面自定义选项也被禁用。例如,如果应用在多窗口模式下运行,则无法隐藏状态栏(请参阅控制系统界面可见性)。

系统会忽略对android:screenOrientation属性的更改。

多窗口模式查询和回调

Activity类提供以下方法来支持多窗口模式:

  • isInMultiWindowMode():指示 activity 是否处于多窗口模式。

  • isInPictureInPictureMode():指示 activity 是否处于画中画模式。

  • onMultiWindowModeChanged():每当 activity 进入或退出多窗口模式时,系统都会调用此方法。如果 activity 正在进入多窗口模式,系统会向该方法传递一个 true 值;如果 activity 正在退出多窗口模式,则传递一个 false 值。

  • onPictureInPictureModeChanged():每当 activity 进入或退出画中画模式时,系统都会调用此方法。如果 activity 正在进入画中画模式,系统会向该方法传递一个 true 值;如果 activity 正在退出画中画模式,则传递一个 false 值。

Fragment类公开了许多这些方法的版本;例如,Fragment.onMultiWindowModeChanged()

画中画模式

要将 activity 置于画中画模式,请调用enterPictureInPictureMode()。如果设备不支持画中画模式,此方法无效。如需了解更多信息,请参阅使用画中画 (PiP) 添加视频

多窗口模式下的新 activity

启动新 activity 时,您可以指示新 activity 尽可能显示在当前 activity 旁边。使用 intent 标志FLAG_ACTIVITY_LAUNCH_ADJACENT,它会告诉系统尝试在相邻窗口中创建新 activity,以便两个 activity 共享屏幕。系统会尽最大努力执行此操作,但不能保证一定会发生。

如果设备处于桌面窗口模式且您正在启动新 activity,可以通过调用ActivityOptions.setLaunchBounds()来指定新 activity 的尺寸和屏幕位置。如果设备未处于多窗口模式,该方法无效。

在 API 级别 30 及更低版本上,如果您在任务栈中启动 activity,该 activity 会替换屏幕上的 activity,并继承其所有多窗口属性。如果您想以多窗口模式将新 activity 作为单独的窗口启动,则必须将其启动到一个新的任务栈中。

Android 12 (API 级别 31) 允许应用在activity 嵌入中将应用的任务窗口拆分为多个 activity。您可以通过创建 XML 配置文件或调用 Jetpack WindowManager API 来确定应用如何显示其 activity — 全屏、并排或堆叠。

拖放

当两个 activity 共享屏幕时,用户可以从一个 activity 拖放数据到另一个 activity。(在 Android 7.0 之前,用户只能在单个 activity 内拖放数据。)要快速添加对接受拖放内容的支持,请参阅DropHelper API。有关全面的拖放指南,请参阅启用拖放

多实例

每个根 activity 都有自己的任务,显示在自己的窗口中。要在单独的窗口中启动应用的新实例,请使用FLAG_ACTIVITY_NEW_TASK标志启动新 activity。您可以将此设置与多窗口属性结合使用,以请求新窗口的特定位置。例如,购物应用可以显示多个相邻窗口来比较产品。

Android 12 (API 级别 31) 及更高版本允许您在activity 嵌入中,在同一个任务窗口中并排启动 activity 的两个实例。

如果您希望用户能够从应用启动器或任务栏启动您的应用的另一个实例,请在启动 activity 的清单中设置android:resizeableActivity="true",并且不要使用阻止多个实例的启动模式。例如,当设置了FLAG_ACTIVITY_MULTIPLE_TASKFLAG_ACTIVITY_NEW_DOCUMENT时,singleInstancePerTask activity 可以在不同的任务中多次实例化。

在 Android 15 (API 级别 35) 及更高版本中,PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI允许您声明对多实例的支持。该属性是系统界面向用户公开控件以创建应用多个实例的明确信号。该属性独立于启动模式,但只有当 activity 或应用的启动模式与该属性兼容时才能使用,例如,当启动模式不是singleInstance时。

当应用的多个实例在可折叠设备上的单独窗口中运行时,如果设备姿势发生变化,一个或多个实例可能会被发送到后台。例如,假设设备已展开,并且在折叠的每一侧的单独窗口中运行着两个应用实例。如果设备折叠,其中一个实例可能会被终止,而不是尝试将两个实例的窗口都适配到较小的屏幕上。

多窗口模式验证

无论您的应用是否面向 API 级别 24 或更高版本,您都应验证它在多窗口模式下的行为,以防用户尝试在运行 Android 7.0 或更高版本的设备上以多窗口模式启动它。

测试设备

运行 Android 7.0 (API 级别 24) 或更高版本的设备支持多窗口模式。

API 级别 23 或更低

当用户尝试在多窗口模式下使用应用时,除非应用声明了固定方向,否则系统会强制调整应用大小。

如果您的应用未声明固定方向,您应该在运行 Android 7.0 或更高版本的设备上启动您的应用,并尝试将应用置于分屏模式。验证在应用被强制调整大小时,用户体验是否可接受。

如果应用声明了固定方向,您应该尝试将应用置于多窗口模式。验证当您这样做时,应用是否保持全屏模式。

API 级别 24 到 30

如果您的应用面向 API 级别 24 到 30 且未禁用多窗口支持,请在分屏和桌面窗口模式下验证以下行为:

  • 全屏启动应用,然后长按最近应用按钮切换到多窗口模式。验证应用是否正确切换。

  • 直接在多窗口模式下启动应用,并验证应用是否正确启动。您可以通过按最近应用按钮,然后长按应用标题栏并将其拖动到屏幕上的高亮区域之一来在多窗口模式下启动应用。

  • 通过拖动屏幕分隔条在分屏模式下调整应用大小。验证应用是否在不崩溃的情况下调整大小,并且必要的 UI 元素可见。

  • 如果您为应用指定了最小尺寸,请尝试调整应用大小,使其窗口尺寸小于这些尺寸。验证您无法将应用调整到小于指定的最小尺寸。

  • 在所有测试中,验证您应用的性能是否可接受。例如,验证调整应用大小后更新 UI 的延迟是否不会太长。

API 级别 31 或更高

如果您的应用面向 API 级别 31 或更高版本,并且主 activity 的最小宽度和最小高度小于或等于可用显示区域的相应尺寸,请验证API 级别 24 到 30中列出的所有行为。

测试清单

要验证您的应用在多窗口模式下的性能,请尝试以下操作。除了另有说明外,您应在分屏和桌面窗口模式下尝试这些操作。

  • 进入和退出多窗口模式。

  • 从您的应用切换到另一个应用,并验证当您的应用可见但不活动时,其行为是否正常。例如,如果您的应用正在播放视频,请验证在用户与另一个应用互动时,视频是否继续播放。

  • 在分屏模式下,尝试移动屏幕分隔条以使您的应用更大和更小。在并排和上下配置中尝试这些操作。验证应用是否未崩溃,基本功能是否可见,以及调整大小操作不会花费太长时间。

  • 连续快速执行多次调整大小操作。验证您的应用是否不会崩溃或内存泄漏。 Android Studio 的内存分析器提供有关您应用内存使用情况的信息(请参阅使用内存分析器检查应用内存使用情况)。

  • 在多种不同的窗口配置中正常使用您的应用,并验证应用的行为是否正常。验证文本是否可读,并且 UI 元素不会太小而无法互动。

多窗口支持已禁用

在 API 级别 24 到 30 上,如果您通过设置android:resizeableActivity="false"禁用了多窗口支持,则应在运行 Android 7.0 到 11 的设备上启动您的应用,并尝试将应用置于分屏和桌面窗口模式。验证当您这样做时,应用是否保持全屏模式。

其他资源

有关 Android 中多窗口支持的更多信息,请参阅: