Activity 嵌入通过将应用的任务窗口拆分给两个 Activity 或同一 Activity 的两个实例来优化大屏设备上的应用。

如果您的应用包含多个 Activity,Activity 嵌入可让您在平板电脑、可折叠设备和 ChromeOS 设备上提供增强的用户体验。
Activity 嵌入无需代码重构。您可以通过创建 XML 配置文件或调用Jetpack WindowManager API 来确定应用如何显示其 Activity(并排或堆叠)。
小屏设备的支持会自动维护。当您的应用在小屏设备上运行时,Activity 会相互堆叠。在大屏幕上,Activity 会并排显示。系统根据您创建的配置决定显示方式,无需分支逻辑。
Activity 嵌入适应设备方向变化,并在可折叠设备上无缝工作,在设备折叠和展开时堆叠和取消堆叠 Activity。
大多数运行 Android 12L(API 级别 32)及更高版本的大屏设备都支持 Activity 嵌入。
拆分任务窗口
Activity 嵌入将应用任务窗口拆分为两个容器:主容器和副容器。这些容器容纳从主 Activity 或容器中已有的其他 Activity 启动的 Activity。
Activity 在启动时堆叠在副容器中,在小屏幕上,副容器堆叠在主容器上方,因此 Activity 堆叠和返回导航与应用中已有的 Activity 顺序一致。
Activity 嵌入让您能够以多种方式显示 Activity。您的应用可以通过同时并排或上下启动两个 Activity 来拆分任务窗口


占用整个任务窗口的 Activity 可以通过在旁边启动一个新 Activity 来创建拆分

已处于拆分状态并共享任务窗口的 Activity 可以通过以下方式启动其他 Activity
在另一 Activity 上方并排显示
图 4. Activity A 在 Activity B 上方并排启动 Activity C。 并排显示,并向侧面移动拆分,隐藏先前的 Primary Activity
图 5. Activity B 在旁边启动 Activity C 并向侧面移动拆分。 就地在顶部启动 Activity;也就是说,在同一个 Activity 堆栈中
图 6. Activity B 启动 Activity C,不带任何额外 Intent 标志。 在同一个任务中全窗口启动 Activity
图 7. Activity A 或 Activity B 启动 Activity C,后者填充任务窗口。
返回导航
不同类型的应用在拆分任务窗口状态下可能具有不同的返回导航规则,具体取决于 Activity 之间的依赖关系或用户如何触发返回事件,例如
- 一起退出:如果 Activity 相互关联,并且一个 Activity 不能在没有另一个的情况下显示,则可以将返回导航配置为同时结束两者。
- 独立退出:如果 Activity 完全独立,则在某个 Activity 上的返回导航不会影响任务窗口中另一个 Activity 的状态。
使用按钮导航时,返回事件会发送给最后获得焦点的 Activity。
对于手势导航
Android 14(API 级别 34)及更低版本 — 返回事件会发送到发生手势的 Activity。当用户从屏幕左侧滑动时,返回事件会发送到拆分窗口左侧窗格中的 Activity。当用户从屏幕右侧滑动时,返回事件会发送到右侧窗格中的 Activity。
Android 15(API 级别 35)及更高版本
处理同一应用的多个 Activity 时,无论滑动方向如何,手势都会结束顶层 Activity,从而提供更统一的体验。
在涉及来自不同应用的两个 Activity(叠加)的场景中,返回事件会定向到最后获得焦点的 Activity,这与按钮导航的行为一致。
多窗格布局
Jetpack WindowManager 允许您在运行 Android 12L(API 级别 32)或更高版本的大屏设备以及某些运行较早平台版本的设备上构建 Activity 嵌入式多窗格布局。基于多个 Activity 而非 Fragment 或基于视图的布局(例如 SlidingPaneLayout
)的现有应用可以在无需重构源代码的情况下提供改进的大屏用户体验。
一个常见的示例是列表-详情拆分。为了确保高质量的呈现,系统首先启动列表 Activity,然后应用立即启动详情 Activity。过渡系统会等到两个 Activity 都绘制完毕,然后将它们一起显示。对用户而言,这两个 Activity 是同时启动的。

拆分属性
您可以指定任务窗口在拆分容器之间如何分配比例以及容器之间如何相对布局。
对于 XML 配置文件中定义的规则,设置以下属性
splitRatio
:设置容器的比例。该值是开区间 (0.0, 1.0) 中的浮点数。splitLayoutDirection
:指定拆分容器之间如何相对布局。值包括ltr
:从左到右rtl
:从右到左locale
:ltr
或rtl
从语言区域设置确定
有关示例,请参阅XML 配置部分。
对于使用 WindowManager API 创建的规则,使用 SplitAttributes.Builder
创建一个 SplitAttributes
对象,并调用以下构建器方法
setSplitType()
:设置拆分容器的比例。有关有效参数,请参阅SplitAttributes.SplitType
,包括SplitAttributes.SplitType.ratio()
方法。setLayoutDirection()
:设置容器的布局。有关可能的值,请参阅SplitAttributes.LayoutDirection
。
有关示例,请参阅WindowManager API部分。

