Android NDK开发详解大屏设备之activity 嵌入三

跨应用嵌入信任模型托管应用

分屏示例从全窗口分屏默认分屏深层链接分屏分屏容器中的多个 activity新任务中的 activityactivity 替换多重分屏

跨应用嵌入

在 Android 13(API 级别 33)及更高版本中,应用可以嵌入其他应用中的 activity。借助跨应用(或跨 UID)的 activity 嵌入,我们可以直观地集成多个 Android 应用中的 activity。系统会在屏幕上并排或在上下显示托管应用的 activity 和其他应用中嵌入的 activity,像在单应用 activity 嵌入中一样。

例如,“设置”应用可以嵌入 WallpaperPicker 应用中的壁纸选择器 activity:

图 14. “设置”应用(左侧菜单),其中壁纸选择器就是嵌入的 activity(右侧)。

信任模型

借助嵌入其他应用中的 activity 的主机进程,我们可以重新定义嵌入的 activity 的呈现方式,包括大小、位置、剪裁和透明度。恶意主机可能会利用此功能误导用户并发起点击劫持攻击或其他界面伪装攻击。

为防止跨应用 activity 嵌入的滥用,Android 要求应用选择允许嵌入 activity。应用可以将主机指定为受信任或不受信任。

受信任的托管 如需允许其他应用嵌入并完全控制您应用中 activity 的呈现方式,请在 或 应用清单文件的 android:knownActivityEmbeddingCerts 属性中,指定托管应用的 SHA-256 证书。

将 android:knownActivityEmbeddingCerts 的值设置为字符串:

android:name=".MyEmbeddableActivity"

android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"

... />

如需指定多个证书,则设置为字符串数组:

android:name=".MyEmbeddableActivity"

android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"

... />

引用的资源如下所示:

cert1

cert2

...

应用所有者可以通过运行 Gradle signingReport 任务来获取 SHA 证书摘要。证书摘要是 SHA-256 指纹,不含分号分隔符。如需了解详情,请参阅生成签名报告和对客户端进行身份验证。

注意:如果 和 清单元素中都声明了 android:knownActivityEmbeddingCerts, 元素中的值优先级更高。 不受信任的托管 如需允许任何应用都能嵌入您应用的 activity 并控制其呈现方式,请在应用清单的 或 元素中指定 android:allowUntrustedActivityEmbedding 属性,例如:

android:name=".MyEmbeddableActivity"

android:allowUntrustedActivityEmbedding="true"

... />

此属性的默认值为 false,这样可以阻止跨应用 activity 嵌入。

警告:允许不受信任嵌入的 activity 不得公开可能会引发点击劫持攻击的敏感信息、界面控件或输入字段。 注意:如果 和 清单元素中都声明了 android:allowUntrustedActivityEmbedding, 元素中的值优先级更高。 自定义身份验证 为了降低不受信任的 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(this).isActivityEmbedded(activity);

}

最小大小限制 Android 系统会将应用清单 元素中指定的最小高度和宽度应用于嵌入的 activity。如果应用未指定最小高度和宽度,则应用系统默认值 (sw220dp)。

如果主机尝试将嵌入的容器的大小调整为小于最小大小,则嵌入的容器会占据整个任务边界。 如需让受信任或不受信任 activity 嵌入与 元素一起使用,必须对目标 activity(而非别名)应用 android:knownActivityEmbeddingCerts 或 android:allowUntrustedActivityEmbedding。用于验证系统服务器安全性的政策取决于在目标上设置的标志,而不是别名。

托管应用

托管应用实现跨应用 activity 嵌入的方式与实现单应用 activity 嵌入的方式别无二致。SplitPairRule 和 SplitPairFilter 或 ActivityRule 和 ActivityFilter 对象指定了嵌入的 activity 和任务窗口分屏。分屏规则在 XML 中以静态方式进行定义,或在运行时使用 Jetpack WindowManager API 调用进行定义。

