多窗口模式允许多个应用同时共享同一屏幕。应用可以并排显示或一个在上一个在下(分屏模式),一个应用以小窗口的形式覆盖其他应用(画中画模式),或者各个应用在独立的可移动、可调整大小的窗口中(自由窗格模式)。
用户体验取决于 Android 版本和设备类型。
Android 7.0(API 级别 24)在小屏幕设备上引入了分屏模式,并在特定设备上引入了画中画模式。
分屏模式使用两个应用填充屏幕,将它们并排显示或一个在上一个在下。用户可以拖动分隔两个应用的分隔线,使一个应用变大,另一个应用变小。
画中画模式允许用户在与另一个应用交互时继续播放视频(请参阅画中画支持)。
自由窗格模式允许用户自由调整每个活动的尺寸,可以由大屏幕设备的制造商启用。
您可以通过指定活动的最小允许尺寸来配置您的应用如何处理多窗口模式。您还可以通过设置
resizeabableActivity="false"
来禁用应用的多窗口模式,以确保系统始终以全屏显示您的应用。
Android 8.0(API 级别 26)将画中画模式扩展到小屏幕设备。
Android 12(API 级别 31)使多窗口模式成为标准行为。
分屏模式
用户可以通过以下操作激活分屏模式
- 打开最近使用的应用屏幕
- 滑动一个应用使其可见
- 按应用标题栏中的应用图标
- 选择分屏菜单选项
- 从最近使用的应用屏幕中选择另一个应用,或关闭最近使用的应用屏幕并运行另一个应用
用户可以通过将窗口分隔线拖动到屏幕边缘(向上或向下、向左或向右)来退出分屏模式。
启动相邻应用
如果您的应用需要通过意图访问内容,则可以使用FLAG_ACTIVITY_LAUNCH_ADJACENT
在相邻的分屏窗口中打开内容。
FLAG_ACTIVITY_LAUNCH_ADJACENT
是在 Android 7.0(API 级别 24)中引入的,用于允许在分屏模式下运行的应用在相邻窗口中启动活动。
Android 12L(API 级别 32)及更高版本扩展了该标志的定义,以允许以全屏模式运行的应用激活分屏模式,然后在相邻窗口中启动活动。
要启动相邻活动,请将FLAG_ACTIVITY_LAUNCH_ADJACENT
与FLAG_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) } }
多窗口模式下的活动生命周期
多窗口模式不会更改活动生命周期。但是,在不同版本的 Android 上,多个窗口中应用的恢复状态会有所不同。
多恢复
Android 10(API 级别 29)及更高版本支持多恢复 - 当设备处于多窗口模式时,所有活动都保持在RESUMED
状态。如果透明活动位于活动之上或活动不可聚焦,则活动可能会暂停,例如,活动处于画中画模式。也有可能在给定时间没有活动获得焦点,例如,如果通知抽屉已打开。onStop()
方法按通常方式工作:该方法在每次活动从屏幕上移开时都会被调用。
在运行 Android 9(API 级别 28)的特定设备上也提供了多恢复。要在 Android 9 设备上启用多恢复,请添加以下清单元数据
<meta-data android:name="android.allow_multiple_resumed_activities" android:value="true" />
要验证给定设备是否支持此清单元数据,请参阅设备规格。
Android 9
在 Android 9(API 级别 28)及更低版本上的多窗口模式下,在给定时间,只有用户最近交互过的活动处于活动状态。此活动被认为是最顶层的,并且是唯一处于RESUMED
状态的活动。所有其他可见活动都处于STARTED
状态,但未处于RESUMED
状态。但是,系统会为这些可见但未恢复的活动提供比不可见活动更高的优先级。如果用户与其中一个可见活动进行交互,则该活动将恢复,而先前最顶层的活动将进入STARTED
状态。
当单个活动应用进程中存在多个活动时,z 顺序最高的活动将恢复,其他活动将暂停。
配置更改
当用户将应用置于多窗口模式时,系统会通知活动配置更改,如处理配置更改中所述。当用户调整应用大小或将应用恢复到全屏模式时,也会发生这种情况。
从本质上讲,此更改与系统通知应用设备已从纵向切换到横向时的活动生命周期含义相同,只是应用尺寸发生了更改,而不仅仅是交换。您的活动可以自行处理配置更改,或者您的应用可以允许系统销毁活动并使用新尺寸重新创建它。
如果用户正在调整窗口大小并在任一维度上使其更大,则系统会调整活动大小以匹配用户操作并根据需要发出配置更改。如果应用在绘制新公开区域方面滞后,则系统会使用windowBackground
属性或默认windowBackgroundFallback
样式属性中指定的颜色临时填充这些区域。
独占资源访问
为了帮助支持多恢复功能,请使用onTopResumedActivityChanged()
生命周期回调。
当活动获得或失去最顶层恢复活动位置时,会调用此回调,这在活动使用共享单例资源(如麦克风或摄像头)时非常重要。
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
不能保证独占摄像头访问权限,因为其他使用摄像头的应用可以在其他显示器上打开。
您的应用不一定需要在应用失去焦点时释放摄像头。例如,您可能希望在用户与新获得焦点的最顶层恢复应用交互时继续摄像头预览。您的应用在不是最顶层恢复应用时继续运行摄像头是可以的,但它必须正确处理断开连接的情况。当最顶层恢复应用想要使用摄像头时,它可以打开它,您的应用将失去访问权限。您的应用可以在获得焦点后重新打开摄像头。
在应用收到CameraDevice.StateCallback#onDisconnected()
回调后,对摄像头设备的后续调用将抛出CameraAccessException
。
多显示器
Android 10(API 级别 29)支持辅助显示器上的活动。如果活动在具有多个显示器的设备上运行,则用户可以将活动从一个显示器移动到另一个显示器。多恢复也适用于多屏幕场景;多个活动可以同时接收用户输入。
应用可以在启动时或创建另一个活动时指定应在其上运行的显示器。此行为取决于清单文件中定义的活动启动模式以及启动活动的实体设置的意图标志和选项。有关更多详细信息,请参阅ActivityOptions
类。
当活动转移到辅助显示屏时,它可能会经历上下文更新、窗口大小调整以及配置和资源更改。如果活动处理配置更改,则会在onConfigurationChanged()
中收到通知;否则,活动将重新启动。
如果活动处理配置更改,则应在onCreate()
和onConfigurationChanged()
中检查当前显示屏。确保在显示屏更改时更新资源和布局。
如果活动的所选启动模式允许多个实例,则在辅助屏幕上启动可能会创建活动的新实例。这两个活动将同时恢复。
您可能还想了解 Android 8.0 中引入的多显示屏 API。
活动与应用程序上下文
在多显示屏中使用正确的上下文至关重要。访问资源时,活动上下文(正在显示的)与应用程序上下文(未显示的)不同。
活动上下文包含有关显示屏的信息,并且始终根据活动显示的显示屏区域进行调整。这使您能够获取有关应用程序的显示屏密度或窗口指标的正确信息。您应始终使用活动上下文(或其他基于 UI 的上下文)来获取有关当前窗口或显示屏的信息。这还会影响使用上下文信息的一些系统 API(例如,请参阅Toast 概述)。
活动窗口配置和父显示屏定义资源和上下文。获取当前显示屏的方法如下
val activityDisplay = activity.getDisplay()
获取当前活动窗口指标
val windowMetrics = activity.getWindowManager().getCurrentWindowMetrics()
获取当前系统配置的最大窗口指标
val maximumWindowMetrics = activity.getWindowManager().getMaximumWindowMetrics()
最大窗口指标用于进行计算、布局选择或确定提前获取资源的大小。在onCreate()
中提供此信息使您能够在第一次布局传递之前做出这些决策。这些指标不应用于布局特定的视图元素;而应使用Configuration
对象中的信息。
显示屏挖孔
折叠式设备在折叠和展开时可能具有不同的挖孔几何形状。要避免挖孔问题,请参阅支持显示屏挖孔。
辅助显示屏
您可以从DisplayManager
系统服务获取可用的显示屏
val displayManager =
getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val displays =
displayManager.getDisplays()
使用Display
类获取有关特定显示屏的信息,例如显示屏大小或指示显示屏是否安全的标志。但是,不要假设显示屏大小将与分配给应用程序的显示屏区域相同。请记住,在多窗口模式下,您的应用程序占据显示屏的一部分。
确定活动是否可以在显示屏上启动
val activityManager =
getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager val
activityAllowed = activityManager.isActivityStartAllowedOnDisplay(context,
displayId, intent)
然后在显示屏上启动活动
val options = ActivityOptions.makeBasic()
options.setLaunchDisplayId(targetDisplay.displayId) startActivity(intent,
options.toBundle())
多显示屏支持
Android 为软件键盘、壁纸和启动器提供多显示屏支持。
软件键盘
如果显示屏配置为支持系统装饰,则可以在辅助屏幕上显示键盘。如果文本字段在该显示屏上请求输入,则输入法编辑器会自动出现。
壁纸
在 Android 10(API 级别 29)中,辅助屏幕可以有壁纸。框架为每个显示屏创建一个单独的WallpaperService.Engine
实例。确保每个引擎的表面独立绘制。开发人员可以使用显示屏上下文在WallpaperService.Engine#getDisplayContext()
中加载资产。此外,请确保您的WallpaperInfo.xml
文件设置了android:supportsMultipleDisplays="true"
。
启动器
一个新的意图过滤器类别SECONDARY_HOME
提供了一个专用于辅助屏幕的活动。该活动的实例用于所有支持系统装饰的显示屏,每个显示屏一个。
<activity>
...
<intent-filter>
<category android:name="android.intent.category.SECONDARY_HOME" />
...
</intent-filter>
</activity>
该活动必须具有不阻止多个实例并且可以适应不同屏幕尺寸的启动模式。启动模式不能是singleInstance
或singleTask
。
例如,Launcher3
的 AOSP 实现支持SECONDARY_HOME
活动。
窗口指标
Android 11(API 级别 30)引入了以下WindowManager
方法来提供在多窗口模式下运行的应用程序的边界
getCurrentWindowMetrics()
:返回一个WindowMetrics
对象,用于系统的当前窗口状态。getMaximumWindowMetrics()
:返回WindowMetrics
,用于系统的最大潜在窗口状态。
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)或更高版本为目标,则可以配置应用程序的活动如何以及是否支持多窗口模式。您可以在清单中设置属性来控制大小和布局。根活动的属性设置适用于其任务堆栈中的所有活动。例如,如果根活动具有android:resizeableActivity="true"
,则任务堆栈中的所有活动都可调整大小。在某些较大的设备(例如 Chromebook)上,即使您指定了android:resizeableActivity="false"
,您的应用也可能在可调整大小的窗口中运行。如果这会破坏您的应用,则可以使用Google Play 上的过滤器来限制您的应用在这些设备上的可用性。
Android 12(API 级别 31)默认为多窗口模式。在大屏幕(中等或扩展窗口大小类)上,所有应用都在多窗口模式下运行,无论应用配置如何。在小屏幕上,系统会检查活动的minWidth
、minHeight
和resizeableActivity
设置以确定活动是否可以在多窗口模式下运行。
resizeableActivity
在清单的<activity>
或<application>
元素中设置此属性以启用或禁用 API 级别 30 及更低版本的活动的多窗口模式
<application
android:name=".MyActivity"
android:resizeableActivity=["true" | "false"] />;
如果此属性设置为true
,则可以在分屏和自由窗格模式下启动活动。如果属性设置为false
,则活动不支持多窗口模式。如果值为 false,并且用户尝试在多窗口模式下启动活动,则活动将占据整个屏幕。
如果您的应用以 API 级别 24 或更高版本为目标,但您未为此属性指定值,则该属性的值默认为 true。
如果您的应用以 API 级别 31 或更高版本为目标,则此属性在小屏幕和大屏幕上的工作方式不同
- 大屏幕(中等或扩展窗口大小类):所有应用都支持多窗口模式。该属性指示活动是否可以调整大小。如果
resizeableActivity="false"
,则在必要时将应用置于兼容模式以符合显示尺寸。 - 小屏幕(紧凑窗口大小类):如果
resizeableActivity="true"
并且活动的最小宽度和最小高度在多窗口要求范围内,则活动支持多窗口模式。如果resizeableActivity="false"
,则活动不支持多窗口模式,无论活动的最小宽度和高度如何。
supportsPictureInPicture
在清单的<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:defaultHeight
、android:defaultWidth
:在自由窗格模式下启动 Activity 时的默认高度和宽度。android:gravity
:在自由窗格模式下启动 Activity 时的初始位置。有关合适的取值,请参阅Gravity
类。android:minHeight
、android:minWidth
: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 的功能。
此外,某些系统 UI 自定义选项也会被禁用。例如,如果应用在多窗口模式下运行,则无法隐藏状态栏(请参阅控制系统 UI 可见性)。
系统会忽略对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。您可以通过创建 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 设置了android:resizeableActivity="true"
并且未使用阻止多个实例的启动模式。例如,当设置FLAG_ACTIVITY_MULTIPLE_TASK
或FLAG_ACTIVITY_NEW_DOCUMENT
时,singleInstancePerTask
Activity 可以在不同的任务中多次实例化。
请勿将多实例与多面板布局混淆,例如使用SlidingPaneLayout
的列表-详情展示,后者在一个窗口内运行。
请注意,当多个实例在可折叠设备上的单独窗口中运行时,如果姿势发生变化,则可能会将一个或多个实例发送到后台。例如,假设设备展开并且有两个应用实例在折叠的两侧的两个窗口中运行。如果设备折叠,则可能会终止其中一个实例,而不是尝试在较小的屏幕上为两个实例的窗口都留出空间。
多窗口模式验证
无论您的应用是否以 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 中多窗口支持的更多信息,请参阅
- Android MultiWindowPlayground 示例
推荐内容 * 注意:当 JavaScript 关闭时显示链接文本 * 设备兼容模式 * 支持大屏幕可调整大小 * 处理配置更改