(已废弃) 分享到 Android 应用

1. 简介

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

4272b0cacbab9093.png

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

Direct Share 使用了 Sharing Shortcuts(共享快捷方式)的概念。应用可以提前发布共享目标,从而允许系统 Intent 选择器对话框在需要时显示这些目标。要发布共享目标,我们将使用 ShortcutManager API。任何已发布的共享快捷方式都会由系统持久保留,直到应用更新它们或应用被卸载为止。

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

本 Codelab 将指导您在应用中实现 Direct Share,包括使其与较旧的 Android 版本向后兼容。

您将构建什么

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

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

1a2f836312a50861.gif

所有类和功能都已为您创建。您将实现 Direct Share 的特定逻辑,以

  • 使用特定类别发布共享快捷方式
  • 使 Direct Share 与较旧的 Android 版本向后兼容
  • 为内容预览添加标题和缩略图

您将学到什么

  • 如何在应用中实现 Direct Share
  • 如何使 Direct Share 与较旧的 Android 版本向后兼容
  • 如何在共享内容中显示内容预览

前提条件

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

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

2. 入门

获取代码

从 GitHub 获取 Direct Share Codelab

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

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

获取 Android Studio 3.3 或更高版本

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

如果您需要下载最新版本的 Android Studio,可以在此处下载。

项目设置

实现 Direct Share 功能的首选方式是使用 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):当用户选择“Direct Share”消息传递应用来处理共享文本时,用户可以选择与谁共享消息。请注意,用户必须选择应用,而不是特定的联系人。

1b36a20eb107b020.png

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

14e2b1d9aa12bbb1.png

4. 声明共享快捷方式

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

让我们开始在应用中实现 Direct Share。从 direct-share-start 子项目开始。

主要有以下两个步骤

  1. 在应用的快捷方式 xml 资源中声明 share-target 元素。
  2. 使用 ShortcutManager API 将具有匹配类别的动态快捷方式发布到声明的 share-target。

Share targets 声明

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

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

share target 必须在应用资源文件中声明,静态快捷方式也在该文件中定义。在我们的示例中,您可以在 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>

Share target 定义与其他可能的静态快捷方式定义一起添加在资源文件的 <shortcuts> 根元素内。

Data 元素在 share-target 中类似于intent filter 中的数据规范

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

shortcuts.xml 文件需要在 AndroidManifest.xml 文件中 Intent filter 设置为 android.intent.action.MAIN 动作和 android.intent.category.LAUNCHER 类别的 activity 中声明。

更新 AndroidManifest.xml 文件以声明 share targets

更新代码以在应用中定义 share targets。

  1. 打开 AndroidManifest.xml 文件
  2. 取消注释步骤 4,为 MainActivity 添加一个 <meta-data> 元素,该元素引用定义应用 share targets 的资源文件

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 中,我们使用 AndroidX 中提供的 ShortcutManagerCompat 来发布快捷方式,因为它提供了向下兼容至 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 文件中定义的类型匹配。在这种情况下,我们在 shortcuts.xml 中定义了 SendMessageActivity 与类别 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,以及一个 String id。

id 参数很重要,因为它将在目标 activity 接收共享 intent 时标识此快捷方式。activity 将通过 EXTRA_SHORTCUT_ID Intent extra 获取 id。在我们的示例中,id 将是联系人 ID,由其在数组中的位置表示。

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

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

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

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

2f68efb77e7a8317.png

ShortcutInfoCompat builder 中定义的 intent 仅在快捷方式作为静态快捷方式打开时触发。理解这一点很重要,以避免将来混淆。

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

为了区分该 Intent 与本例中作为共享快捷方式接收的 Intent,我们使用 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)

类别将用于过滤可以处理各种共享 intent 或动作的快捷方式。此字段对于打算用作 share target 的快捷方式是必需的。要分配我们之前创建的类别,请使用以下方法

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

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

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)

触发 Intent

如果用户在系统 Intent 选择器对话框中选择了与我们上面定义的 share-target 匹配的我们应用的 Direct Share 联系人之一。将触发以下 intent

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>