如果托管应用尝试嵌入尚未选择接受跨应用嵌入的 activity,则该 activity 会占用整个任务边界。因此,托管应用需要了解目标 activity 是否允许跨应用嵌入。

如果嵌入的 activity 在同一任务中启动新 activity,并且新 activity 未选择接受跨应用嵌入,则该 activity 会占用整个任务边界,而不是在嵌入的容器中叠加该 activity。

托管应用可以不受限制地嵌入自己的 activity,前提是这些 activity 都在同一任务中启动。

分屏示例

从全窗口分屏

图 15. activity A 在侧面启动 activity B。 无需重构。您可以静态地或在运行时定义分屏的配置,然后调用 Context#startActivity() 而不必指定任何额外的参数。

window:primaryActivityName=".A"

window:secondaryActivityName=".B"/>

默认分屏

如果应用的着陆页设计为在大屏幕上拆分成两个容器,当同时创建和呈现两个 activity 时,用户体验最佳。不过,在用户与主要容器中的 activity 交互(例如,用户从导航菜单中选择一项)之前,分屏的辅助容器中可能没有可用的内容。占位符 activity 可以填补这一空白,直到可以在分屏的辅助容器中显示内容(请参阅上文的占位符)。

图 16. 通过同时打开两个 activity 创建分屏。一个 activity 是占位符。 如需创建带有占位符的分屏,请创建一个占位符并将其与主要 activity 相关联:

window:placeholderActivityName=".PlaceholderActivity">

window:activityName=".MainActivity"/>

深层链接分屏

当应用收到 intent 时,目标 activity 可以显示为 activity 分屏的辅助部分;例如,请求显示详情屏幕,该屏幕包含有关列表中某一项的信息。在小显示屏上,详情显示在完整的任务窗口中;在较大的设备上,详情显示在列表旁边。

图 17. 深层链接详情 activity 单独显示在小屏幕上,但与列表 activity 一起显示在大屏幕上。 启动请求应传送到主 activity,并且目标详情 activity 应在分屏中启动。系统会根据可用的显示屏宽度自动选择正确的呈现方式(堆叠或并排)。

Kotlin

override fun onCreate(savedInstanceState Bundle?) {

. . .

RuleController.getInstance(this)

.addRule(SplitPairRule.Builder(filterSet).build())

startActivity(Intent(this, DetailActivity::class.java))

}

Java

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {

. . .

RuleController.getInstance(this)

.addRule(new SplitPairRule.Builder(filterSet).build());

startActivity(new Intent(this, DetailActivity.class));

}

深层链接目的地可能是用户在返回导航堆栈中唯一可用的 activity,因此建议您不要关闭详情 activity,并仅保留主 activity:

并排显示列表 activity 和详情 activity 的大显示屏。返回导航无法关闭详情 activity,而将列表 activity 留在屏幕上。

仅显示详情 activity 的小显示屏。返回导航无法关闭详情 activity 而显示列表 activity。

您可以使用 finishPrimaryWithSecondary 属性来同时结束这两个 activity:

window:finishPrimaryWithSecondary="always">

window:primaryActivityName=".ListActivity"

window:secondaryActivityName=".DetailActivity"/>

请参阅下面的配置属性。

分屏容器中的多个 activity

在分屏容器中堆叠多个 activity 能让用户访问深层内容。例如,对于列表-详情分屏,用户可能需要进入子详情部分,但让主要 activity 留在原地:

图 18. 在任务窗口的辅助窗格中原位打开了 activity。 Kotlin

class DetailActivity {

. . .

fun onOpenSubDetail() {

startActivity(Intent(this, SubDetailActivity::class.java))

}

}

Java

public class DetailActivity {

. . .

void onOpenSubDetail() {

startActivity(new Intent(this, SubDetailActivity.class));

}

}

子详情 activity 被置于详情 activity 之上,从而将详情 activity 隐藏起来:

然后,用户可以通过在堆栈中进行返回导航来回到之前的详情级别:

