复制和粘贴

Android 提供了一个强大的基于剪贴板的框架,用于复制和粘贴。它支持简单的 和复杂数据类型,包括文本字符串、复杂数据结构、文本和二进制流 数据和应用资产简单的文本数据直接存储在剪贴板中,而复杂的 数据会存储为引用,粘贴应用使用 content provider 解析该引用。正在复制 和粘贴既可在应用内进行,也可在实现 框架。

由于框架的一部分使用内容提供程序,因此本文档假定您对 Android Content Provider API,这在 内容提供程序

在将内容复制到剪贴板时,用户希望获得反馈,因此,除了 支持复制和粘贴功能,在 Android 13(API 级别 33)中复制时,Android 会向用户显示默认界面 和更高版本。因为此功能,有出现重复通知的风险。如需详细了解 在这个极端情况下, 避免收到重复的通知 部分。

展示 Android 13 剪贴板通知的动画
图 1. 在 Android 中,内容进入剪贴板时显示的界面 13 及更高版本。

在 Android 12L(API 级别 32)及更低版本中复制内容时,手动向用户提供反馈。请参阅 建议

剪贴板框架

使用剪贴板框架时,将数据放入一个剪贴对象中,然后将这个剪贴对象放入 。剪贴对象可以采用以下三种形式之一:

文本
一个文本字符串。直接将字符串放在剪贴对象中,然后将其放在 剪贴板。要粘贴字符串,请从剪贴板中获取剪切对象,然后复制 复制到应用的存储空间中。
URI
一个表示任意值的 Uri 对象 URI 的形式。这主要用于从 content provider 复制复杂的数据。复制 请将一个 Uri 对象放入一个剪贴对象中,并将该剪贴对象放到 剪贴板。如需粘贴数据,请获取剪贴对象,获取 Uri 对象, 将其解析为数据源(例如 content provider),并复制 发送到应用的存储空间
Intent
一个 Intent。这个 支持复制应用程序快捷方式。如需复制数据,请创建一个 Intent,将 然后将其放入一个剪切对象中,并将该剪切对象放到剪贴板中。要粘贴数据,请执行以下操作: 剪切对象,然后将 Intent 对象复制到应用的 内存区域

剪贴板一次只保留一个剪贴对象。当应用将一个剪贴对象放在 上一个剪切对象会消失。

如果您想允许用户将数据粘贴到您的应用中,则无需处理所有类型的 数据。您可以先检查剪贴板中的数据,然后再向用户提供粘贴数据的选项。 除了具有特定的数据形式之外,该剪切对象还包含元数据,告知您 类型可用。这些元数据可以帮助您确定应用是否可以执行一些有用的操作 以及剪贴板数据例如,如果您有一个主要处理文本的应用, 可能希望忽略包含 URI 或 intent 的剪辑对象。

您可能还希望无论剪贴板中的数据采用何种形式,用户都可以粘贴文本。接收者 执行此操作,将剪贴板数据强制转换为文本表示形式,然后粘贴此文本。这是 将剪贴板数据强制转换为文本部分中所述。

剪贴板类

本部分介绍了剪贴板框架使用的类。

ClipboardManager

Android 系统剪贴板由全局 ClipboardManager 类。 请勿直接实例化此类,而是通过调用 getSystemService(CLIPBOARD_SERVICE)

ClipData、ClipData.Item 和 ClipDescription

要将数据添加到剪贴板,请创建一个 ClipData 对象,其中包含 对数据和数据本身的说明剪贴板中每次放置一个 ClipDataClipData 包含 ClipDescription 个对象 以及一个或多个 ClipData.Item 对象。

ClipDescription 对象包含关于剪切的元数据。具体而言, 包含剪辑数据的可用 MIME 类型数组。此外, 对于 Android 12(API 级别 31)及更高版本,元数据包含对象是否 包含 风格化文本 对象中文本的类型。 当您将剪切的内容放到剪贴板中时,粘贴应用可以获得这些信息, 可以检查自己能否处理剪辑数据。

ClipData.Item 对象包含文本、URI 或 intent 数据:

文本
一个 CharSequence
URI
一个 Uri。它通常包含一个内容提供程序 URI,尽管任何 URI 都是 允许。提供数据的应用将 URI 放到剪贴板中。应用 要粘贴数据的用户从剪贴板获取 URI,并使用该 URI 访问内容 或其他数据源,并检索数据。
Intent
一个 Intent。利用此数据类型,您可以将应用快捷方式复制到 剪贴板。然后,用户可以将快捷方式粘贴到自己的应用中以供日后使用。

