在包可见性有限的情况下满足常见用例

本文档介绍了应用与其他应用交互的几个常见用例。每个部分都提供了有关如何在包可见性有限的情况下完成应用功能的指导,如果您应用的目标是 Android 11(API 级别 30)或更高版本,则需要考虑这一点。

当目标为 Android 11 或更高版本的应用使用 Intent 启动另一个应用中的活动时,最直接的方法是调用 Intent,并在没有可用应用时处理 ActivityNotFoundException 异常。

如果应用的一部分依赖于了解对 startActivity() 的调用是否可以成功(例如显示 UI),请在应用清单的 <queries> 元素中添加一个元素。通常,这是一个 <intent> 元素。

打开网址

本部分介绍了在目标为 Android 11 或更高版本的应用中打开网址的各种方法。

在浏览器或其他应用中打开网址

要打开网址,请使用包含 ACTION_VIEW Intent 操作的 Intent,如 加载网页网址 指南中所述。使用此 Intent 调用 startActivity() 后,将发生以下情况之一

  • 网址将在网页浏览器应用中打开。
  • 网址将在支持该网址作为 深层链接 的应用中打开。
  • 将出现一个歧义对话框,允许用户选择哪个应用打开网址。
  • 由于设备上没有安装可以打开网址的应用,因此会发生 ActivityNotFoundException。(这很少见。)

    建议您的应用捕获并处理发生的 ActivityNotFoundException

由于 startActivity() 方法不需要包可见性即可启动另一个应用的活动,因此您无需在应用的清单中添加 <queries> 元素或对现有的 <queries> 元素进行任何更改。对于打开网址的隐式和显式 Intent,情况都是如此。

检查浏览器是否可用

在某些情况下,您的应用可能希望在尝试打开网址之前,验证设备上是否至少有一个浏览器可用,或者特定浏览器是否为默认浏览器。在这些情况下,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包括在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="https" />
</intent>

当您调用 queryIntentActivities() 并传递网络意图作为参数时,在某些情况下,返回的列表将包含可用的浏览器应用。如果用户已将 URL 配置为默认在非浏览器应用中打开,则列表不包含浏览器应用。

在自定义标签中打开 URL

自定义标签 允许应用自定义浏览器的外观和感觉。您可以 在自定义标签中打开 URL,而无需在应用清单中添加或更改 <queries> 元素。

但是,您可能希望 检查设备是否具有支持自定义标签的浏览器 或使用 CustomTabsClient.getPackageName() 选择要使用自定义标签启动的特定浏览器。在这些情况下,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>

让非浏览器应用处理 URL

即使您的应用可以使用自定义标签打开 URL,也建议您尽可能让非浏览器应用打开 URL。要在您的应用中提供此功能,请尝试使用设置 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 意图标志的意图调用 startActivity()。如果系统抛出 ActivityNotFoundException,则您的应用可以随后在自定义标签中打开 URL。

如果意图包含此标志,则当发生以下任一情况时,对 startActivity() 的调用会导致抛出 ActivityNotFoundException

  • 该调用将直接启动浏览器应用。
  • 该调用将向用户显示一个歧义对话框,其中唯一的选项是浏览器应用。

以下代码片段显示了如何更新您的逻辑以使用 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 意图标志

Kotlin

try {
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        // The URL should either launch directly in a non-browser app (if it's
        // the default) or in the disambiguation dialog.
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url)
}

Java

try {
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    // The URL should either launch directly in a non-browser app (if it's the
    // default) or in the disambiguation dialog.
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // Only browser apps are available, or a browser is the default.
    // So you can open the URL directly in your app, for example in a
    // Custom Tab.
    openInCustomTabs(url);
}

避免歧义对话框

如果您想避免显示用户在打开 URL 时可能看到的歧义对话框,而是希望在这种情况下自己处理 URL,则可以使用设置 FLAG_ACTIVITY_REQUIRE_DEFAULT 意图标志的意图。

如果意图包含此标志,则当该调用会向用户显示歧义对话框时,对 startActivity() 的调用会导致抛出 ActivityNotFoundException

如果意图同时包含此标志和 FLAG_ACTIVITY_REQUIRE_NON_BROWSER 意图标志,则当发生以下任一情况时,对 startActivity() 的调用会导致抛出 ActivityNotFoundException

  • 该调用将直接启动浏览器应用。
  • 该调用将向用户显示歧义对话框。

以下代码片段显示了如何一起使用 FLAG_ACTIVITY_REQUIRE_NON_BROWSERFLAG_ACTIVITY_REQUIRE_DEFAULT 标志

Kotlin

val url = URL_TO_LOAD
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    val intent = Intent(ACTION_VIEW, Uri.parse(url)).apply {
        addCategory(CATEGORY_BROWSABLE)
        flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_REQUIRE_NON_BROWSER or
                FLAG_ACTIVITY_REQUIRE_DEFAULT
    }
    startActivity(intent)
} catch (e: ActivityNotFoundException) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url)
}

Java