拆分方向
显示器的尺寸和纵横比决定了 Activity 嵌入拆分中 Activity 的位置。在大型横向显示器上,Activity 并排显示;在高纵向显示器或可折叠设备桌面模式下,Activity 上下显示。
您可以使用 SplitController
SplitAttributes
计算器指定拆分方向。计算器会为活动的 SplitRule
计算 SplitAttributes
。
使用计算器为不同的设备状态以不同方向拆分父容器,例如
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val parentConfiguration = params.parentConfiguration val builder = SplitAttributes.Builder() return@setSplitAttributesCalculator if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build() } else { // Fallback to expand the secondary container. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { Configuration parentConfiguration = params.getParentConfiguration(); SplitAttributes.Builder builder = new SplitAttributes.Builder(); if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenHeightDp >= 600) { // Horizontal split for tall displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.BOTTOM_TO_TOP) .build(); } else { // Fallback to expand the secondary container. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
在可折叠设备上,如果设备处于横向模式,您可以垂直拆分屏幕;如果设备处于纵向模式,则显示单个 Activity;如果设备处于桌面模式,则水平拆分屏幕
Kotlin
if (WindowSdkExtensions.getInstance().extensionVersion >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator { params -> val tag = params.splitRuleTag val parentWindowMetrics = params.parentWindowMetrics val parentConfiguration = params.parentConfiguration val foldingFeatures = params.parentWindowLayoutInfo.displayFeatures.filterIsInstance<FoldingFeature>() val feature = if (foldingFeatures.size == 1) foldingFeatures[0] else null val builder = SplitAttributes.Builder() builder.setSplitType(SPLIT_TYPE_HINGE) return@setSplitAttributesCalculator if (feature?.isSeparating == true) { // Horizontal split for tabletop posture. builder .setSplitType(SPLIT_TYPE_HINGE) .setLayoutDirection( if (feature.orientation == FoldingFeature.Orientation.HORIZONTAL) { SplitAttributes.LayoutDirection.BOTTOM_TO_TOP } else { SplitAttributes.LayoutDirection.LOCALE } ) .build() } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build() } else { // No split for tall displays. builder .setSplitType(SPLIT_TYPE_EXPAND) .build() } } }
Java
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 2) { SplitController.getInstance(this).setSplitAttributesCalculator(params -> { String tag = params.getSplitRuleTag(); WindowMetrics parentWindowMetrics = params.getParentWindowMetrics(); Configuration parentConfiguration = params.getParentConfiguration(); List<FoldingFeature> foldingFeatures = params.getParentWindowLayoutInfo().getDisplayFeatures().stream().filter( item -> item instanceof FoldingFeature) .map(item -> (FoldingFeature) item) .collect(Collectors.toList()); FoldingFeature feature = foldingFeatures.size() == 1 ? foldingFeatures.get(0) : null; SplitAttributes.Builder builder = new SplitAttributes.Builder(); builder.setSplitType(SplitType.SPLIT_TYPE_HINGE); if (feature != null && feature.isSeparating()) { // Horizontal slit for tabletop posture. return builder .setSplitType(SplitType.SPLIT_TYPE_HINGE) .setLayoutDirection( feature.getOrientation() == FoldingFeature.Orientation.HORIZONTAL ? SplitAttributes.LayoutDirection.BOTTOM_TO_TOP : SplitAttributes.LayoutDirection.LOCALE) .build(); } else if (parentConfiguration.screenWidthDp >= 840) { // Side-by-side dual-pane layout for wide displays. return builder .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE) .build(); } else { // No split for tall displays. return builder .setSplitType(SplitType.SPLIT_TYPE_EXPAND) .build(); } }); }
占位符
占位符 Activity 是空的辅助 Activity,它们占用 Activity 拆分中的某个区域。它们最终将被另一个包含内容的 Activity 替换。例如,在列表-详情布局中,占位符 Activity 可以占用 Activity 拆分的辅助侧,直到从列表中选择一个项,此时包含选定列表项详细信息的 Activity 会替换占位符。
默认情况下,系统仅在 Activity 拆分有足够的空间时显示占位符。当显示尺寸更改为太小而无法显示拆分的宽度或高度时,占位符会自动结束。当空间允许时,系统会使用重新初始化的状态重新启动占位符。

但是,SplitPlaceholderRule
的 stickyPlaceholder
属性或 SplitPlaceholder.Builder
的 setSticky()
方法可以覆盖默认行为。当属性或方法指定值为 true
时,当显示器从双窗格显示器缩小到单窗格显示器时,系统会将占位符显示为任务窗口中最顶部的 Activity(有关示例,请参阅拆分配置)。

窗口大小更改
当设备配置更改减小任务窗口宽度,使其不足以容纳多窗格布局时(例如,当大屏可折叠设备从平板电脑尺寸折叠成手机尺寸或应用窗口在多窗口模式下调整大小时),任务窗口辅助窗格中的非占位符 Activity 会堆叠在主窗格的 Activity 上方。
占位符 Activity 仅在拆分有足够的显示宽度时显示。在较小的屏幕上,占位符会自动关闭。当显示区域再次足够大时,占位符会重新创建。(请参阅占位符部分。)
Activity 堆叠之所以可行,是因为 WindowManager 将辅助窗格中的 Activity 按 z 轴顺序排在主窗格中的 Activity 上方。
辅助窗格中的多个 Activity
Activity B 就地启动 Activity C,不带任何额外 intent 标志
在同一个任务中产生以下 Activity 的 z 轴顺序
因此,在较小的任务窗口中,应用会缩小到只有一个 Activity,C 在堆栈顶部
在较小的窗口中执行返回导航会在相互堆叠的 Activity 之间导航。
如果任务窗口配置恢复到可以容纳多个窗格的较大尺寸,Activity 将再次并排显示。
堆叠拆分
Activity B 在旁边启动 Activity C 并向侧面移动拆分
结果是在同一个任务中产生以下 Activity 的 z 轴顺序
在较小的任务窗口中,应用会缩小到只有一个 Activity,C 在顶部
固定纵向方向
通过清单设置 android:screenOrientation,应用可以将 Activity 限制在纵向或横向。为了改善平板电脑和可折叠设备等大屏设备上的用户体验,设备制造商 (OEM) 可以忽略屏幕方向请求,并在横向显示器上以纵向模式或在纵向显示器上以横向模式对应用进行信箱模式处理。

类似地,当 Activity 嵌入启用时,OEM 可以自定义设备,以便在大型屏幕(宽度 ≥ 600dp)的横向模式下对固定纵向 Activity 进行信箱模式处理。当固定纵向 Activity 启动第二个 Activity 时,设备可以在双窗格显示屏中并排显示这两个 Activity。

请务必将 android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
属性添加到应用的清单文件,以通知设备您的应用支持 Activity 嵌入(请参阅拆分配置部分)。然后,OEM 定制的设备可以确定是否对固定纵向 Activity 进行信箱模式处理。
拆分配置
拆分规则配置 Activity 拆分。您可以在 XML 配置文件中定义拆分规则,或通过调用 Jetpack WindowManager API 来定义。
无论哪种情况,您的应用都必须访问 WindowManager 库,并且必须通知系统应用已实现 Activity 嵌入。
执行以下操作
将最新的 WindowManager 库依赖项添加到应用的模块级
build.gradle
文件,例如implementation 'androidx.window:window:1.1.0-beta02'
WindowManager 库提供 Activity 嵌入所需的所有组件。
通知系统您的应用已实现 Activity 嵌入。
将
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
属性添加到应用清单文件的 <application> 元素,并将值设置为 true,例如<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <application> <property android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED" android:value="true" /> </application> </manifest>
在 WindowManager 1.1.0-alpha06 及更高版本中,除非将该属性添加到清单并设置为 true,否则 Activity 嵌入拆分处于禁用状态。
此外,设备制造商使用该设置来为支持 Activity 嵌入的应用启用自定义功能。例如,设备可以在横向显示器上对仅纵向 Activity 进行信箱模式处理,以便在启动第二个 Activity 时使 Activity 适应过渡到双窗格布局的方向(请参阅固定纵向方向)。
XML 配置
要创建基于 XML 的 Activity 嵌入实现,请完成以下步骤
创建一个执行以下操作的 XML 资源文件
- 定义共享拆分的 Activity
- 配置拆分选项
- 当内容不可用时,为拆分的辅助容器创建一个占位符
- 指定不应成为拆分一部分的 Activity
例如
<!-- main_split_config.xml --> <resources xmlns:window="http://schemas.android.com/apk/res-auto"> <!-- Define a split for the named activities. --> <SplitPairRule window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:finishPrimaryWithSecondary="never" window:finishSecondaryWithPrimary="always" window:clearTop="false"> <SplitPairFilter window:primaryActivityName=".ListActivity" window:secondaryActivityName=".DetailActivity"/> </SplitPairRule> <!-- Specify a placeholder for the secondary container when content is not available. --> <SplitPlaceholderRule window:placeholderActivityName=".PlaceholderActivity" window:splitRatio="0.33" window:splitLayoutDirection="locale" window:splitMinWidthDp="840" window:splitMaxAspectRatioInPortrait="alwaysAllow" window:stickyPlaceholder="false"> <ActivityFilter window:activityName=".ListActivity"/> </SplitPlaceholderRule> <!-- Define activities that should never be part of a split. Note: Takes precedence over other split rules for the activity named in the rule. --> <ActivityRule window:alwaysExpand="true"> <ActivityFilter window:activityName=".ExpandedActivity"/> </ActivityRule> </resources>
创建初始化程序。
WindowManager
RuleController
组件会解析 XML 配置文件,并将规则提供给系统。Jetpack Startup 库的Initializer
在应用启动时将 XML 文件提供给RuleController
,以便在任何 Activity 启动时规则都生效。要创建初始化程序,请执行以下操作
将最新的 Jetpack Startup 库依赖项添加到您的模块级
build.gradle
文件,例如implementation 'androidx.startup:startup-runtime:1.1.1'
创建一个实现
Initializer
接口的类。初始化程序通过将 XML 配置文件(
main_split_config.xml
)的 ID 传递给RuleController.parseRules()
方法,使拆分规则对RuleController
可用。Kotlin
class SplitInitializer : Initializer<RuleController> { override fun create(context: Context): RuleController { return RuleController.getInstance(context).apply { setRules(RuleController.parseRules(context, R.xml.main_split_config)) } } override fun dependencies(): List<Class<out Initializer<*>>> { return emptyList() } }
Java
public class SplitInitializer implements Initializer<RuleController> { @NonNull @Override public RuleController create(@NonNull Context context) { RuleController ruleController = RuleController.getInstance(context); ruleController.setRules( RuleController.parseRules(context, R.xml.main_split_config) ); return ruleController; } @NonNull @Override public List<Class<? extends Initializer<?>>> dependencies() { return Collections.emptyList(); } }
为规则定义创建内容提供程序。
将
androidx.startup.InitializationProvider
作为<provider>
添加到您的应用清单文件。包含对RuleController
初始化程序SplitInitializer
实现的引用<!-- AndroidManifest.xml --> <provider android:name="androidx.startup.InitializationProvider" android:authorities="${applicationId}.androidx-startup" android:exported="false" tools:node="merge"> <!-- Make SplitInitializer discoverable by InitializationProvider. --> <meta-data android:name="${applicationId}.SplitInitializer" android:value="androidx.startup" /> </provider>
InitializationProvider
在应用调用onCreate()
方法之前发现并初始化SplitInitializer
。因此,当应用的主 Activity 启动时,拆分规则就已生效。
WindowManager API
您可以使用少量 API 调用以编程方式实现 Activity 嵌入。在 Application
子类的 onCreate()
方法中进行调用,以确保在任何 Activity 启动之前规则都已生效。
要以编程方式创建 Activity 拆分,请执行以下操作
创建拆分规则
创建一个
SplitPairFilter
,用于标识共享拆分的 ActivityKotlin
val splitPairFilter = SplitPairFilter( ComponentName(this, ListActivity::class.java), ComponentName(this, DetailActivity::class.java), null )
Java
SplitPairFilter splitPairFilter = new SplitPairFilter( new ComponentName(this, ListActivity.class), new ComponentName(this, DetailActivity.class), null );
将过滤器添加到过滤器集
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
为拆分创建布局属性
Kotlin
val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build()
Java
SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build();
SplitAttributes.Builder
创建一个包含布局属性的对象setSplitType()
:定义如何将可用显示区域分配给每个 Activity 容器。比例拆分类型指定分配给主容器的可用显示区域比例;辅助容器占用剩余的可用显示区域。setLayoutDirection()
:指定 Activity 容器之间如何相对布局,主容器在前。
-
Kotlin
val splitPairRule = SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build()
Java
SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet) .setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER) .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS) .setClearTop(false) .build();
SplitPairRule.Builder
创建和配置规则filterSet
:包含拆分对过滤器,通过标识共享拆分的 Activity 来确定何时应用规则。setDefaultSplitAttributes()
:将布局属性应用于规则。setMinWidthDp()
:设置启用拆分的最小显示宽度(以密度无关像素 dp 为单位)。setMinSmallestWidthDp()
:设置两个显示尺寸中较小的一个必须具有的最小值(以 dp 为单位),无论设备方向如何,都可以启用拆分。setMaxAspectRatioInPortrait()
:设置显示 Activity 拆分时在纵向方向上的最大显示纵横比(高:宽)。如果纵向显示器的纵横比超过最大纵横比,则无论显示器宽度如何,都会禁用拆分。注意:默认值为 1.4,这会导致在大多数平板电脑上,Activity 在纵向模式下会占用整个任务窗口。另请参阅SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
和setMaxAspectRatioInLandscape()
。横向的默认值为ALWAYS_ALLOW
。setFinishPrimaryWithSecondary()
:设置结束辅助容器中的所有 Activity 如何影响主容器中的 Activity。NEVER
表示当辅助容器中的所有 Activity 结束时,系统不应结束主 Activity(请参阅结束 Activity)。setFinishSecondaryWithPrimary()
:设置结束主容器中的所有 Activity 如何影响辅助容器中的 Activity。ALWAYS
表示当主容器中的所有 Activity 结束时,系统应始终结束辅助容器中的 Activity(请参阅结束 Activity)。setClearTop()
:指定当在辅助容器中启动新 Activity 时,是否结束辅助容器中的所有 Activity。false
值指定新 Activity 将堆叠在辅助容器中已有的 Activity 之上。
获取 WindowManager
RuleController
的单例实例,并添加规则Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
当内容不可用时,为辅助容器创建占位符
创建一个
ActivityFilter
,用于标识占位符与哪个 Activity 共享任务窗口拆分Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
将过滤器添加到过滤器集
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
-
Kotlin
val splitPlaceholderRule = SplitPlaceholderRule.Builder( placeholderActivityFilterSet, Intent(context, PlaceholderActivity::class.java) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build()
Java
SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder( placeholderActivityFilterSet, new Intent(this, PlaceholderActivity.class) ).setDefaultSplitAttributes(splitAttributes) .setMinWidthDp(840) .setMinSmallestWidthDp(600) .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f)) .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS) .setSticky(false) .build();
SplitPlaceholderRule.Builder
创建和配置规则placeholderActivityFilterSet
:包含 Activity 过滤器,通过标识与占位符 Activity 相关联的 Activity 来确定何时应用规则。Intent
:指定占位符 Activity 的启动。setDefaultSplitAttributes()
:将布局属性应用于规则。setMinWidthDp()
:设置允许拆分的最小显示宽度(以密度无关像素 dp 为单位)。setMinSmallestWidthDp()
:设置两个显示尺寸中较小的一个必须具有的最小值(以 dp 为单位),无论设备方向如何,都可以允许拆分。setMaxAspectRatioInPortrait()
:设置显示 Activity 拆分时在纵向方向上的最大显示纵横比(高:宽)。注意:默认值为 1.4,这会导致在大多数平板电脑上,Activity 在纵向模式下会填充任务窗口。另请参阅SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT
和setMaxAspectRatioInLandscape()
。横向的默认值为ALWAYS_ALLOW
。setFinishPrimaryWithPlaceholder()
:设置结束占位符 Activity 如何影响主容器中的 Activity。ALWAYS 表示当占位符结束时,系统应始终结束主容器中的 Activity(请参阅结束 Activity)。setSticky()
:确定占位符 Activity 在小显示器上是否显示在 Activity 堆栈顶部,前提是该占位符首次出现在具有足够最小宽度的拆分中。
将规则添加到 WindowManager
RuleController
Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
指定不应成为拆分一部分的 Activity
创建一个
ActivityFilter
,用于标识应始终占用整个任务显示区域的 ActivityKotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
将过滤器添加到过滤器集
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
创建
ActivityRule
Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builder
创建和配置规则expandedActivityFilterSet
:包含 Activity 过滤器,通过标识您想要从拆分中排除的 Activity 来确定何时应用规则。setAlwaysExpand()
:指定 Activity 是否应填充整个任务窗口。
将规则添加到 WindowManager
RuleController
Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
跨应用嵌入
在 Android 13(API 级别 33)及更高版本中,应用可以嵌入来自其他应用的 Activity。跨应用(或跨 UID)的 Activity 嵌入实现了来自多个 Android 应用的 Activity 的视觉集成。系统将主机应用的 Activity 和来自其他应用的嵌入式 Activity 在屏幕上并排或上下显示,就像单应用 Activity 嵌入一样。
例如,Settings 应用可以嵌入 WallpaperPicker 应用的壁纸选择器 Activity

信任模型
嵌入其他应用 Activity 的主机进程能够重新定义嵌入式 Activity 的呈现方式,包括大小、位置、剪裁和透明度。恶意主机可以利用此功能误导用户并创建点击劫持或其他 UI 重绘攻击。
为防止滥用跨应用 Activity 嵌入,Android 要求应用选择加入以允许嵌入其 Activity。应用可以将主机指定为受信任或不受信任。
受信任的主机
要允许其他应用嵌入并完全控制您应用的 Activity 呈现方式,请在应用的清单文件的 <activity>
或 <application>
元素的 android:knownActivityEmbeddingCerts
属性中指定主机应用的 SHA-256 证书。
将 android:knownActivityEmbeddingCerts
的值设置为字符串
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
... />
或者,要指定多个证书,使用字符串数组
<activity
android:name=".MyEmbeddableActivity"
android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
... />
它引用了如下所示的资源
<resources>
<string-array name="known_host_certificate_digests">
<item>cert1</item>
<item>cert2</item>
...
</string-array>
</resources>
应用所有者可以通过运行 Gradle signingReport
任务获取 SHA 证书摘要。证书摘要是没有分隔冒号的 SHA-256 指纹。有关详细信息,请参阅运行签名报告和验证客户端。
不受信任的主机
要允许任何应用嵌入您的应用 Activity 并控制其呈现方式,请在应用清单的 <activity>
或 <application>
元素中指定 android:allowUntrustedActivityEmbedding
属性,例如
<activity
android:name=".MyEmbeddableActivity"
android:allowUntrustedActivityEmbedding="true"
... />
此属性的默认值为 false,这会阻止跨应用 Activity 嵌入。
自定义身份验证
为降低不受信任的 Activity 嵌入风险,请创建自定义身份验证机制,验证主机身份。如果您知道主机证书,请使用 androidx.security.app.authenticator
库进行身份验证。如果主机在嵌入您的 Activity 后通过身份验证,则可以显示实际内容。否则,您可以通知用户不允许此操作并阻止内容。
使用 Jetpack WindowManager 库的 ActivityEmbeddingController#isActivityEmbedded()
方法检查主机是否正在嵌入您的 Activity,例如
Kotlin
fun isActivityEmbedded(activity: Activity): Boolean { return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity) }
Java
boolean isActivityEmbedded(Activity activity) { return ActivityEmbeddingController.getInstance(context).isActivityEmbedded(activity); }
最小尺寸限制
Android 系统会将应用清单 <layout>
元素中指定的最小高度和宽度应用于嵌入式 Activity。如果应用未指定最小高度和宽度,则应用系统默认值 (sw220dp
)。
如果主机尝试将嵌入式容器的大小调整到小于最小值,则嵌入式容器将扩展并占用整个任务边界。
<activity-alias>
对于受信任或不受信任的 Activity 嵌入,要使其与 <activity-alias>
元素一起工作,android:knownActivityEmbeddingCerts
或 android:allowUntrustedActivityEmbedding
必须应用于目标 Activity,而不是别名。在系统服务器上验证安全性的策略是基于在目标上设置的标志,而不是别名。
主机应用
主机应用实现跨应用 Activity 嵌入的方式与实现单应用 Activity 嵌入的方式相同。SplitPairRule
和 SplitPairFilter
或 ActivityRule
和 ActivityFilter
对象指定嵌入式 Activity 和任务窗口拆分。拆分规则是在 XML 中静态定义的,或者在运行时使用 Jetpack WindowManager API 调用定义的。
如果主机应用尝试嵌入未选择加入跨应用嵌入的 Activity,则该 Activity 会占用整个任务边界。因此,主机应用需要知道目标 Activity 是否允许跨应用嵌入。
如果嵌入式 Activity 在同一个任务中启动了一个新 Activity,并且新 Activity 未选择加入跨应用嵌入,则该 Activity 会占用整个任务边界,而不是叠加在嵌入式容器中的 Activity 上。
只要 Activity 在同一个任务中启动,主机应用就可以不受限制地嵌入其自身的 Activity。
拆分示例
从全窗口拆分

无需重构。您可以在静态或运行时定义拆分的配置,然后调用 Context#startActivity()
,无需任何额外参数。
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
默认拆分
当应用的着陆页被设计用于在大屏幕上拆分为两个容器时,同时创建和呈现两个 Activity 会提供最佳的用户体验。但是,在用户与主容器中的 Activity 交互之前(例如,用户从导航菜单中选择一个项),拆分的辅助容器可能没有可用的内容。占位符 Activity 可以填补空白,直到拆分的辅助容器中可以显示内容(请参阅占位符部分)。

要创建带有占位符的拆分,请创建一个占位符并将其与主 Activity 关联
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
深层链接分屏
深层链接拆分

图 17. 深层链接详情 Activity 在小屏幕上单独显示,但在大屏幕上与列表 Activity 一起显示。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) RuleController.getInstance(this) .addRule(SplitPairRule.Builder(filterSet).build()) startActivity(Intent(this, DetailActivity::class.java)) }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); RuleController.getInstance(this) .addRule(new SplitPairRule.Builder(filterSet).build()); startActivity(new Intent(this, DetailActivity.class)); }
启动请求应路由到主 Activity,目标详情 Activity 应在拆分中启动。系统会根据可用的显示宽度自动选择正确的呈现方式——堆叠或并排。
深层链接目标可能是返回导航堆栈中唯一应提供给用户的 Activity,您可能希望避免关闭详情 Activity 而只留下主 Activity
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".ListActivity"
window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>
相反,您可以使用 finishPrimaryWithSecondary
属性同时结束两个 Activity
请参阅配置属性部分。
拆分容器中的多个 Activity

Kotlin
class DetailActivity : AppCompatActivity() { fun onOpenSubdetail() { startActivity(Intent(this, SubdetailActivity::class.java)) } }
Java
public class DetailActivity extends AppCompatActivity { void onOpenSubdetail() { startActivity(new Intent(this, SubdetailActivity.class)); } }
图 18. 在任务窗口辅助窗格中就地打开的 Activity。
子详情 Activity 放置在详情 Activity 的顶部,将其遮盖

图 19. 从堆栈顶部移除的 Activity。
新任务中的 Activity
当拆分任务窗口中的 Activity 在新任务中启动 Activity 时,新任务与包含拆分的任务是分开的,并全窗口显示。最近使用的应用屏幕显示两个任务:拆分中的任务和新任务。

Activity 替换
Activity 可以在辅助容器堆栈中被替换;例如,当主 Activity 用于顶层导航而辅助 Activity 是选定的目标时。每次从顶层导航进行选择时,应在辅助容器中启动一个新 Activity,并移除先前在那里的 Activity 或 Activity。

如果在导航选择更改时应用未结束辅助容器中的 Activity,则当拆分折叠时(设备折叠时),返回导航可能会令人困惑。例如,如果在主窗格中有菜单,并且辅助窗格中堆叠了屏幕 A 和 B,当用户折叠手机时,B 在 A 上方,A 在菜单上方。当用户从 B 返回导航时,显示的是 A 而不是菜单。
在这种情况下,必须从返回堆栈中移除屏幕 A。
当在现有拆分的上方以新容器侧边启动时,默认行为是将新的辅助容器放在顶部,并保留旧容器在返回堆栈中。您可以配置拆分,以使用 clearTop
清除先前的辅助容器并正常启动新 Activity。
<SplitPairRule
window:clearTop="true">
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenA"/>
<SplitPairFilter
window:primaryActivityName=".Menu"
window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>
Kotlin
inner class MenuActivity : AppCompatActivity() { fun onMenuItemSelected(selectedMenuItem: Int) { startActivity(Intent(this, classForItem(selectedMenuItem))) } }
Java
public class MenuActivity extends AppCompatActivity{ void onMenuItemSelected(int selectedMenuItem) { startActivity(new Intent(this, classForItem(selectedMenuItem))); } }
或者,使用相同的辅助 Activity,并从主(菜单)Activity 发送新的 Intent,这些 Intent 解析到同一个实例,但在辅助容器中触发状态或 UI 更新。
多个拆分
应用可以通过在旁边启动额外的 Activity 来提供多层深层导航。
当辅助容器中的 Activity 在旁边启动一个新 Activity 时,会在现有拆分的上方创建一个新的拆分。