您可以向一个剪切添加多个 ClipData.Item 对象。这样,用户既可以复制 将多项选择粘贴为一个剪辑。例如,如果您有一个列表微件, 用户一次选择多个项目,您可以同时将所有这些项目复制到剪贴板。待办事项 为此,请为每个列表项创建一个单独的 ClipData.Item,然后添加 将 ClipData.Item 对象映射到 ClipData 对象。

ClipData 便捷方法

ClipData 类提供了用于创建 ClipData 对象,其中包含一个 ClipData.Item 对象和一个简单 ClipDescription 对象:

newPlainText(label, text)
返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象 包含文本字符串。ClipDescription 对象的标签设置为 labelClipDescription 中的单一 MIME 类型是 MIMETYPE_TEXT_PLAIN

使用 newPlainText() 可从文本字符串创建剪切。

newUri(resolver, label, URI)
返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象 包含 URI。ClipDescription 对象的标签设置为 label。如果该 URI 是内容 URI(也就是说, Uri.getScheme() 返回 content:,该方法会使用 ContentResolver 对象来检索可用的 MIME 类型(位于 resolver 中) content provider。然后,它会将其存储在 ClipDescription 中。对于非 content: URI,此方法会将 MIME 类型设置为 MIMETYPE_TEXT_URILIST

使用 newUri() 根据 URI(尤其是 content: URI。

newIntent(label, intent)
返回一个 ClipData 对象,该对象的单个 ClipData.Item 对象 包含 IntentClipDescription 对象的标签设置为 label。MIME 类型设置为 MIMETYPE_TEXT_INTENT

使用 newIntent() 可从 Intent 对象创建剪切。

将剪贴板数据强制转换为文本

即使您的应用仅处理文本,您也可以通过以下方式从剪贴板复制非文本数据: 将其转换为 ClipData.Item.coerceToText() 方法。

此方法会将 ClipData.Item 中的数据转换为文本,并返回 CharSequenceClipData.Item.coerceToText() 返回的值基于 采用 ClipData.Item 数据形式:

文本
如果 ClipData.Item 是文本,也就是说, getText() 不为 null,coerceToText() 会返回该文本。
URI
如果 ClipData.Item 是一个 URI(也就是说,如果 getUri() 不为 null - coerceToText() 会尝试将其用作内容 URI。
  • 如果该 URI 是内容 URI 且提供程序可以返回文本流, coerceToText() 会返回一个文本流。
  • 如果此 URI 是内容 URI,但提供程序不提供文本流, coerceToText() 会返回 URI 的表示形式。该表示法是 与 Uri.toString()
  • 如果此 URI 不是内容 URI,coerceToText() 会返回以下表示形式: URI该表示形式与 Uri.toString()
Intent
如果 ClipData.ItemIntent(也就是说,如果 getIntent() 不为 null - coerceToText() 会将其转换为 Intent URI 并返回。 该表示形式与 Intent.toUri(URI_INTENT_SCHEME)

图 2 总结了剪贴板框架。为了复制数据,应用会将 ClipboardManager 全局剪贴板中的 ClipData 对象。通过 ClipData 包含一个或多个 ClipData.Item 对象和一个 ClipDescription 对象。如需粘贴数据,应用需要获取 ClipData、 从 ClipDescription 获取其 MIME 类型,并从 ClipData.Item,或者从由 ClipData.Item

<ph type="x-smartling-placeholder">
</ph> 一张图片,显示复制和粘贴框架的方框图
图 2.Android 剪贴板框架。

复制到剪贴板

如需将数据复制到剪贴板,请获取全局 ClipboardManager 对象的句柄, 创建一个 ClipData 对象,并添加一个 ClipDescription 以及一个或多个 ClipData.Item 对象。然后,将完成的 ClipData 对象添加到 ClipboardManager 对象。以下过程对此有进一步的说明:

  1. 如果您要使用内容 URI 复制数据,请设置一个 content provider。
  2. 获取系统剪贴板:

    Kotlin

    when(menuItem.itemId) {
        ...
        R.id.menu_copy -> { // if the user selects copy
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
        }
    }
    

    Java

    ...
    // If the user selects copy.
    case R.id.menu_copy:
    
    // Gets a handle to the clipboard service.
    ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
  3. 将数据复制到新的 ClipData 对象:

    • 适用于文本

      Kotlin

      // Creates a new text clip to put on the clipboard.
      val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
      

      Java

      // Creates a new text clip to put on the clipboard.
      ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
      
    • 对于 URI

      以下代码段通过将记录 ID 编码到以下对象的内容 URI 来构建 URI: 。此技术在 在 URI 部分中对标识符进行编码

      Kotlin

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      const val CONTACTS = "content://com.example.contacts"
      
      // Declares a path string for URIs, used to copy data.
      const val COPY_PATH = "/copy"
      
      // Declares the Uri to paste to the clipboard.
      val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName")
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
      

      Java

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      private static final String CONTACTS = "content://com.example.contacts";
      
      // Declares a path string for URIs, used to copy data.
      private static final String COPY_PATH = "/copy";
      
      // Declares the Uri to paste to the clipboard.
      Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
      
    • 对于 intent

      此代码段为应用构建 Intent,然后将 将其置于剪切对象中:

      Kotlin

      // Creates the Intent.
      val appIntent = Intent(this, com.example.demo.myapplication::class.java)
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      val clip: ClipData = ClipData.newIntent("Intent", appIntent)
      

      Java

      // Creates the Intent.
      Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      ClipData clip = ClipData.newIntent("Intent", appIntent);
      
  4. 将新的剪贴对象放到剪贴板中:

    Kotlin

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip)
    

    Java

    // Set the clipboard's primary clip.
    clipboard.setPrimaryClip(clip);
    

在复制到剪贴板时提供反馈

当应用将内容复制到剪贴板时,用户希望获得视觉反馈。操作完成 但对于 Android 13 及更高版本中的用户,必须手动实现 版本。

从 Android 13 开始,添加内容时,系统会显示标准视觉确认界面 复制到剪贴板新确认界面会执行以下操作:

  • 确认内容已成功复制。
  • 提供所复制内容的预览。

展示 Android 13 剪贴板通知的动画
图 3. 在 Android 中,内容进入剪贴板时显示的界面 13 及更高版本。

在 Android 12L(API 级别 32)及更低版本中,用户可能不确定自己是否已成功复制 内容或复制的内容此功能可将应用在不同时间段显示的各种通知 复制功能,让用户能够更好地控制剪贴板。

避免显示重复的通知

在 Android 12L(API 级别 32)及更低版本中,我们建议在用户成功复制内容后提醒用户 使用 Toast 或 复制后为 Snackbar

为避免重复显示信息,我们强烈建议您移除消息框 或信息提示控件,在 Android 13 及更高版本的应用内复制内容后显示。

在应用内复制内容后显示信息提示控件。
图 4. 如果您在 Android 13 中显示复制确认信息提示控件, 用户会看到重复消息。
<ph type="x-smartling-placeholder">
</ph> 在应用内复制内容后显示消息框。
图 5.如果您在 Android 13 中显示复制确认消息框, 用户会看到重复消息。

以下示例说明了如何实现这一点:

fun textCopyThenPost(textCopied:String) {
    val clipboardManager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
    // When setting the clipboard text.
    clipboardManager.setPrimaryClip(ClipData.newPlainText   ("", textCopied))
    // Only show a toast for Android 12 and lower.
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2)
        Toast.makeText(context, “Copied”, Toast.LENGTH_SHORT).show()
}

将敏感内容添加到剪贴板

如果您的应用允许用户将密码或赠金等敏感内容复制到剪贴板 银行卡信息,您必须在 ClipData 中的 ClipDescription 添加一个标记 然后再调用 ClipboardManager.setPrimaryClip()。添加此标志可防止敏感 在 Android 13 及更高版本中,禁止在复制内容的视觉确认部分中显示内容。

所复制文本的预览(未标记敏感内容)
图 6. 所复制文本的预览(不带敏感内容标记)。
<ph type="x-smartling-placeholder">
</ph> 所复制文本的预览(已标记敏感内容)。
图 7.所复制文本的预览(带有敏感内容标记)。

如需标记敏感内容,请向 ClipDescription 添加一个布尔型 extra。所有应用都必须遵循 无论目标 API 级别如何。

// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

// If your app is compiled with a lower SDK.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean("android.content.extra.IS_SENSITIVE", true)
    }
}

从剪贴板粘贴

如前所述,通过获取全局剪贴板对象,从剪贴板中粘贴数据, 获取剪贴对象,查看其数据,如果可以,从剪贴对象复制数据 复制到自己的存储空间中本部分详细介绍了如何粘贴三种形式的剪贴板 数据。

<ph type="x-smartling-placeholder">

粘贴纯文本

如需粘贴纯文本,请获取全局剪贴板并验证它能否返回纯文本。然后获取 剪切对象,并使用 getText() 将其文本复制到您自己的存储空间,如 具体步骤如下:

  1. 使用以下命令获取全局 ClipboardManager 对象: getSystemService(CLIPBOARD_SERVICE)。此外,声明一个全局变量,以包含 粘贴的文本:

    Kotlin

    var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    var pasteData: String = ""
    

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
    
  2. 确定是否需要启用或停用“粘贴”功能选项 活动。验证剪贴板是否包含剪切,以及您是否可以处理数据类型 以下片段所表示:

    Kotlin

    // Gets the ID of the "paste" menu item.
    val pasteItem: MenuItem = menu.findItem(R.id.menu_paste)
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    pasteItem.isEnabled = when {
        !clipboard.hasPrimaryClip() -> {
            false
        }
        !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> {
            // Disables the paste menu item, since the clipboard has data but it
            // isn't plain text.
            false
        }
        else -> {
            // Enables the paste menu item, since the clipboard contains plain text.
            true
        }
    }
    

    Java

    // Gets the ID of the "paste" menu item.
    MenuItem pasteItem = menu.findItem(R.id.menu_paste);
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    if (!(clipboard.hasPrimaryClip())) {
    
        pasteItem.setEnabled(false);
    
    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
    
        // Disables the paste menu item, since the clipboard has data but
        // it isn't plain text.
        pasteItem.setEnabled(false);
    } else {
    
        // Enables the paste menu item, since the clipboard contains plain text.
        pasteItem.setEnabled(true);
    }
    
  3. 从剪贴板复制数据。代码中的该时间点只有在 “粘贴”所以您可以假定剪贴板中包含纯文本 文本。您目前还不知道它是否包含文本字符串或指向纯文本的 URI。 以下代码段对此进行了测试,但它仅显示用于处理纯文本的代码:

    Kotlin

    when (menuItem.itemId) {
        ...
        R.id.menu_paste -> {    // Responds to the user selecting "paste".
            // Examines the item on the clipboard. If getText() doesn't return null,
            // the clip item contains the text. Assumes that this application can only
            // handle one item at a time.
            val item = clipboard.primaryClip.getItemAt(0)
    
            // Gets the clipboard as text.
            pasteData = item.text
    
            return if (pasteData != null) {
                // If the string contains data, then the paste operation is done.
                true
            } else {
                // The clipboard doesn't contain text. If it contains a URI,
                // attempts to get data from it.
                val pasteUri: Uri? = item.uri
    
                if (pasteUri != null) {
                    // If the URI contains something, try to get text from it.
    
                    // Calls a routine to resolve the URI and get data from it.
                    // This routine isn't presented here.
                    pasteData = resolveUri(pasteUri)
                    true
                } else {
    
                    // Something is wrong. The MIME type was plain text, but the
                    // clipboard doesn't contain text or a Uri. Report an error.
                    Log.e(TAG,"Clipboard contains an invalid data type")
                    false
                }
            }
        }
    }
    

    Java

    // Responds to the user selecting "paste".
    case R.id.menu_paste:
    
    // Examines the item on the clipboard. If getText() does not return null,
    // the clip item contains the text. Assumes that this application can only
    // handle one item at a time.
     ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
    
    // Gets the clipboard as text.
    pasteData = item.getText();
    
    // If the string contains data, then the paste operation is done.
    if (pasteData != null) {
        return true;
    
    // The clipboard doesn't contain text. If it contains a URI, attempts to get
    // data from it.
    } else {
        Uri pasteUri = item.getUri();
    
        // If the URI contains something, try to get text from it.
        if (pasteUri != null) {
    
            // Calls a routine to resolve the URI and get data from it.
            // This routine isn't presented here.
            pasteData = resolveUri(Uri);
            return true;
        } else {
    
            // Something is wrong. The MIME type is plain text, but the
            // clipboard doesn't contain text or a Uri. Report an error.
            Log.e(TAG, "Clipboard contains an invalid data type");
            return false;
        }
    }
    

从内容 URI 中粘贴数据

如果 ClipData.Item 对象包含内容 URI,并且您确定可以 处理它的某个 MIME 类型,创建一个 ContentResolver 并调用适当的内容 provider 方法检索数据。

以下过程介绍了如何根据内容 URI 从内容提供程序获取数据。 剪贴板中。它会检查 提供商。

  1. 声明一个全局变量以包含 MIME 类型:

    Kotlin

    // Declares a MIME type constant to match against the MIME types offered
    // by the provider.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
    

    Java

    // Declares a MIME type constant to match against the MIME types offered by
    // the provider.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
    
  2. 获取全局剪贴板。另外,获取一个内容解析器,以便您可以访问 Content Provider:

    Kotlin

    // Gets a handle to the Clipboard Manager.
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
    // Gets a content resolver instance.
    val cr = contentResolver
    

    Java

    // Gets a handle to the Clipboard Manager.
    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    
    // Gets a content resolver instance.
    ContentResolver cr = getContentResolver();
    
  3. 从剪贴板中获取主要剪切,并获取其内容作为 URI:

    Kotlin

    // Gets the clipboard data from the clipboard.
    val clip: ClipData? = clipboard.primaryClip
    
    clip?.run {
    
        // Gets the first item from the clipboard data.
        val item: ClipData.Item = getItemAt(0)
    
        // Tries to get the item's contents as a URI.
        val pasteUri: Uri? = item.uri
    

    Java

    // Gets the clipboard data from the clipboard.
    ClipData clip = clipboard.getPrimaryClip();
    
    if (clip != null) {
    
        // Gets the first item from the clipboard data.
        ClipData.Item item = clip.getItemAt(0);
    
        // Tries to get the item's contents as a URI.
        Uri pasteUri = item.getUri();
    
  4. 通过调用以下方法来测试 URI 是否为内容 URI: getType(Uri)。 如果 Uri 不指向有效的 content provider,此方法会返回 null。

    Kotlin

        // If the clipboard contains a URI reference...
        pasteUri?.let {
    
            // ...is this a content URI?
            val uriMimeType: String? = cr.getType(it)
    

    Java

        // If the clipboard contains a URI reference...
        if (pasteUri != null) {
    
            // ...is this a content URI?
            String uriMimeType = cr.getType(pasteUri);
    
  5. 测试 content provider 是否支持应用能够理解的 MIME 类型。如果 是的,调用 ContentResolver.query() 来获取数据返回值是 Cursor

    Kotlin

            // If the return value isn't null, the Uri is a content Uri.
            uriMimeType?.takeIf {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                it == MIME_TYPE_CONTACT
            }?.apply {
    
                // Get the data from the content provider.
                cr.query(pasteUri, null, null, null, null)?.use { pasteCursor ->
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                    }
    
                    // Kotlin `use` automatically closes the Cursor.
                }
            }
        }
    }
    

    Java

            // If the return value isn't null, the Uri is a content Uri.
            if (uriMimeType != null) {
    
                // Does the content provider offer a MIME type that the current
                // application can use?
                if (uriMimeType.equals(MIME_TYPE_CONTACT)) {
    
                    // Get the data from the content provider.
                    Cursor pasteCursor = cr.query(uri, null, null, null, null);
    
                    // If the Cursor contains data, move to the first record.
                    if (pasteCursor != null) {
                        if (pasteCursor.moveToFirst()) {
    
                        // Get the data from the Cursor here.
                        // The code varies according to the format of the data model.
                        }
                    }
    
                    // Close the Cursor.
                    pasteCursor.close();
                 }
             }
         }
    }
    

粘贴 intent

如需粘贴 intent,请先获取全局剪贴板。检查 ClipData.Item 对象 以查看其是否包含 Intent。然后调用 getIntent(),将 复制到自己的存储空间中以下代码段演示了此过程:

Kotlin

// Gets a handle to the Clipboard Manager.
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

// Checks whether the clip item contains an Intent by testing whether
// getIntent() returns null.
val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Java

// Gets a handle to the Clipboard Manager.
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks whether the clip item contains an Intent, by testing whether
// getIntent() returns null.
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

在应用访问剪贴板数据时显示系统通知

在 Android 12(API 级别 31)及更高版本中,系统通常会在您的应用时显示消息框消息 通话 getPrimaryClip()。 消息中的文本包含以下格式:

APP pasted from your clipboard

当应用执行以下任一操作时,系统不会显示消息框消息:

  • 访问 ClipData
  • 从特定应用反复访问 ClipData。仅当 您的应用将首次访问该应用中的数据。
  • 检索剪贴对象的元数据,例如通过调用 getPrimaryClipDescription() 而非 getPrimaryClip()

使用 content provider 复制复杂的数据

content provider 支持复制数据库记录或文件流等复杂的数据。复制 请将内容 URI 放到剪贴板中。然后,粘贴应用会从 剪贴板并使用它来检索数据库数据或文件流描述符。

由于粘贴应用只有您的数据的内容 URI,因此它需要知道 数据块进行检索。您可以通过对数据的标识符进行编码来提供此信息 也可以提供一个唯一的 URI,用于返回您要复制的数据。哪个 您选择的方法取决于数据的组织方式。

以下各部分介绍了如何设置 URI、提供复杂数据以及提供文件 。这些说明假定您熟悉 content provider 的一般原则 设计。

对 URI 中的标识符进行编码

通过 URI 将数据复制到剪贴板的一种实用方法是,对 URI 本身包含的数据然后,Content Provider 便可从 URI 中获取此标识符,并使用 以检索数据。粘贴应用不必知道该标识符的存在。它 只需从您的 读取剪贴板,为其提供内容提供程序,然后获取数据。

通常情况下,您可以通过将标识符连接到内容 URI 的末尾,将标识符编码到内容 URI 中。 例如,假设您将提供程序 URI 定义为以下字符串:

"content://com.example.contacts"

如果您希望将某个名称编码到此 URI,请使用以下代码段:

Kotlin

val uriString = "content://com.example.contacts/Smith"

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
val copyUri = Uri.parse(uriString)

Java

String uriString = "content://com.example.contacts" + "/" + "Smith";

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
Uri copyUri = Uri.parse(uriString);

如果您已经在使用内容提供程序,则可能需要添加指示 URI 用于复制例如,假设您已拥有以下 URI 路径:

"content://com.example.contacts/people"
"content://com.example.contacts/people/detail"
"content://com.example.contacts/people/images"

您可以添加另一个用于复制 URI 的路径:

"content://com.example.contacts/copying"

然后,您便可检测出通过模式匹配找到 URI,并使用符合 专门用于复制和粘贴

如果您已经在使用内容提供程序、内部 数据库或内部表来整理数据。在这些情况下,您会有多项数据 并且假定每部分都有一个唯一标识符在响应来自 您可以根据数据标识符查找数据并返回。

如果您没有多份数据,则可能不需要对标识符进行编码。 您可以使用提供程序独有的 URI。在响应查询时,您的提供程序会返回 当前包含的数据。

复制数据结构

将用于复制和粘贴复杂数据的 content provider 设置为 ContentProvider 组件。对您放到剪贴板中的 URI 进行编码,使其指向您要访问的确切记录 提供。此外,请考虑应用的现有状态:

  • 如果您已有 Content Provider,则可以添加其功能。您可能只有 需要修改其 query() 方法,以处理来自应用的 URI, 粘贴数据。您可能需要修改方法来处理“副本”URI 模式。
  • 如果您的应用维护一个内部数据库,您可能需要迁移此数据库 复制到内容提供程序中,以便于从中复制。
  • 如果不使用数据库,则可以实现一个简单的 content provider,其 的唯一目的是向从剪贴板粘贴内容的应用提供数据。

在 content provider 中,请至少替换以下方法:

query()
粘贴应用假定它们可以通过使用此方法和您提供的 URI 来获取数据 放到剪贴板中要支持复制,请让此方法检测包含特殊的 “copy”路径。然后,您的应用就可以创建“副本”放在 剪贴板,其中包含复制路径和指向要复制的确切记录的指针。
getType()
此方法必须返回要复制的数据的 MIME 类型。方法 newUri() 调用 getType() 以将 MIME 类型放入新的 ClipData 中 对象。

如需了解复杂数据的 MIME 类型,请参阅 内容提供程序

您无需使用任何其他 content provider 方法,例如 insert()update()。 粘贴应用只需获取受支持的 MIME 类型,并从提供程序复制数据。 如果您已拥有这些方法,它们不会干扰复制操作。

以下代码段演示了如何设置应用以复制复杂数据:

  1. 在应用的全局常量中,声明一个基本 URI 字符串和一个 标识您用于复制数据的 URI 字符串。另外,声明所复制的 MIME 类型 数据。

    Kotlin

    // Declares the base URI string.
    private const val CONTACTS = "content://com.example.contacts"
    
    // Declares a path string for URIs that you use to copy data.
    private const val COPY_PATH = "/copy"
    
    // Declares a MIME type for the copied data.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
    

    Java

    // Declares the base URI string.
    private static final String CONTACTS = "content://com.example.contacts";
    
    // Declares a path string for URIs that you use to copy data.
    private static final String COPY_PATH = "/copy";
    
    // Declares a MIME type for the copied data.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
    
  2. 在用户从中复制数据的 activity 中,设置用于将数据复制到剪贴板的代码。 为响应复制请求,将 URI 放到剪贴板中。

    Kotlin

    class MyCopyActivity : Activity() {
        ...
    when(item.itemId) {
        R.id.menu_copy -> { // The user has selected a name and is requesting a copy.
            // Appends the last name to the base URI.
            // The name is stored in "lastName".
            uriString = "$CONTACTS$COPY_PATH/$lastName"
    
            // Parses the string into a URI.
            val copyUri: Uri? = Uri.parse(uriString)
    
            // Gets a handle to the clipboard service.
            val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
            val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
    
            // Sets the clipboard's primary clip.
            clipboard.setPrimaryClip(clip)
        }
    }
    

    Java

    public class MyCopyActivity extends Activity {
        ...
    // The user has selected a name and is requesting a copy.
    case R.id.menu_copy:
    
        // Appends the last name to the base URI.
        // The name is stored in "lastName".
        uriString = CONTACTS + COPY_PATH + "/" + lastName;
    
        // Parses the string into a URI.
        Uri copyUri = Uri.parse(uriString);
    
        // Gets a handle to the clipboard service.
        ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    
        // Sets the clipboard's primary clip.
        clipboard.setPrimaryClip(clip);
    
  3. 在 content provider 的全局范围内,创建一个 URI 匹配器并添加一个 URI 模式, 与您放到剪贴板中的 URI 匹配。

    Kotlin

    // A Uri Match object that simplifies matching content URIs to patterns.
    private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    
        // Adds a matcher for the content URI. It matches.
        // "content://com.example.contacts/copy/*"
        addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT)
    }
    
    // An integer to use in switching based on the incoming URI pattern.
    private const val GET_SINGLE_CONTACT = 0
    ...
    class MyCopyProvider : ContentProvider() {
        ...
    }
    

    Java

    public class MyCopyProvider extends ContentProvider {
        ...
    // A Uri Match object that simplifies matching content URIs to patterns.
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
    // An integer to use in switching based on the incoming URI pattern.
    private static final int GET_SINGLE_CONTACT = 0;
    ...
    // Adds a matcher for the content URI. It matches
    // "content://com.example.contacts/copy/*"
    sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
    
  4. 设置 query() 方法。此方法可处理不同的 URI 模式(具体取决于您的编码方式),但仅适用于 即会显示剪贴板复制操作的模式

    Kotlin

    // Sets up your provider's query() method.
    override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        ...
        // When based on the incoming content URI:
        when(sUriMatcher.match(uri)) {
    
            GET_SINGLE_CONTACT -> {
    
                // Queries and returns the contact for the requested name. Decodes
                // the incoming URI, queries the data model based on the last name,
                // and returns the result as a Cursor.
            }
        }
        ...
    }
    

    Java

    // Sets up your provider's query() method.
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
        ...
        // Switch based on the incoming content URI.
        switch (sUriMatcher.match(uri)) {
    
        case GET_SINGLE_CONTACT:
    
            // Queries and returns the contact for the requested name. Decodes the
            // incoming URI, queries the data model based on the last name, and
            // returns the result as a Cursor.
        ...
    }
    
  5. 设置 getType() 方法,以便返回复制的 MIME 类型 数据:

    Kotlin

    // Sets up your provider's getType() method.
    override fun getType(uri: Uri): String? {
        ...
        return when(sUriMatcher.match(uri)) {
            GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT
            ...
        }
    }
    

    Java

    // Sets up your provider's getType() method.
    public String getType(Uri uri) {
        ...
        switch (sUriMatcher.match(uri)) {
        case GET_SINGLE_CONTACT:
            return (MIME_TYPE_CONTACT);
        ...
        }
    }
    

从内容 URI 中粘贴数据部分介绍了如何获取 内容 URI,并使用它来获取和粘贴数据。

复制数据流

您可以以流的形式复制和粘贴大量文本数据和二进制数据。数据可以包含表单 例如:

  • 存储在实际设备上的文件
  • 来自套接字的流
  • 存储在提供程序的底层数据库系统中的大量数据

数据流的 content provider 通过文件描述符对象提供对其数据的访问, 例如 AssetFileDescriptor, 而不是 Cursor 对象。粘贴应用使用此 文件描述符。

如需设置您的应用以通过提供商复制数据流,请按以下步骤操作:

  1. 为要放到剪贴板中的数据流设置内容 URI。您可以使用以下选项执行此操作:
    • 将数据流的标识符编码到 URI 中,如 在 URI 部分对标识符进行编码,然后维护 表,其中包含标识符和相应的数据流名称。
    • 直接在 URI 中对流名称进行编码。
    • 使用一个始终会从提供程序返回当前流的唯一 URI。如果您 使用此选项,请记得更新提供商以指向其他视频流 。
  2. 为您计划提供的每种数据流类型提供 MIME 类型。正在粘贴应用 需要此信息来确定能否将数据粘贴到剪贴板中。
  3. 实现一个会返回以下文件描述符的 ContentProvider 方法之一: 数据流。如果您在内容 URI 中对标识符进行编码,可以使用此方法来确定 才能打开该数据流。
  4. 要将数据流复制到剪贴板,请构建内容 URI 并将其放在 剪贴板。

要粘贴数据流,应用需要从剪贴板获取剪切,获取 URI,然后使用 调用用于打开流的 ContentResolver 文件描述符方法。通过 ContentResolver 方法会调用相应的 ContentProvider 方法, 将内容 URI 传递给它。您的提供程序会将文件描述符返回给 ContentResolver 方法结合使用。然后,粘贴应用负责读取 从数据流中获取数据

以下列表显示了对 content provider 而言最重要的文件描述符方法。每个 都有相应的 ContentResolver 方法,方法为 “描述符”附加到方法名称。例如,ContentResolver 模拟 openAssetFile()openAssetFileDescriptor()

openTypedAssetFile()

此方法会返回一个资源文件描述符,但仅当提供的 MIME 类型为 。调用方(执行粘贴的应用)会提供 MIME 类型模式。用于将 URI 复制到 如果剪贴板可以提供该句柄,则返回 AssetFileDescriptor 文件句柄 MIME 类型,如果无法实现,则会抛出异常。

此方法可处理文件的子部分。您可以使用它来读取 content provider 已复制到剪贴板。

openAssetFile()
此方法是 openTypedAssetFile() 的更通用的形式。不过滤 的 MIME 类型,但它可以读取文件的子部分。
openFile()
这是 openAssetFile() 的更通用的形式。它无法读取 文件。

您可以选择使用 openPipeHelper() 方法。这样,粘贴应用便可以在 后台线程。要使用此方法,请实现 ContentProvider.PipeDataWriter 界面。

设计有效的复制和粘贴功能

要为您的应用设计有效的复制和粘贴功能,请记住以下几点:

  • 在任何时候,剪贴板中都只能有一个剪切。由任意 应用会覆盖上一个剪辑。由于用户可能会浏览 离开应用,并在返回前进行复制, 包含用户之前在您的应用中复制的剪辑。
  • 每个剪辑有多个 ClipData.Item 对象的预期目的是 支持复制和粘贴多个选项,而不是不同形式的选择 对单个选项的引用。您通常需要所有的ClipData.Item 对象具有相同的形式。也就是说,它们必须均为简单的文字、内容 URI 或 Intent,而非混合使用。
  • 提供数据时,您可以提供不同的 MIME 表示形式。添加 MIME 类型 支持 ClipDescription,然后在 内容提供方。
  • 当您从剪贴板获取数据时,您的应用负责检查可用的 MIME 类型,并决定使用哪种类型(如果有)。即使有 剪切,并且用户请求粘贴,则您的应用不是必需的 执行粘贴操作如果 MIME 类型兼容,请执行粘贴操作。您可能会通过强制转换 使用 coerceToText() 将剪贴板中的内容转换为文本。如果您的应用支持 多种可用的 MIME 类型,您可以让用户选择要使用的类型。