String url = URL_TO_LOAD;
try {
    // For this intent to be invoked, the system must directly launch a
    // non-browser app.
    Intent intent = new Intent(ACTION_VIEW, Uri.parse(url));
    intent.addCategory(CATEGORY_BROWSABLE);
    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REQUIRE_NON_BROWSER |
            FLAG_ACTIVITY_REQUIRE_DEFAULT);
    startActivity(intent);
} catch (ActivityNotFoundException e) {
    // This code executes in one of the following cases:
    // 1. Only browser apps can handle the intent.
    // 2. The user has set a browser app as the default app.
    // 3. The user hasn't set any app as the default for handling this URL.
    openInCustomTabs(url);
}

打开文件

如果您的应用处理文件或附件(例如,检查设备是否可以打开给定文件),则通常最简单的方法是尝试启动可以处理该文件的活动。为此,请使用包含 ACTION_VIEW 意图操作和表示特定文件的 URI 的意图。如果设备上没有可用的应用,则您的应用可以捕获 ActivityNotFoundException。在您的异常处理逻辑中,您可以显示错误或尝试自己处理文件。

如果您的应用必须预先知道其他应用是否可以打开给定文件,请将以下代码片段中的 <intent> 元素作为清单中 <queries> 元素的一部分包含在内。如果您在编译时已知文件类型,请包含它。

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
  <data android:mimeType="application/pdf" />
</intent>

然后,您可以通过使用您的意图调用 resolveActivity() 来检查应用是否可用。

授予 URI 访问权限

注意:对于以 Android 11(API 级别 30)或更高版本为目标的应用,需要声明本节中所述的 URI 访问权限,并且建议所有应用都这样做,无论其目标 SDK 版本如何,以及它们是否 导出 其内容提供程序。

对于以 Android 11 或更高版本为目标的应用,若要访问内容 URI,您的应用的意图必须通过设置以下一个或两个意图标志来声明 URI 访问权限FLAG_GRANT_READ_URI_PERMISSIONFLAG_GRANT_WRITE_URI_PERMISSION

在 Android 11 及更高版本上,URI 访问权限会为接收意图的应用提供以下功能

  • 根据给定的 URI 权限,从内容 URI 表示的数据中读取或写入数据。
  • 了解包含与 URI 权限匹配的内容提供程序的应用。包含内容提供程序的应用可能与发送意图的应用不同。

以下代码片段演示了如何添加 URI 权限意图标志,以便以 Android 11 或更高版本为目标的其他应用可以查看内容 URI 中的数据

Kotlin

val shareIntent = Intent(Intent.ACTION_VIEW).apply {
    flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    data = CONTENT_URI_TO_SHARE_WITH_OTHER_APP
}

Java

Intent shareIntent = new Intent(Intent.ACTION_VIEW);
shareIntent.setFlags(FLAG_GRANT_READ_URI_PERMISSION);
shareIntent.setData(CONTENT_URI_TO_SHARE_WITH_OTHER_APP);

连接到服务

如果您的应用需要与 不会自动显示 的服务交互,则可以在 <queries> 元素中声明相应的意图操作。以下部分提供使用常用服务的示例。

连接到文本转语音引擎

如果您的应用与文本转语音 (TTS) 引擎交互,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.TTS_SERVICE" />
</intent>

连接到语音识别服务

如果您的应用与语音识别服务交互,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.speech.RecognitionService" />
</intent>

连接到媒体浏览器服务

如果您的应用是 客户端媒体浏览器应用,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.media.browse.MediaBrowserService" />
</intent>

提供自定义功能

如果您的应用需要根据其与其他应用的交互执行可自定义的操作或显示可自定义的信息,则可以使用 意图过滤器签名 作为清单中 <queries> 元素的一部分来表示该自定义行为。以下部分提供了几个常见场景的详细指导。

查询 SMS 应用

如果您的应用需要有关设备上安装的 SMS 应用集的信息(例如,检查哪个应用是设备的默认 SMS 处理程序),请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SENDTO"/>
  <data android:scheme="smsto" android:host="*" />
</intent>

创建自定义共享表单

在任何时候,请使用 系统提供的共享表单。或者,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.SEND" />
  <!-- Replace with the MIME type that your app works with, if needed. -->
  <data android:mimeType="image/jpeg" />
</intent>

在您的应用逻辑中构建共享表单的过程(例如,对 queryIntentActivities() 的调用)与 Android 11 之前的版本相比保持不变。

显示自定义文本选择操作

当用户在您的应用中选择文本时,文本选择工具栏 会显示对选定文本执行的一系列可能的运算。如果此工具栏显示来自其他应用的自定义操作,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<intent>
  <action android:name="android.intent.action.PROCESS_TEXT" />
  <data android:mimeType="text/plain" />
</intent>

显示联系人的自定义数据行

应用可以 向联系人提供程序添加自定义数据行。为了让联系人应用显示此自定义数据,它需要能够执行以下操作

  1. 从其他应用读取 contacts.xml 文件。
  2. 加载与自定义 MIME 类型相对应的图标。

如果您的应用是联系人应用,请将以下 <intent> 元素作为清单中 <queries> 元素的一部分包含在内

<!-- Place inside the <queries> element. -->
<!-- Lets the app read the contacts.xml file from other apps. -->
<intent>
  <action android:name="android.accounts.AccountAuthenticator" />
</intent>
<!-- Lets the app load an icon corresponding to the custom MIME type. -->
<intent>
  <action android:name="android.intent.action.VIEW" />
  <data android:scheme="content" android:host="com.android.contacts"
        android:mimeType="vnd.android.cursor.item/*" />
</intent>