返回堆栈包含所有先前打开的 Activity,因此用户可以在结束 C 后导航到 A/B 拆分。
要创建新的拆分,请从现有的辅助容器中在旁边启动新的 Activity。声明 A/B 和 B/C 拆分的配置,并从 B 正常启动 Activity C
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
<SplitPairFilter
window:primaryActivityName=".B"
window:secondaryActivityName=".C"/>
</SplitPairRule>
Kotlin
class B : AppCompatActivity() { fun onOpenC() { startActivity(Intent(this, C::class.java)) } }
Java
public class B extends AppCompatActivity{ void onOpenC() { startActivity(new Intent(this, C.class)); } }
响应拆分状态变化
应用中的不同 Activity 可以具有执行相同功能的 UI 元素;例如,打开包含帐户设置窗口的控件。

如果两个具有相同 UI 元素的 Activity 处于拆分状态,则在两个 Activity 中都显示该元素是多余的,可能会令人困惑。

要了解 Activity 何时处于拆分状态,请检查 SplitController.splitInfoList
流,或使用 SplitControllerCallbackAdapter
注册监听器以获取拆分状态的变化。然后,相应地调整 UI
Kotlin
val layout = layoutInflater.inflate(R.layout.activity_main, null) val view = layout.findViewById<View>(R.id.infoButton) lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance. .collect { list -> view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE } } }
Java
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); new SplitControllerCallbackAdapter(SplitController.getInstance(this)) .addSplitListener( this, Runnable::run, splitInfoList -> { View layout = getLayoutInflater().inflate(R.layout.activity_main, null); layout.findViewById(R.id.infoButton).setVisibility( splitInfoList.isEmpty() ? View.VISIBLE : View.GONE); }); }
协程可以在任何生命周期状态下启动,但通常在 STARTED
状态下启动以节省资源(有关详细信息,请参阅将 Kotlin 协程与感知生命周期的组件结合使用)。
可以在任何生命周期状态下进行回调,包括 Activity 停止时。监听器通常应在 onStart()
中注册并在 onStop()
中取消注册。
全窗口模态
有些 Activity 会阻止用户与应用互动,直到执行指定的操作为止;例如,登录屏幕 Activity、策略确认屏幕或错误消息。应阻止模态 Activity 出现在拆分中。
可以使用展开配置强制 Activity 始终填充任务窗口
<ActivityRule
window:alwaysExpand="true">
<ActivityFilter
window:activityName=".FullWidthActivity"/>
</ActivityRule>
结束 Activity
用户可以通过从显示屏边缘滑动来结束拆分两侧的 Activity


