Android 应用的直接分享

1. 简介

直接分享是一项功能,允许应用在系统 Intent 选择器对话框中直接显示特定于应用的选项。用户然后可以在从其他应用共享内容时直接跳转到您的应用。例如,使用直接分享的消息应用可以允许用户将内容直接共享给联系人,该联系人显示在选择器对话框中。直接分享使内容共享更快更容易。

4272b0cacbab9093.png

在图像中,您可以看到用户如何快速选择 Tereasa 或 Chang 将文本“Hello!”直接共享到使用直接分享的应用中,而无需浏览联系人列表。

直接分享与共享快捷方式的概念一起使用。应用可以提前发布共享目标,允许系统 Intent 选择器对话框在需要时显示它们。要发布共享目标,我们将使用ShortcutManager API。任何已发布的共享快捷方式都由系统保留,直到应用更新它们或应用被卸载。

当显示给用户时,系统会使用预测服务对任何适用的快捷方式进行排名,这会显示更可能使用的快捷方式。

此代码实验室将引导您完成在应用中实现直接分享的过程,包括使其向后兼容旧版 Android 版本。

您将构建什么

在此代码实验室中,您将使用一个消息应用,该应用可以接收包含纯文本的 Intent。当用户从其他应用(或我们正在构建的应用)共享某些文本时,此应用将被列为选项。通过使用直接分享功能,此应用还会发布系统 Intent 选择器对话框中显示的一些联系人。

您可以在下面看到示例应用

1a2f836312a50861.gif

所有类和功能已为您创建。您将实现特定于直接分享的逻辑以

  • 发布具有特定类别的共享快捷方式
  • 使直接分享向后兼容旧版 Android 版本
  • 向内容预览添加标题和缩略图

您将学到什么

  • 如何在您的应用中实现直接分享
  • 如何使直接分享向后兼容旧版 Android 版本
  • 如何在共享内容中显示内容预览

先决条件

  • Kotlin 基础知识(此代码实验室使用 Kotlin)
  • Android Studio 3.3 或更高版本
  • 运行 API 21+ 的模拟器或设备

如果您在完成此代码实验室时遇到任何问题(代码错误、语法错误、措辞不清等),请通过代码实验室左下角的“报告错误”链接报告该问题。

2. 入门

获取代码

从 GitHub 获取直接分享代码实验室

$ git clone https://github.com/android/codelab-android-direct-share

或者,您可以将存储库下载为 Zip 文件

获取 Android Studio 3.3 或更高版本

确保您使用的是 Android Studio 3.3 或更高版本。

如果您需要下载最新版本的 Android Studio,可以在这里进行下载。

项目设置

实现直接分享功能的首选方法是使用 Android Q API 的功能。这就是为什么您的compileSdkVersion需要至少为“29”。

要遵循代码实验室,请使用 Android Studio 打开根文件夹。它将在其中包含两个子项目

  • direct-share-start 代码实验室的起点。
  • direct-share-done 代码实验室的解决方案。

3. 示例应用概述

此消息应用能够

  • 通过ACTION_SEND Intent 共享纯文本
  • 侦听纯文本 Intent 以将消息发送给联系人
  • 如果没有选择联系人,用户可以选择联系人

运行示例应用

如果您运行direct-share-start-app,则将启动共享屏幕MainActivity.kt):在 EditText 上键入任何内容,然后点击共享以将该文本共享到任何应用。

a591d10e2db09026.png

点击共享将触发发送到系统的 Intent,并且会出现系统 Intent 对话框选择器。

f8f107984daca635.png

选择联系人屏幕SelectContactActivity.kt):当用户选择“直接分享”消息应用来处理共享文本时,用户可以选择与谁共享消息。请注意,用户必须选择应用,而不是特定联系人。

1b36a20eb107b020.png

发送消息屏幕SendMessageActivity.kt):点击发送以将消息发送给联系人。该应用将显示 Toast 作为消息已发送的信号。

14e2b1d9aa12bbb1.png

4. 声明共享快捷方式

直接分享使用ShortcutManager API发布共享快捷方式。它类似于应用快捷方式功能

让我们开始在应用上工作并实现直接分享。从direct-share-start子项目开始。

有两个主要步骤需要遵循

  1. 在应用的快捷方式 xml 资源中声明共享目标元素。
  2. 使用 ShortcutManager API 发布与已声明的共享目标匹配的具有匹配类别的动态快捷方式。

共享目标声明

共享目标定义了共享快捷方式的元数据,包括

  • 有关共享数据类型的信息
  • 可以与共享快捷方式关联的类别
  • 将处理共享 Intent 的活动

共享目标必须在应用的资源文件中声明,其中还定义了静态快捷方式。在我们的示例中,您可以在app/src/main/res/xml/shortcuts.xml中找到它。

您目前无需执行任何操作,此文件已在项目中可用。您可以打开并查看它。

shortcuts.xml

<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
    <share-target android:targetClass="com.example.android.directshare.SendMessageActivity">
        <data android:mimeType="text/plain" />
        <category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" />
    </share-target>
</shortcuts>

共享目标定义添加到资源文件中的<shortcuts>根元素内,以及其他可能的静态快捷方式定义。

共享目标中的数据元素类似于Intent 过滤器中的数据规范

每个共享目标可以有多个关联类别,这些类别将仅用于将应用的已发布快捷方式与其共享目标定义匹配。类别可以是任何任意字符串。在内部,框架将共享快捷方式的类别与应用的 xml 资源中给出的类别匹配。目标类将处理共享 Intent。

需要在 AndroidManifest.xml 文件中声明 shortcuts.xml 文件,该文件在其中声明一个活动的 Intent 过滤器,该过滤器被设置为 android.intent.action.MAIN 操作和 android.intent.category.LAUNCHER 类别。

更新 AndroidManifest.xml 文件以声明共享目标

更新代码以在应用中定义共享目标。

  1. 打开AndroidManifest.xml文件
  2. 取消注释步骤 4 以向MainActivity添加<meta-data>元素,该元素引用定义应用共享目标的资源文件

AndroidManifest.xml

<activity
    android:name=".MainActivity"
    android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    <!-- Reference resource file where the app's shortcuts are defined -->
    <meta-data
        android:name="android.app.shortcuts"
        android:resource="@xml/shortcuts" />
</activity>

5. 使用 ShortcutManager API

定义共享目标元素后,我们需要使用ShortcutManager API发布与这些定义匹配的动态快捷方式。共享快捷方式保留在系统中,直到由同一个应用更新或应用被卸载。您需要在每次认为合适时手动更新快捷方式列表(例如,您可以在用户打开应用或消息应用中最新的对话更改时更新它们)。该 API 提供了更新、删除或添加快捷方式的方法。

在代码实验室中,我们使用ShortcutManagerCompat(在 AndroidX 中可用)发布快捷方式,因为它提供了向后兼容性,直至 Android M。通过将androidx:core依赖项添加到您的项目中,在您的项目中使用它。

请参阅已添加到direct-share-start-app/app/build.gradlebuild.gradle (Module: direct-share-start-app)文件中的依赖项,如下所示

build.gradle

implementation "androidx.core:core:${versions.androidxCore}"

介绍 SharingShortcutsManager

应用中负责与 ShortcutManager 交互的类是 SharingShortcutsManager。每次用户打开应用时(即 MainActivity 启动时),我们都会推送我们的分享快捷方式。

您可以在 MainActivity 类中看到我们如何与它进行交互。

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    ...
    sharingShortcutsManager = SharingShortcutsManager().also {
        it.pushDirectShareTargets(this)
    }
}

创建与您的共享目标定义匹配的类别

我们可以配置的参数之一是与快捷方式关联的类别列表。我们在本 Codelab 中创建的所有快捷方式都是同一类型。并且该类型必须与我们之前在 shortcuts.xml 文件中定义的类型匹配。在本例中,我们定义了 SendMessageActivityshortcuts.xml 中的 TEXT_SHARE_TARGET 类别相关联。

如果您打开 SharingShortcutsManager.kt,您可以在一个常量变量中看到定义的类别。

SharingShortcutsManager.kt

/**
 * Category name defined in res/xml/shortcuts.xml that accepts data of type text/plain
 * and will trigger [SendMessageActivity]
 */
private val categoryTextShareTarget = "com.example.android.directshare.category.TEXT_SHARE_TARGET"

现在转到 pushDirectShareTargets 方法,并取消注释创建快捷方式关联的类别集的步骤 5 代码。

SharingShortcutsManager.kt

fun pushDirectShareTargets(context: Context) {
    ...
    // Category that our sharing shortcuts will be assigned to
    val contactCategories = setOf(categoryTextShareTarget)
    ...
}

6. 快捷方式的结构

让我们探索一下在快捷方式中可以配置哪些内容。

取消注释步骤 6 中创建快捷方式并将其添加到将由 ShortcutManagerCompat 发布的快捷方式列表中的代码。我们将在本节中更详细地介绍代码。

为简单起见,我们始终添加在 Contact.kt 文件中定义的前四个相同的联系人。如果您打开它,您可以看到我们创建了一个包含十个联系人的数组以及用于检索它们的方法。

在取消注释的代码中,我们使用 ShortcutInfoCompat.Builder 创建了一个快捷方式。这使用了传统的 构建器模式。它以 Context 对象作为参数,在本例中是调用此方法的 Activity 和一个字符串 ID。

**id 参数** 非常重要,因为它将在目标活动接收共享意图时识别此快捷方式。活动将使用 **EXTRA_SHORTCUT_ID** 意图额外信息获取 ID。在我们的例子中,ID 将是联系人 ID,它由其在数组中所占的位置表示。

ShortcutInfoCompat.Builder(context, Integer.toString(id))

接下来,我们配置将在系统意图选择器对话框中显示的 **短标签** 和 **图标**。

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .setShortLabel(contact.name)
        // Icon that will be displayed in the share target
        .setIcon(IconCompat.createWithResource(context, contact.icon))

您可以在下图中看到设置这些属性如何修改系统意图对话框选择器中显示的 UI。

2f68efb77e7a8317.png

仅当快捷方式作为 静态快捷方式 打开时,才会触发 ShortcutInfoCompat 构建器中定义的 **意图**。了解这一点以避免将来出现混淆非常重要。

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        ...
        .setIntent(staticLauncherShortcutIntent)

为了将该意图与本示例中作为共享快捷方式接收到的意图区分开来,我们使用 ACTION_DEFAULT 操作来定义它。

// Item that will be sent if the shortcut is opened as a static launcher shortcut
val staticLauncherShortcutIntent = Intent(Intent.ACTION_DEFAULT)

如果快捷方式是 **长期存在的**,则各种系统服务可能会将其缓存,即使应用已取消发布或删除它,它也可能显示为共享目标。

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        // Make this sharing shortcut cached by the system
        // Even if it is unpublished, it can still appear on the sharesheet
        .setLongLived(true)

**类别** 将用于筛选可以处理各种共享意图或操作的快捷方式。对于旨在用作共享目标的快捷方式,此字段是必需的。要分配我们之前创建的类别,请使用以下方法

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .setCategories(contactCategories)

您还可以为快捷方式分配 **人员**。这用于更好地了解不同应用中的用户行为,并帮助 Android 框架中的潜在预测服务在选择器对话框中提供更好的建议。向快捷方式添加人员信息是 **可选的**,但强烈建议这样做。请注意,并非所有共享目标都可以与人员关联(例如,共享到云端)。

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        // Person objects are used to give better suggestions
        .setPerson(
                Person.Builder()
                        .setName(contact.name)
                        .build()
        )

通过所有这些配置,我们的快捷方式已准备就绪,我们可以构建它。

ShortcutInfoCompat.Builder(context, Integer.toString(id))
        .build()

步骤 6 中的代码创建了四个共享快捷方式,这些快捷方式已经可以发布了。

7. 发布共享快捷方式

现在我们有四个共享目标在数组中,我们可以使用 ShortcutManagerCompat 发布它们。从 SharingShortcutsManager.kt 文件中取消注释步骤 7。

SharingShortcutsManager.kt

ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)

触发意图

如果用户在系统意图选择器对话框中选择我们应用的直接共享联系人之一,该联系人与我们上面定义的共享目标匹配。则将触发以下意图。

Action: Intent.ACTION_SEND
ComponentName: { com.example.android.directshare /
                  com.example.android.directshare.SendMessageActivity}
Data: Uri to the shared content
EXTRA_SHORTCUT_ID: <ID of the selected shortcut>

接收意图

SendMessageActivity 将接收意图,因为它是在 shortcuts.xml 文件中定义的。除此之外,活动还需要在 AndroidManifest.xml 文件中定义它正在处理此类意图。这在 SendMessageActivity 清单声明中的 <intent-filter> 标记中指定,如下面的代码所示。

AndroidManifest.xml

<activity
    android:name=".SendMessageActivity"
    android:label="@string/app_name"
    android:theme="@style/DirectShareDialogTheme">
    <!-- This activity can respond to Intents of ACTION_SEND and with text/plain data -->
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:mimeType="text/plain" />
    </intent-filter>
</activity>

打开 SendMessageActivity.kt 类,让我们浏览一下代码。当它启动时,它会检查意图中接收到的信息。如果没有关于联系人的信息,则它会启动 SelectContactActivity 来选择联系人。它将在 onActivityResult 方法中接收选定的联系人信息。

运行应用

此时,我们可以运行已取消注释步骤 1、2 和 3 的应用。由于我们尚未使其向后兼容旧版 Android 版本,因此我们需要在 Android 10 设备上运行它。

在选择器对话框中选择应用

如上所述,如果我们选择我们的应用来接收共享文本,则会提示用户选择联系人。选择联系人后,用户可以发送共享消息。

1c756facb7566057.gif

在选择器对话框中选择直接共享联系人

如果我们选择一个由直接共享应用发布的联系人,SendMessageActivity 可以使用 Intent.EXTRA_SHORTCUT_ID 获取联系人的 ID。检查 SendMessageActivity 中的 handleIntent 方法以了解其实现方式。

SendMessageActivity.kt

val shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)

2bbc2b76794c6c75.gif

启动直接共享启动器快捷方式

如果我们打开作为启动器快捷方式发布的共享快捷方式之一,系统将触发我们在 ShortcutInfoCompat.Builder 中定义的 Intent.ACTION_DEFAULT 意图。

cb975dc2d172b6a.gif

8. 使直接共享向后兼容

直接共享是在 Android M 中 首次引入的,您必须 实现一个扩展 ChooserTargetService 的服务 以按需提供直接共享目标。在 Android Q 中,这种方法发生了变化,我们使用 ShortcutManager API 预先提供直接共享目标。

如果我们在 **API 级别 23-28** 的设备上运行应用,我们可以看到该应用被列为一个选项,但没有直接目标。

7ef6f7bafb63cdb.png

我们在前面几节介绍 AndroidX 中可用的 ShortcutManagerCompat 时已经提到了向后兼容性。不过,仍需完成更多工作。

  1. 打开 direct-share-start-app/app/build.gradle(或 build.gradle (Module: direct-share-start-app) 文件)。您可以在其中看到一些与直接共享相关的 AndroidX 依赖项。与之前一样,core 依赖项包含 ShortcutManagerCompat,而 sharetarget 依赖项将实现 ChooserTargetServiceCompat 服务,使其能够在旧版 Android 版本上运行。如果您在项目中实现此功能,则需要这些依赖项(或更新的依赖项)。

app/build.gradle

implementation 'androidx.core:core:1.2.0-beta01'
implementation 'androidx.sharetarget:sharetarget:1.0.0-beta01'
  1. 打开 AndroidManifest.xml 文件,并转到 SendMessageActivity 声明。由于它接收来自共享快捷方式(直接共享)的意图,因此需要提供一个名称为 android.service.chooser.chooser_target_service<meta-data> 标记。
  2. 取消注释步骤 8 以包含向后兼容功能的服务。

AndroidManifest.xml

<activity
    android:name=".SendMessageActivity">
    ...
    <meta-data
        android:name="android.service.chooser.chooser_target_service"
        android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
</activity>

android.service.chooser.chooser_target_serviceandroidx.sharetarget.ChooserTargetServiceCompat 的值都是静态的,并且始终相同。如果您的应用使用多个活动处理共享意图,则需要将此 <meta-data> 添加到所有活动中。

通过这些步骤,直接共享现在已向后兼容。

运行应用

如果我们执行上述步骤,则直接分享功能在 API 级别 23-28 的设备上可以正常工作。

62a9b6a5751c9802.png

如果我们在没有直接分享功能的Android L 设备上运行应用,应用仍然会显示为选项。

c779d94e0ffbd6fa.png

9. 内容预览

当应用共享内容时,您可以在 Android Q+ 上显示其可选预览。预览可以包含标题、图片或两者兼而有之。

共享图片时

当同时使用 ACTION_SENDACTION_SEND_MULTIPLE 以及通过 EXTRA_STREAM 共享的 URI 时,系统会检查 MIME 类型,并尝试在预览区域中渲染图片/文件预览。

无需添加其他字段。要获得正确的图片预览,请在剪贴板数据中提供 contentUri,如 Intent.ACTION_SEND参考文档 中所述。

共享文本时

之前添加共享内容标题的方法已弃用。您需要使用 Intent.EXTRA_TITLE 将标题作为 Intent 附加信息传递。

  1. 打开 MainActivity.kt 并找到 share 方法。取消注释步骤 9.1 以向共享内容添加标题。

MainActivity.kt

sharingIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.send_intent_title))

如果运行应用,您会看到共享内容“Hello!”的标题为“发送消息”。

99404bc94b7198c9.png

要向标题添加缩略图,请使用要显示的图片创建内容 URI。使用 Intent.setClipData(contentUri) 方法设置 Uri,并在 Intent 中设置 Intent.FLAG_GRANT_READ_URI_PERMISSION 标志。

  1. share() 方法中,取消注释步骤 9.2 以向共享内容添加缩略图。

MainActivity.kt

val thumbnail = getClipDataThumbnail()
thumbnail?.let {
    sharingIntent.clipData = it
    sharingIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}

创建内容 Uri

查看 MainActivity.kt 中的 getClipDataThumbnail() 方法,我们如下创建内容 Uri

MainActivity.kt

val contentUri = saveThumbnailImage()
ClipData.newUri(contentResolver, null, contentUri)
...

contentUri 是启动器图片的 Uri,我们将其保存到缓存(在 images 文件夹中),方法是在 saveThumbnailImage() 方法中保存。 contentResolver 需要访问我们刚刚声明的文件夹。为此

  1. 打开 AndroidManifest.xml 文件。
  2. <application> 标签内,取消注释步骤 9.3。这将定义一个 FileProvider,它能够安全地创建和共享内容 Uri。

AndroidManifest.xml

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.example.android.directshare.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

如果在 Android 10 设备上运行应用,您可以在标题旁边看到缩略图。只要共享内容具有标题,缩略图就会出现。如果没有标题,则不会出现。

df966d69090d6be3.png

10. [可选] 自行尝试直接分享

您可以对代码进行一些改进。这是一个可选步骤,您可以自行尝试。

  1. 修改用户点击启动器快捷方式时触发的 Intent,使其打开 SendMessageActivity 并在其中预填充联系人。提示:Intent 的类型需要为 Intent.ACTION_SEND,并且需要使用它发送联系人 ID。
  2. 修改 SendMessageActivity 以处理新的 Intent。

11. 祝贺您!

您现在熟悉了直接分享背后的概念,并且可以将其应用到您的应用中。在本 Codelab 中,您学习了以下内容:

  • 如何在您的应用中实现直接分享
  • 如何使直接分享向后兼容旧版 Android 版本
  • 如何在共享内容中显示内容预览