接收 Intent

SendMessageActivity 将接收在 shortcuts.xml 文件中定义的 Intent。除此之外,该 activity 还需要在 AndroidManifest.xml 文件中定义其正在处理该类型的 Intent。这在 <intent-filter> 标签内 SendMessageActivity manifest 声明中指定,您可以在以下代码中看到

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 类,让我们来看看代码。启动时,它会检查在 Intent 中收到的信息。如果没有关于联系人的信息,则会启动 SelectContactActivity 来选择联系人。它将在 onActivityResult 方法中接收选中的联系人信息。

运行应用

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

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

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

1c756facb7566057.gif

在选择器对话框中选择 Direct Share 联系人

如果我们选择 Direct Share 应用发布的联系人,SendMessageActivity 可以通过 Intent.EXTRA_SHORTCUT_ID 获取联系人的 ID。查看 SendMessageActivity 中的 handleIntent 方法,了解其实现方式。

SendMessageActivity.kt

val shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)

2bbc2b76794c6c75.gif

启动 Direct Share 启动器快捷方式

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

cb975dc2d172b6a.gif

8. 使 Direct Share 向后兼容

Direct Share 首次引入于 Android M,您必须实现一个服务扩展 ChooserTargetService 以按需提供 direct share target。在 Android Q 中,实现方式发生了变化,我们使用 ShortcutManager API 提前提供 direct share target。

如果我们在 API 级别 23-28 的设备上运行应用,我们可以看到应用被列为一个选项,但没有 direct target

7ef6f7bafb63cdb.png

在前面介绍 AndroidX 中提供的 ShortcutManagerCompat 时,我们已经提到了向后兼容性。不过,还有更多工作要做

  1. 打开 direct-share-start-app/app/build.gradle(或 build.gradle (Module: direct-share-start-app) 文件。在那里您可以看到一些与 Direct Share 相关的 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 声明。由于它接收来自共享快捷方式 (Direct Share) 的 Intent,因此需要提供一个名为 android.service.chooser.chooser_target_service<meta-data> 标签。
  2. 取消注释步骤 8,以包含用于向后兼容功能的 service。

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 的值都是静态的且始终相同。如果您的应用使用多个 activities 处理共享 intent,则必须为所有这些 activity 添加此 <meta-data>

完成这些步骤后,Direct Share 现在可以向后兼容了。

运行应用

如果按照上述步骤实施,Direct Share 可以在 API 级别 23-28 的设备上正常工作

62a9b6a5751c9802.png

如果我们运行应用在没有 Direct Share 的 Android L 设备上,应用仍然会列为一个选项。

c779d94e0ffbd6fa.png

9. 内容预览

当应用共享内容时,您可以在 Android Q+ 中显示可选的内容预览。预览可以有标题、图片,或两者都有。

共享图片时

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

无需额外的字段。要获得正确的图片预览,请同时在 clipData 中提供 contentUri,如 Intent.ACTION_SEND 参考文档中所述。

共享文本时

以前为共享内容添加标题的方式已废弃。您需要使用 Intent.EXTRA_TITLE 将标题作为 Intent extra 传递。

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

MainActivity.kt

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

如果您运行应用,您将看到“Send message”是共享内容“Hello!”的标题。

99404bc94b7198c9.png

要向标题添加缩略图,请使用要显示的图片创建一个 content 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
}

创建 content Uri

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

MainActivity.kt

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

contentUri 是我们在 saveThumbnailImage() 方法中保存到缓存(在 images 文件夹中)的启动器图片的 Uri。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. [可选] 自己尝试 Direct Share

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

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

11. 恭喜!

您现在已经熟悉了 Direct Share 背后的概念,并已准备好在您的应用中实现它。在本 Codelab 中,您学习了

  • 如何在应用中实现 Direct Share
  • 如何使 Direct Share 与较旧的 Android 版本向后兼容
  • 如何在共享内容中显示内容预览