图 19. 从堆栈顶部移除了 activity。 当从同一辅助容器中的一个 activity 启动多个 activity 时,相互堆叠 activity 是默认行为。从活跃分屏的主要容器中启动的 activity 最终也会出现在 activity 堆栈顶部的辅助容器中。

注意:在 API 的这一初始版本中,系统无法在分屏任务窗口的主要容器中启动辅助 activity。当 activity 在较小的显示屏上重叠时,此行为与现有系统行为一致。

新任务中的 activity

当分屏任务窗口中的 activity 启动新任务中的 activity 时,新任务将与包含分屏的任务分开并显示在全窗口中。“最近使用的应用”屏幕显示两项任务:分屏中的任务和新任务。

图 20. 从 activity B 启动新任务中的 activity C。

activity 替换

可以在辅助容器堆栈中替换 activity;例如,当主要 activity 用于顶级导航,而辅助 activity 是选定的目的地时。每当从顶级导航中选择一项时,都应在辅助容器中启动一个新的 activity,并移除之前在辅助容器中的一个或多个 activity。

图 21. 主要窗格中的顶级导航 activity 替换辅助窗格中的目的地 activity。 如果在导航选择发生变化时应用未完成辅助容器中的 activity,那么在分屏收起后(设备折叠后),返回导航可能会令人感到困惑。例如,如果主要窗格中有一个菜单,并且屏幕 A 和屏幕 B 堆叠在辅助窗格中,当用户折叠手机时,屏幕 B 在屏幕 A 之上,屏幕 A 又在菜单之上。当用户从屏幕 B 进行返回导航时,系统会显示屏幕 A 而不是菜单。

在此类情况下,必须从返回堆栈中移除屏幕 A。

在现有分屏之上的新容器中启动到侧面时的默认行为是将新的辅助容器置于顶部,并将旧的辅助容器保留在返回堆栈中。您可以将分屏配置为通过 clearTop 清除之前的辅助容器,并正常启动新的 activity。

window:clearTop="true">

window:primaryActivityName=".Menu"

window:secondaryActivityName=".ScreenA"/>

window:primaryActivityName=".Menu"

window:secondaryActivityName=".ScreenB"/>

Kotlin

class MenuActivity {

. . .

fun onMenuItemSelected(selectedMenuItem: Int) {

startActivity(Intent(this, classForItem(selectedMenuItem)))

}

}

Java

public class MenuActivity {

. . .

void onMenuItemSelected(int selectedMenuItem) {

startActivity(new Intent(this, classForItem(selectedMenuItem)));

}

}

或者,使用相同的辅助 activity,并从主要(菜单)activity 发送新的 intent,这些 intent 解析为相同的实例,但会在辅助容器中触发状态或界面更新。

多重分屏

应用可以通过在侧面启动额外的 activity 来提供多级深层导航。

当辅助容器中的 activity 在侧面启动一个新的 activity 时,系统会在现有分屏之上创建一个新的分屏。

图 22. activity B 在侧面启动 activity C。 返回堆栈包含之前打开的所有 activity,因此用户在完成 activity C 之后可以导航到 activity A/activity B 分屏。

堆栈中的 activity A、activity B 和 activity C。这些 activity 按以下顺序堆叠,从上到下依次为:activity C、activity B 和 activity A。

如需创建新的分屏,请从现有辅助容器中在侧面启动新的 activity。声明 activity A/activity B 分屏和 activity B/activity C 分屏的配置,并正常从 activity B 启动 activity C:

window:primaryActivityName=".A"

window:secondaryActivityName=".B"/>

window:primaryActivityName=".B"

window:secondaryActivityName=".C"/>

Kotlin

class B {

. . .

fun onOpenC() {

startActivity(Intent(this, C::class.java))

}

}

Java

public class B {

. . .

void onOpenC() {

startActivity(new Intent(this, C.class));

}

}

注意:从注册了分屏规则的 activity A 启动 activity C 时,会从 activity B 之上的一个新辅助容器中启动 activity C。

精彩文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: