Android 提供了一个强大的基于剪贴板的框架,用于复制和粘贴。它支持简单的 和复杂数据类型,包括文本字符串、复杂数据结构、文本和二进制流 数据和应用资产简单的文本数据直接存储在剪贴板中,而复杂的 数据会存储为引用,粘贴应用使用 content provider 解析该引用。正在复制 和粘贴既可在应用内进行,也可在实现 框架。
由于框架的一部分使用内容提供程序,因此本文档假定您对 Android Content Provider API,这在 内容提供程序。
在将内容复制到剪贴板时,用户希望获得反馈,因此,除了 支持复制和粘贴功能,在 Android 13(API 级别 33)中复制时,Android 会向用户显示默认界面 和更高版本。因为此功能,有出现重复通知的风险。如需详细了解 在这个极端情况下, 避免收到重复的通知 部分。
![展示 Android 13 剪贴板通知的动画](https://developer.android.google.cn/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=9&hl=lv)
在 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
对象,其中包含
对数据和数据本身的说明剪贴板中每次放置一个 ClipData
。ClipData
包含
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
对象的标签设置为label
。ClipDescription
中的单一 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
对象 包含Intent
。ClipDescription
对象的标签设置为label
。MIME 类型设置为MIMETYPE_TEXT_INTENT
。使用
newIntent()
可从Intent
对象创建剪切。
将剪贴板数据强制转换为文本
即使您的应用仅处理文本,您也可以通过以下方式从剪贴板复制非文本数据:
将其转换为
ClipData.Item.coerceToText()
方法。
此方法会将 ClipData.Item
中的数据转换为文本,并返回
CharSequence
。ClipData.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()
。
- 如果该 URI 是内容 URI 且提供程序可以返回文本流,
- Intent
- 如果
ClipData.Item
为Intent
(也就是说,如果getIntent()
不为 null -coerceToText()
会将其转换为 Intent URI 并返回。 该表示形式与Intent.toUri(URI_INTENT_SCHEME)
。
图 2 总结了剪贴板框架。为了复制数据,应用会将
ClipboardManager
全局剪贴板中的 ClipData
对象。通过
ClipData
包含一个或多个 ClipData.Item
对象和一个
ClipDescription
对象。如需粘贴数据,应用需要获取 ClipData
、
从 ClipDescription
获取其 MIME 类型,并从
ClipData.Item
,或者从由
ClipData.Item
。
![一张图片,显示复制和粘贴框架的方框图](https://developer.android.google.cn/static/images/ui/clipboard/copy_paste_framework.png?authuser=9&hl=lv)
复制到剪贴板
如需将数据复制到剪贴板,请获取全局 ClipboardManager
对象的句柄,
创建一个 ClipData
对象,并添加一个 ClipDescription
以及一个或多个
ClipData.Item
对象。然后,将完成的 ClipData
对象添加到
ClipboardManager
对象。以下过程对此有进一步的说明:
- 如果您要使用内容 URI 复制数据,请设置一个 content provider。
- 获取系统剪贴板:
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);
-
将数据复制到新的
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);
-
适用于文本
-
将新的剪贴对象放到剪贴板中:
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 剪贴板通知的动画](https://developer.android.google.cn/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=9&hl=lv)
在 Android 12L(API 级别 32)及更低版本中,用户可能不确定自己是否已成功复制 内容或复制的内容此功能可将应用在不同时间段显示的各种通知 复制功能,让用户能够更好地控制剪贴板。
避免显示重复的通知
在 Android 12L(API 级别 32)及更低版本中,我们建议在用户成功复制内容后提醒用户
使用 Toast
或
复制后为 Snackbar
。
为避免重复显示信息,我们强烈建议您移除消息框 或信息提示控件,在 Android 13 及更高版本的应用内复制内容后显示。
![在应用内复制内容后显示信息提示控件。](https://developer.android.google.cn/static/images/about/versions/13/snackbar-overlap.png?authuser=9&hl=lv)
![在应用内复制内容后显示消息框。](https://developer.android.google.cn/static/images/about/versions/13/toast-overlap.png?authuser=9&hl=lv)
以下示例说明了如何实现这一点:
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 及更高版本中,禁止在复制内容的视觉确认部分中显示内容。
![所复制文本的预览(未标记敏感内容)](https://developer.android.google.cn/static/images/about/versions/13/sensitive-content-before.png?authuser=9&hl=lv)
![所复制文本的预览(已标记敏感内容)。](https://developer.android.google.cn/static/images/about/versions/13/sensitive-content-after.png?authuser=9&hl=lv)
如需标记敏感内容,请向 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()
将其文本复制到您自己的存储空间,如
具体步骤如下:
- 使用以下命令获取全局
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 = "";
- 确定是否需要启用或停用“粘贴”功能选项
活动。验证剪贴板是否包含剪切,以及您是否可以处理数据类型
以下片段所表示:
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); }
- 从剪贴板复制数据。代码中的该时间点只有在
“粘贴”所以您可以假定剪贴板中包含纯文本
文本。您目前还不知道它是否包含文本字符串或指向纯文本的 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 从内容提供程序获取数据。 剪贴板中。它会检查 提供商。
-
声明一个全局变量以包含 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";
- 获取全局剪贴板。另外,获取一个内容解析器,以便您可以访问 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();
- 从剪贴板中获取主要剪切,并获取其内容作为 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();
- 通过调用以下方法来测试 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);
- 测试 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 类型,并从提供程序复制数据。
如果您已拥有这些方法,它们不会干扰复制操作。
以下代码段演示了如何设置应用以复制复杂数据:
-
在应用的全局常量中,声明一个基本 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";
- 在用户从中复制数据的 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);
-
在 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);
-
设置
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. ... }
-
设置
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
对象。粘贴应用使用此
文件描述符。
如需设置您的应用以通过提供商复制数据流,请按以下步骤操作:
-
为要放到剪贴板中的数据流设置内容 URI。您可以使用以下选项执行此操作:
- 将数据流的标识符编码到 URI 中,如 在 URI 部分对标识符进行编码,然后维护 表,其中包含标识符和相应的数据流名称。
- 直接在 URI 中对流名称进行编码。
- 使用一个始终会从提供程序返回当前流的唯一 URI。如果您 使用此选项,请记得更新提供商以指向其他视频流 。
- 为您计划提供的每种数据流类型提供 MIME 类型。正在粘贴应用 需要此信息来确定能否将数据粘贴到剪贴板中。
- 实现一个会返回以下文件描述符的
ContentProvider
方法之一: 数据流。如果您在内容 URI 中对标识符进行编码,可以使用此方法来确定 才能打开该数据流。 - 要将数据流复制到剪贴板,请构建内容 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 类型,您可以让用户选择要使用的类型。