直接分享到 Android 应用程序

1. 简介

直接分享功能允许应用程序在系统 Intent 选择器对话框中直接显示特定于应用程序的选项。用户可以从另一个应用程序分享内容时直接跳转到您的应用程序。例如,使用直接分享功能的消息应用程序可以允许用户将内容直接分享到联系人,该联系人会出现在选择器对话框中。直接分享使分享内容更快更容易。

4272b0cacbab9093.png

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

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

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

本 Codelab 将指导您在应用程序中实现直接分享功能,包括使其与旧版 Android 版本向后兼容。

您将构建的内容

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

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

1a2f836312a50861.gif

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

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

您将学到什么

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

先决条件

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

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

2. 开始

获取代码

从 GitHub 获取直接分享 Codelab

$ 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”。

要遵循 Codelab,请使用 Android Studio 打开根文件夹。它将包含两个子项目

  • direct-share-start Codelab 的起点。
  • direct-share-done Codelab 的解决方案。

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 资源中声明 share-target 元素。
  2. 使用 ShortcutManager API 发布与已声明的 share-target 匹配的动态快捷方式。

分享目标声明

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

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

分享目标必须在应用程序的资源文件中声明,其中 静态快捷方式 也被定义。在我们的示例中,您可以在 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> 根元素内,与其他可能的静态快捷方式定义一起。

**数据元素** 在 share-target 中类似于 Intent 过滤器中的数据规范

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

shortcuts.xml 文件需要在 Intent 过滤器设置为 android.intent.action.MAIN 操作和 android.intent.category.LAUNCHER 类别的 Activity 中声明,位于 AndroidManifest.xml 文件中。

更新 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

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

在本 Codelab 中,我们使用 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)
    }
}

创建与您的 share-target 定义匹配的类别

我们可以配置的参数之一是与快捷方式关联的类别列表。我们在此 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创建快捷方式。这利用了传统的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声明。由于它接收来自共享快捷方式(直接共享)的意图,因此它需要提供一个<meta-data>标签,其名称为:android.service.chooser.chooser_target_service
  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将标题作为意图附加信息传递。

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

MainActivity.kt

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

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

99404bc94b7198c9.png

要向标题添加缩略图,请使用要显示的图像创建一个 content URI。使用Intent.setClipData(contentUri)方法设置 Uri,并在意图中设置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
}

创建 content Uri

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

MainActivity.kt

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

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

  1. 打开AndroidManifest.xml文件。
  2. <application>标签内,取消注释步骤 9.3。这将定义一个FileProvider,它能够安全地创建和共享 content 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. 修改用户点击启动器快捷方式时触发的意图,使其打开 SendMessageActivity,并预先填充一个联系人。提示:意图类型需要为 Intent.ACTION_SEND,并将联系人 ID 发送到意图中。
  2. 修改 SendMessageActivity 以处理新的意图。

11. 恭喜!

您现在熟悉了直接共享背后的概念,并且已经准备好将其应用于您的应用程序中。在本代码实验室中,您学习了以下内容

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