如果设备设置为使用返回按钮而不是手势导航,则输入会发送到获得焦点的 Activity——最后一次触摸或启动的 Activity。
结束容器中所有 Activity 对对侧容器的影响取决于拆分配置。
配置属性
您可以指定拆分对规则属性来配置结束拆分一侧的所有 Activity 如何影响拆分另一侧的 Activity。属性如下
window:finishPrimaryWithSecondary
— 结束辅助容器中所有 Activity 如何影响主容器中的 Activitywindow:finishSecondaryWithPrimary
— 结束主容器中所有 Activity 如何影响辅助容器中的 Activity
属性的可能值包括
always
— 始终结束关联容器中的 Activitynever
— 永不结束关联容器中的 Activityadjacent
— 当两个容器相邻显示时结束关联容器中的 Activity,但当两个容器堆叠时则不结束
例如
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
默认配置
当拆分中一个容器的所有 Activity 结束时,剩余容器会占用整个窗口
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
一起结束 Activity
当辅助容器中的所有 Activity 结束时,自动结束主容器中的 Activity
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
当主容器中的所有 Activity 结束时,自动结束辅助容器中的 Activity
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
当主容器或辅助容器中所有 Activity 结束时,一起结束 Activity
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
结束容器中的多个 Activity
如果多个 Activity 堆叠在拆分容器中,结束堆栈底部的 Activity 不会自动结束顶部的 Activity。
例如,如果辅助容器中有两个 Activity,C 在 B 的上方
并且拆分的配置由 Activity A 和 B 的配置定义
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
结束顶部 Activity 会保留拆分。
结束辅助容器底部(根)的 Activity 不会移除其顶部的 Activity;因此,也保留了拆分。
同时,还会执行任何其他关于一起结束 Activity 的规则,例如将辅助 Activity 与主 Activity 一起结束
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
当拆分配置为将主 Activity 和辅助 Activity 一起结束时
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
运行时更改拆分属性
活动且可见的拆分的属性无法更改。更改拆分规则会影响额外的 Activity 启动和新容器,但不会影响现有且活动的拆分。
要更改活动拆分的属性,请结束拆分中的侧边 Activity 或 Activity,然后使用新配置再次在侧边启动。
动态拆分属性
Jetpack WindowManager 1.4 及更高版本支持 Android 15(API 级别 35)及更高版本,提供了动态功能,可配置 Activity 嵌入拆分,包括
- 窗格扩展:交互式可拖动分隔线使用户能够调整拆分呈现中窗格的大小。
- Activity 堆栈固定:用户可以固定一个容器中的内容,并将该容器中的导航与另一个容器中的导航隔离开。
- 对话框全屏变暗:显示对话框时,应用可以指定是否将整个任务窗口变暗,或者仅将打开对话框的容器变暗。
窗格扩展
窗格扩展使用户能够调整在双窗格布局中分配给两个 Activity 的屏幕空间量。
要自定义窗口分隔线的外观并设置分隔线的可拖动范围,请执行以下操作
创建一个
DividerAttributes
实例自定义分隔线属性
color
:可拖动窗格分隔线的颜色。widthDp
:可拖动窗格分隔线的宽度。设置为WIDTH_SYSTEM_DEFAULT
以让系统确定分隔线宽度。拖动范围:任一窗格可以占用的屏幕的最小百分比。范围可以是 0.33 到 0.66。设置为
DRAG_RANGE_SYSTEM_DEFAULT
以让系统确定拖动范围。
Kotlin
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) if (WindowSdkExtensions.getInstance().extensionVersion >= 6) { splitAttributesBuilder.setDividerAttributes( DividerAttributes.DraggableDividerAttributes.Builder() .setColor(getColor(R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ) } val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
Java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.33f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT); if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) { splitAttributesBuilder.setDividerAttributes( new DividerAttributes.DraggableDividerAttributes.Builder() .setColor(ContextCompat.getColor(this, R.color.divider_color)) .setWidthDp(4) .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT) .build() ); } SplitAttributes _splitAttributes = splitAttributesBuilder.build();
Activity 堆栈固定
Activity 堆栈固定使用户能够固定其中一个拆分窗口,以便 Activity 在用户在另一个窗口内导航时保持不变。Activity 堆栈固定提供了增强的多任务处理体验。
要在应用中启用 Activity 堆栈固定,请执行以下操作
在您想要固定的 Activity 的布局文件中添加一个按钮,例如,列表-详情布局的详情 Activity
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/detailActivity" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" tools:context=".DetailActivity"> <TextView android:id="@+id/textViewItemDetail" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="36sp" android:textColor="@color/obsidian" app:layout_constraintBottom_toTopOf="@id/pinButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.appcompat.widget.AppCompatButton android:id="@+id/pinButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/pin_this_activity" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/> </androidx.constraintlayout.widget.ConstraintLayout>
在 Activity 的
onCreate()
方法中,在按钮上设置点击监听器Kotlin
val pinButton: Button = findViewById(R.id.pinButton) pinButton.setOnClickListener { val splitAttributes: SplitAttributes = SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build() val pinSplitRule = SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build() SplitController.getInstance(applicationContext) .pinTopActivityStack(taskId, pinSplitRule) }
Java
Button pinButton = findViewById(R.id.pinButton); pinButton.setOnClickListener( (view) -> { SplitAttributes splitAttributes = new SplitAttributes.Builder() .setSplitType(SplitAttributes.SplitType.ratio(0.66f)) .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT) .build(); SplitPinRule pinSplitRule = new SplitPinRule.Builder() .setSticky(true) .setDefaultSplitAttributes(splitAttributes) .build(); SplitController.getInstance(getApplicationContext()) .pinTopActivityStack(getTaskId(), pinSplitRule); });
对话框全屏变暗
Activity 通常会使显示屏变暗,以吸引用户注意对话框。在 Activity 嵌入中,为了获得统一的 UI 体验,双窗格显示屏的两个窗格都应变暗,而不仅仅是包含打开对话框的 Activity 的窗格。
使用 WindowManager 1.4 及更高版本,对话框打开时,整个应用窗口默认会变暗(请参阅 EmbeddingConfiguration.DimAreaBehavior.ON_TASK
)。
要仅将打开对话框的 Activity 的容器变暗,请使用 EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
。
将 Activity 从拆分中提取到全窗口
创建新的配置,使侧边 Activity 全窗口显示,然后使用解析到同一实例的 intent 重新启动该 Activity。
运行时检查拆分支持
Activity 嵌入在 Android 12L(API 级别 32)及更高版本上受支持,但在某些运行较早平台版本的设备上也可用。要在运行时检查该功能是否可用,请使用 SplitController.splitSupportStatus
属性或 SplitController.getSplitSupportStatus()
方法
Kotlin
if (SplitController.getInstance(this).splitSupportStatus == SplitController.SplitSupportStatus.SPLIT_AVAILABLE ) { // Device supports split activity features. }
Java
if (SplitController.getInstance(this).getSplitSupportStatus() == SplitController.SplitSupportStatus.SPLIT_AVAILABLE) { // Device supports split activity features. }
如果不支持拆分,Activity 将在 Activity 堆栈顶部启动(遵循非 Activity 嵌入模型)。
阻止系统覆盖
Android 设备制造商(原始设备制造商,即 OEM)可以将 Activity 嵌入实现为设备系统的功能。系统会为多 Activity 应用指定拆分规则,覆盖应用的窗口行为。系统覆盖会强制多 Activity 应用进入系统定义的 Activity 嵌入模式。
系统 Activity 嵌入可以通过多窗格布局(例如列表-详情)增强应用呈现,而无需更改应用。但是,系统的 Activity 嵌入也可能导致错误的应用布局、bug 或与应用实现的 Activity 嵌入发生冲突。
您的应用可以通过在应用清单文件中设置 PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE
来阻止或允许系统进行 activity 嵌入,例如
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
android:value="true|false" />
</application>
</manifest>
该属性名称在 Jetpack WindowManager WindowProperties
对象中定义。如果您的应用实现了 activity 嵌入,或者您希望阻止系统将其 activity 嵌入规则应用于您的应用,请将该值设置为 false
。将该值设置为 true
可允许系统将系统定义的 activity 嵌入应用于您的应用。
限制、约束和注意事项
- 只有任务的主机应用(即被标识为任务中根 activity 的所有者)才能在该任务中组织和嵌入其他 activity。如果支持嵌入和分屏的 activity 在属于不同应用的任务中运行,则嵌入和分屏对这些 activity 将不起作用。
- Activity 只能在单个任务中组织。在新任务中启动 activity 总是会将其置于现有分屏之外的新的扩展窗口中。
- 只有同一进程中的 activity 才能被组织并置于分屏中。
SplitInfo
回调仅报告属于同一进程的 activity,因为无法了解不同进程中的 activity。 - 每对或单独的 activity 规则仅适用于规则注册后发生的 activity 启动。目前无法更新现有分屏或其视觉属性。
- 分屏对过滤器配置必须与启动 activity 时使用的 Intent 完全匹配。匹配是在从应用进程启动新 activity 时发生的,因此在使用隐式 Intent 时,它可能不知道稍后在系统进程中解析的组件名称。如果在启动时不知道组件名称,可以使用通配符代替 ("*/*"),并可以根据 Intent Action 执行过滤。
- 创建分屏后,目前无法在容器之间或分屏内外移动 activity。分屏仅在 WindowManager 库启动符合规则的新 activity 时创建,并在分屏容器中的最后一个 activity 完成时销毁。
- 配置更改时,activity 可以重新启动,因此当创建或移除分屏以及 activity 边界更改时,activity 可以完全销毁旧实例并创建新实例。因此,应用开发者在从生命周期回调中启动新 activity 等操作时应谨慎。
- 设备必须包含窗口扩展接口才能支持 activity 嵌入。几乎所有运行 Android 12L (API 级别 32) 或更高版本的大屏设备都包含该接口。但是,一些无法运行多个 activity 的大屏设备不包含窗口扩展接口。如果大屏设备不支持多窗口模式,则可能不支持 activity 嵌入。
其他资源
- Codelabs
- 学习路径 — Activity 嵌入
- 示例应用 — activity-embedding