Android предоставляет мощный фреймворк на основе буфера обмена для копирования и вставки. Он поддерживает простые и сложные типы данных, включая текстовые строки, сложные структуры данных, текстовые и двоичные потоковые данные и ресурсы приложений. Простые текстовые данные хранятся непосредственно в буфере обмена, в то время как сложные данные хранятся как ссылка, которую вставляющее приложение разрешает с поставщиком контента. Копирование и вставка работают как внутри приложения, так и между приложениями, реализующими фреймворк.
Поскольку часть фреймворка использует поставщиков контента, этот документ предполагает некоторое знакомство с API поставщиков контента Android, который описан в разделе Поставщики контента .
Пользователи ожидают обратной связи при копировании контента в буфер обмена, поэтому в дополнение к фреймворку, который обеспечивает копирование и вставку, Android показывает пользователям пользовательский интерфейс по умолчанию при копировании в Android 13 (уровень API 33) и выше. Из-за этой функции существует риск дублирования уведомлений. Вы можете узнать больше об этом пограничном случае в разделе Избегайте дублирования уведомлений .

Вручную предоставляйте обратную связь пользователям при копировании в Android 12L (уровень API 32) и ниже. Смотрите рекомендации по этому вопросу в этом документе.
Структура буфера обмена
При использовании фреймворка буфера обмена поместите данные в объект клипа, а затем поместите объект клипа в системный буфер обмена. Объект клипа может принимать одну из трех форм:
- Текст
- Текстовая строка. Поместите строку непосредственно в объект клипа, который затем поместите в буфер обмена. Чтобы вставить строку, извлеките объект клипа из буфера обмена и скопируйте строку в хранилище вашего приложения.
- URI
- Объект
Uri
, представляющий любую форму URI. Это в первую очередь для копирования сложных данных из поставщика контента. Чтобы скопировать данные, поместите объектUri
в объект клипа и поместите объект клипа в буфер обмена. Чтобы вставить данные, получите объект клипа, получите объектUri
, разрешите его источнику данных, например поставщику контента, и скопируйте данные из источника в хранилище вашего приложения. - Намерение
-
Intent
. Поддерживает копирование ярлыков приложений. Чтобы скопировать данные, создайтеIntent
, поместите его в объект clip и поместите объект clip в буфер обмена. Чтобы вставить данные, получите объект clip и затем скопируйте объектIntent
в область памяти вашего приложения.
Буфер обмена содержит только один объект клипа за раз. Когда приложение помещает объект клипа в буфер обмена, предыдущий объект клипа исчезает.
Если вы хотите, чтобы пользователи могли вставлять данные в ваше приложение, вам не обязательно обрабатывать все типы данных. Вы можете проверить данные в буфере обмена, прежде чем предоставить пользователям возможность вставить их. Помимо определенной формы данных, объект clip также содержит метаданные, сообщающие вам, какие типы MIME доступны. Эти метаданные помогают вам решить, может ли ваше приложение сделать что-то полезное с данными буфера обмена. Например, если у вас есть приложение, которое в основном обрабатывает текст, вы можете игнорировать объекты clip, содержащие URI или намерение.
Вы также можете позволить пользователям вставлять текст независимо от формы данных в буфере обмена. Для этого принудительно преобразуйте данные буфера обмена в текстовое представление, а затем вставьте этот текст. Это описано в разделе Приведение буфера обмена к тексту .
Классы буфера обмена
В этом разделе описываются классы, используемые фреймворком буфера обмена.
Менеджер буфера обмена
Системный буфер обмена Android представлен глобальным классом ClipboardManager
. Не создавайте экземпляр этого класса напрямую. Вместо этого получите ссылку на него, вызвав getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item и ClipDescription
Чтобы добавить данные в буфер обмена, создайте объект ClipData
, содержащий описание данных и сами данные. Буфер обмена содержит один ClipData
за раз. ClipData
содержит объект ClipDescription
и один или несколько объектов ClipData.Item
.
Объект ClipDescription
содержит метаданные о клипе. В частности, он содержит массив доступных типов MIME для данных клипа. Кроме того, на Android 12 (уровень API 31) и выше метаданные включают информацию о том, содержит ли объект стилизованный текст , и о типе текста в объекте . Когда вы помещаете клип в буфер обмена, эта информация становится доступной для вставки приложений, которые могут проверить, могут ли они обработать данные клипа.
Объект ClipData.Item
содержит текст, URI или данные намерения:
- Текст
-
CharSequence
. - URI
-
Uri
. Обычно содержит URI поставщика контента, хотя разрешен любой URI. Приложение, предоставляющее данные, помещает URI в буфер обмена. Приложения, которые хотят вставить данные, получают URI из буфера обмена и используют его для доступа к поставщику контента или другому источнику данных и извлечения данных. - Намерение
-
Intent
. Этот тип данных позволяет копировать ярлык приложения в буфер обмена. Затем пользователи могут вставить ярлык в свои приложения для дальнейшего использования.
Вы можете добавить более одного объекта ClipData.Item
в клип. Это позволяет пользователям копировать и вставлять несколько выделенных элементов как один клип. Например, если у вас есть виджет списка, который позволяет пользователю выбирать более одного элемента за раз, вы можете скопировать все элементы в буфер обмена одновременно. Для этого создайте отдельный ClipData.Item
для каждого элемента списка, а затем добавьте объекты ClipData.Item
в объект ClipData
.
Удобные методы ClipData
Класс ClipData
предоставляет статические удобные методы для создания объекта ClipData
с одним объектом ClipData.Item
и простым объектом ClipDescription
:
-
newPlainText(label, text)
- Возвращает объект
ClipData
, единственный объектClipData.Item
которого содержит текстовую строку. Метка объектаClipDescription
установлена наlabel
. Единственный тип MIME вClipDescription
—MIMETYPE_TEXT_PLAIN
.Используйте
newPlainText()
для создания клипа из текстовой строки. -
newUri(resolver, label, URI)
- Возвращает объект
ClipData
, единственный объектClipData.Item
которого содержит URI. Метка объектаClipDescription
установлена наlabel
. Если URI является URI контента, то есть еслиUri.getScheme()
возвращаетcontent:
, метод использует объектContentResolver
, предоставленный вresolver
, для извлечения доступных типов MIME из поставщика контента. Затем он сохраняет их вClipDescription
. Для URI, который не является URIcontent:
, метод устанавливает тип 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 контента и поставщик может вернуть текстовый поток,
- Намерение
- Если
ClipData.Item
являетсяIntent
— то есть, еслиgetIntent()
не равен null —coerceToText()
преобразует его в Intent URI и возвращает его. Представление такое же, как и возвращаемоеIntent.toUri(URI_INTENT_SCHEME)
.
Структура буфера обмена представлена на рисунке 2. Чтобы скопировать данные, приложение помещает объект ClipData
в глобальный буфер обмена ClipboardManager
. ClipData
содержит один или несколько объектов ClipData.Item
и один объект ClipDescription
. Чтобы вставить данные, приложение получает ClipData
, получает его тип MIME из ClipDescription
и получает данные из ClipData.Item
или от поставщика контента, на которого ссылается ClipData.Item
.

Копировать в буфер обмена
Чтобы скопировать данные в буфер обмена, получите дескриптор глобального объекта ClipboardManager
, создайте объект ClipData
и добавьте к нему ClipDescription
и один или несколько объектов ClipData.Item
. Затем добавьте готовый объект ClipData
к объекту ClipboardManager
. Это подробно описано в следующей процедуре:
- Если вы копируете данные с помощью URI контента, настройте поставщика контента.
- Получить системный буфер обмена:
Котлин
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 } }
Ява
... // 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
:- Для текста
Котлин
// Creates a new text clip to put on the clipboard. val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
Ява
// Creates a new text clip to put on the clipboard. ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
- Для URI
Этот фрагмент создает URI путем кодирования идентификатора записи в URI контента для поставщика. Этот метод более подробно рассматривается в разделе Кодирование идентификатора в URI .
Котлин
// 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)
Ява
// 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
для приложения, а затем помещает его в объект клипа:Котлин
// 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)
Ява
// 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);
- Для текста
- Поместите новый объект клипа в буфер обмена:
Котлин
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Ява
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
Предоставлять обратную связь при копировании в буфер обмена
Пользователи ожидают визуальной обратной связи, когда приложение копирует контент в буфер обмена. Это делается автоматически для пользователей Android 13 и выше, но это должно быть реализовано вручную в предыдущих версиях.
Начиная с Android 13, система отображает стандартное визуальное подтверждение при добавлении контента в буфер обмена. Новое подтверждение делает следующее:
- Подтверждает, что содержимое было успешно скопировано.
- Предоставляет предварительный просмотр скопированного содержимого.

В Android 12L (API уровня 32) и ниже пользователи могут быть не уверены, успешно ли они скопировали контент или что именно они скопировали. Эта функция стандартизирует различные уведомления, отображаемые приложениями после копирования, и предлагает пользователям больше контроля над буфером обмена.
Избегайте дублирования уведомлений
В Android 12L (уровень API 32) и ниже мы рекомендуем оповещать пользователей об успешном копировании, выдавая визуальную обратную связь в приложении с помощью виджета, например Toast
или Snackbar
, после копирования.
Чтобы избежать дублирования отображения информации, мы настоятельно рекомендуем удалить всплывающие уведомления или закусочные панели, отображаемые после копии внутри приложения для 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() }
Добавить конфиденциальную информацию в буфер обмена
Если ваше приложение позволяет пользователям копировать конфиденциальный контент в буфер обмена, например пароли или данные кредитной карты, необходимо добавить флаг в ClipDescription
в ClipData
перед вызовом ClipboardManager.setPrimaryClip()
. Добавление этого флага предотвращает появление конфиденциального контента в визуальном подтверждении скопированного контента в Android 13 и выше.


Чтобы отметить конфиденциальный контент, добавьте логическое дополнение к ClipDescription
. Все приложения должны делать это, независимо от целевого уровня 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) } }
Вставить из буфера обмена
Как было описано ранее, вставьте данные из буфера обмена, получив глобальный объект буфера обмена, получив объект клипа, просмотрев его данные и, если возможно, скопировав данные из объекта клипа в собственное хранилище. В этом разделе подробно объясняется, как вставить три формы данных буфера обмена.
Вставить обычный текст
Чтобы вставить простой текст, получите глобальный буфер обмена и убедитесь, что он может возвращать простой текст. Затем получите объект клипа и скопируйте его текст в собственное хранилище с помощью getText()
, как описано в следующей процедуре:
- Получите глобальный объект
ClipboardManager
с помощьюgetSystemService(CLIPBOARD_SERVICE)
. Также объявите глобальную переменную для хранения вставленного текста:Котлин
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Ява
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- Определите, нужно ли вам включить или отключить опцию «вставить» в текущей активности. Убедитесь, что буфер обмена содержит клип и что вы можете обрабатывать тип данных, представленный клипом:
Котлин
// 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 } }
Ява
// 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, указывающий на простой текст. Следующий фрагмент кода проверяет это, но он показывает только код для обработки простого текста:
Котлин
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 } } } }
Ява
// 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
и вызовите соответствующий метод поставщика контента для извлечения данных.
Следующая процедура описывает, как получить данные от поставщика контента на основе URI контента в буфере обмена. Она проверяет, доступен ли у поставщика тип MIME, который может использовать приложение.
- Объявите глобальную переменную, содержащую тип MIME:
Котлин
// 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"
Ява
// 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";
- Получите глобальный буфер обмена. Также получите распознаватель контента, чтобы иметь доступ к поставщику контента:
Котлин
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Gets a content resolver instance. val cr = contentResolver
Ява
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance. ContentResolver cr = getContentResolver();
- Получите основной клип из буфера обмена и получите его содержимое в виде URI:
Котлин
// 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
Ява
// 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)
. Этот метод возвращает null, еслиUri
не указывает на допустимого поставщика контента.Котлин
// If the clipboard contains a URI reference... pasteUri?.let { // ...is this a content URI? val uriMimeType: String? = cr.getType(it)
Ява
// If the clipboard contains a URI reference... if (pasteUri != null) { // ...is this a content URI? String uriMimeType = cr.getType(pasteUri);
- Проверьте, поддерживает ли поставщик контента тип MIME, который понимает приложение. Если поддерживает, вызовите
ContentResolver.query()
для получения данных. Возвращаемое значение —Cursor
.Котлин
// 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. } } } }
Ява
// 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(); } } } }
Вставить намерение
Чтобы вставить намерение, сначала получите глобальный буфер обмена. Проверьте объект ClipData.Item
, чтобы увидеть, содержит ли он Intent
. Затем вызовите getIntent()
, чтобы скопировать намерение в собственное хранилище. Следующий фрагмент демонстрирует это:
Котлин
// 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. }
Ява
// 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()
.
Используйте поставщиков контента для копирования сложных данных
Поставщики контента поддерживают копирование сложных данных, таких как записи базы данных или потоки файлов. Чтобы скопировать данные, поместите URI контента в буфер обмена. Вставляющие приложения затем получают этот URI из буфера обмена и используют его для извлечения данных базы данных или дескрипторов потоков файлов.
Поскольку у приложения для вставки есть только URI контента для ваших данных, ему нужно знать, какой фрагмент данных извлекать. Вы можете предоставить эту информацию, закодировав идентификатор для данных в самом URI, или вы можете предоставить уникальный URI, который возвращает данные, которые вы хотите скопировать. Какой метод вы выберете, зависит от организации ваших данных.
В следующих разделах описывается, как настроить URI, предоставить сложные данные и предоставить потоки файлов. Описания предполагают, что вы знакомы с общими принципами проектирования поставщиков контента.
Закодировать идентификатор в URI
Полезный метод копирования данных в буфер обмена с помощью URI — кодирование идентификатора для данных в самом URI. Затем ваш поставщик контента может получить идентификатор из URI и использовать его для извлечения данных. Вставляющее приложение не обязательно должно знать, что идентификатор существует. Ему просто нужно получить вашу «ссылку» — URI плюс идентификатор — из буфера обмена, передать ее вашему поставщику контента и получить обратно данные.
Обычно вы кодируете идентификатор в URI контента, присоединяя его к концу URI. Например, предположим, что вы определяете URI своего провайдера как следующую строку:
"content://com.example.contacts"
Если вы хотите закодировать имя в этом URI, используйте следующий фрагмент кода:
Котлин
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)
Ява
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 предназначен для копирования. Например, предположим, что у вас уже есть следующие пути 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, уникальный для вашего провайдера. В ответ на запрос ваш провайдер возвращает данные, которые он в данный момент содержит.
Копировать структуры данных
Настройте поставщика контента для копирования и вставки сложных данных как подкласс компонента ContentProvider
. Закодируйте URI, который вы помещаете в буфер обмена, так, чтобы он указывал на точную запись, которую вы хотите предоставить. Кроме того, рассмотрите текущее состояние вашего приложения:
- Если у вас уже есть поставщик контента, вы можете расширить его функциональность. Вам может потребоваться только изменить его метод
query()
для обработки URI, поступающих из приложений, которые хотят вставить данные. Вероятно, вы захотите изменить метод для обработки шаблона URI "copy". - Если ваше приложение поддерживает внутреннюю базу данных, вы можете перенести эту базу данных в поставщик контента, чтобы упростить копирование из нее.
- Если вы не используете базу данных, вы можете реализовать простого поставщика контента, единственной целью которого является предоставление данных приложениям, вставляющим данные из буфера обмена.
В поставщике контента переопределите как минимум следующие методы:
-
query()
- Приложения вставки предполагают, что они могут получить ваши данные, используя этот метод с URI, который вы поместили в буфер обмена. Для поддержки копирования, этот метод должен обнаруживать URI, содержащие специальный путь «копии». Затем ваше приложение может создать URI «копии» для помещения в буфер обмена, содержащий путь копирования и указатель на точную запись, которую вы хотите скопировать.
-
getType()
- Этот метод должен возвращать типы MIME для данных, которые вы собираетесь копировать. Метод
newUri()
вызываетgetType()
чтобы поместить типы MIME в новый объектClipData
.Типы MIME для сложных данных описаны в разделе Поставщики контента .
Вам не нужны никакие другие методы поставщика контента, такие как insert()
или update()
. Приложению для вставки нужно только получить поддерживаемые вами типы MIME и скопировать данные от вашего поставщика. Если у вас уже есть эти методы, они не будут мешать операциям копирования.
В следующих фрагментах показано, как настроить приложение для копирования сложных данных:
В глобальных константах для вашего приложения объявите базовую строку URI и путь, который идентифицирует строки URI, которые вы используете для копирования данных. Также объявите тип MIME для копируемых данных.
Котлин
// 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"
Ява
// 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";
- В активности пользователи копируют данные из, настройте код для копирования данных в буфер обмена. В ответ на запрос копирования поместите URI в буфер обмена.
Котлин
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) } }
Ява
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);
В глобальной области действия вашего поставщика контента создайте сопоставление URI и добавьте шаблон URI, который соответствует URI, помещенным вами в буфер обмена.
Котлин
// 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() { ... }
Ява
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 в зависимости от того, как вы его закодируете, но отображается только шаблон для операции копирования буфера обмена.Котлин
// 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. } } ... }
Ява
// 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 для скопированных данных:Котлин
// Sets up your provider's getType() method. override fun getType(uri: Uri): String? { ... return when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT ... } }
Ява
// 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 контента из буфера обмена и использовать его для получения и вставки данных.
Копировать потоки данных
Вы можете копировать и вставлять большие объемы текста и двоичных данных как потоки. Данные могут иметь такие формы, как:
- Файлы, хранящиеся на реальном устройстве
- Потоки из сокетов
- Большие объемы данных хранятся в базовой системе базы данных провайдера
Поставщик контента для потоков данных предоставляет доступ к своим данным с помощью объекта дескриптора файла, например AssetFileDescriptor
, вместо объекта Cursor
. Приложение для вставки считывает поток данных, используя этот дескриптор файла.
Чтобы настроить приложение для копирования потока данных с помощью провайдера, выполните следующие действия:
- Настройте URI контента для потока данных, который вы помещаете в буфер обмена. Для этого есть следующие варианты:
- Закодируйте идентификатор для потока данных в URI, как описано в разделе Кодирование идентификатора в URI , а затем ведите у своего провайдера таблицу, содержащую идентификаторы и соответствующее имя потока.
- Кодируйте имя потока непосредственно в URI.
- Используйте уникальный URI, который всегда возвращает текущий поток от провайдера. Если вы используете эту опцию, не забудьте обновить провайдера, чтобы он указывал на другой поток, когда вы копируете поток в буфер обмена с помощью URI.
- Укажите тип MIME для каждого типа потока данных, который вы планируете предложить. Приложениям для вставки нужна эта информация, чтобы определить, могут ли они вставлять данные в буфер обмена.
- Реализуйте один из методов
ContentProvider
, который возвращает дескриптор файла для потока. Если вы кодируете идентификаторы в URI контента, используйте этот метод, чтобы определить, какой поток открывать. - Чтобы скопировать поток данных в буфер обмена, создайте URI контента и поместите его в буфер обмена.
Чтобы вставить поток данных, приложение получает клип из буфера обмена, получает URI и использует его в вызове метода дескриптора файла ContentResolver
, который открывает поток. Метод ContentResolver
вызывает соответствующий метод ContentProvider
, передавая ему URI контента. Ваш поставщик возвращает дескриптор файла методу ContentResolver
. Затем приложение, выполняющее вставку, отвечает за чтение данных из потока.
В следующем списке показаны наиболее важные методы дескриптора файла для поставщика контента. Каждый из них имеет соответствующий метод ContentResolver
со строкой "Descriptor", добавленной к имени метода. Например, аналогом openAssetFile()
в ContentResolver
является openAssetFileDescriptor()
.
-
openTypedAssetFile()
Этот метод возвращает дескриптор файла актива, но только если предоставленный тип MIME поддерживается поставщиком. Вызывающий объект — приложение, выполняющее вставку, — предоставляет шаблон типа MIME. Поставщик контента приложения, копирующего URI в буфер обмена, возвращает дескриптор файла
AssetFileDescriptor
, если он может предоставить этот тип MIME, и выдает исключение, если не может.Этот метод обрабатывает подразделы файлов. Вы можете использовать его для чтения активов, которые поставщик контента скопировал в буфер обмена.
-
openAssetFile()
- Этот метод является более общей формой
openTypedAssetFile()
. Он не фильтрует разрешенные типы MIME, но может читать подразделы файлов. -
openFile()
- Это более общая форма
openAssetFile()
. Она не может читать подразделы файлов.
Вы можете опционально использовать метод openPipeHelper()
с вашим методом дескриптора файла. Это позволяет приложению, выполняющему вставку, читать потоковые данные в фоновом потоке с помощью канала. Чтобы использовать этот метод, реализуйте интерфейс ContentProvider.PipeDataWriter
.
Разработайте эффективную функцию копирования и вставки
Чтобы разработать эффективную функцию копирования и вставки для вашего приложения, помните о следующих моментах:
- В любой момент времени в буфере обмена находится только один клип. Новая операция копирования любым приложением в системе перезаписывает предыдущий клип. Поскольку пользователь может уйти из вашего приложения и скопировать перед возвращением, вы не можете предполагать, что буфер обмена содержит клип, который пользователь ранее скопировал в вашем приложении.
- Предполагаемая цель нескольких объектов
ClipData.Item
на клип — поддержка копирования и вставки нескольких выделений, а не различных форм ссылок на одно выделение. Обычно вы хотите, чтобы все объектыClipData.Item
в клипе имели одинаковую форму. То есть все они должны быть простым текстом, URI контента илиIntent
и не должны быть смешанными. - Когда вы предоставляете данные, вы можете предлагать различные MIME-представления. Добавьте поддерживаемые вами типы MIME в
ClipDescription
, а затем реализуйте типы MIME в вашем поставщике контента. - Когда вы получаете данные из буфера обмена, ваше приложение отвечает за проверку доступных типов MIME и затем решает, какой из них использовать, если таковой имеется. Даже если в буфере обмена есть клип и пользователь запрашивает вставку, ваше приложение не обязано выполнять вставку. Выполните вставку, если тип MIME совместим. Вы можете преобразовать данные в буфере обмена в текст с помощью
coerceToText()
. Если ваше приложение поддерживает более одного из доступных типов MIME, вы можете позволить пользователю выбрать, какой из них использовать.
Android предоставляет мощный фреймворк на основе буфера обмена для копирования и вставки. Он поддерживает простые и сложные типы данных, включая текстовые строки, сложные структуры данных, текстовые и двоичные потоковые данные и ресурсы приложений. Простые текстовые данные хранятся непосредственно в буфере обмена, в то время как сложные данные хранятся как ссылка, которую вставляющее приложение разрешает с поставщиком контента. Копирование и вставка работают как внутри приложения, так и между приложениями, реализующими фреймворк.
Поскольку часть фреймворка использует поставщиков контента, этот документ предполагает некоторое знакомство с API поставщиков контента Android, который описан в разделе Поставщики контента .
Пользователи ожидают обратной связи при копировании контента в буфер обмена, поэтому в дополнение к фреймворку, который обеспечивает копирование и вставку, Android показывает пользователям пользовательский интерфейс по умолчанию при копировании в Android 13 (уровень API 33) и выше. Из-за этой функции существует риск дублирования уведомлений. Вы можете узнать больше об этом пограничном случае в разделе Избегайте дублирования уведомлений .

Вручную предоставляйте обратную связь пользователям при копировании в Android 12L (уровень API 32) и ниже. Смотрите рекомендации по этому вопросу в этом документе.
Структура буфера обмена
При использовании фреймворка буфера обмена поместите данные в объект клипа, а затем поместите объект клипа в системный буфер обмена. Объект клипа может принимать одну из трех форм:
- Текст
- Текстовая строка. Поместите строку непосредственно в объект клипа, который затем поместите в буфер обмена. Чтобы вставить строку, извлеките объект клипа из буфера обмена и скопируйте строку в хранилище вашего приложения.
- URI
- Объект
Uri
, представляющий любую форму URI. Это в первую очередь для копирования сложных данных из поставщика контента. Чтобы скопировать данные, поместите объектUri
в объект клипа и поместите объект клипа в буфер обмена. Чтобы вставить данные, получите объект клипа, получите объектUri
, разрешите его источнику данных, например поставщику контента, и скопируйте данные из источника в хранилище вашего приложения. - Намерение
-
Intent
. Поддерживает копирование ярлыков приложений. Чтобы скопировать данные, создайтеIntent
, поместите его в объект clip и поместите объект clip в буфер обмена. Чтобы вставить данные, получите объект clip и затем скопируйте объектIntent
в область памяти вашего приложения.
Буфер обмена содержит только один объект клипа за раз. Когда приложение помещает объект клипа в буфер обмена, предыдущий объект клипа исчезает.
Если вы хотите, чтобы пользователи могли вставлять данные в ваше приложение, вам не обязательно обрабатывать все типы данных. Вы можете проверить данные в буфере обмена, прежде чем предоставить пользователям возможность вставить их. Помимо определенной формы данных, объект clip также содержит метаданные, сообщающие вам, какие типы MIME доступны. Эти метаданные помогают вам решить, может ли ваше приложение сделать что-то полезное с данными буфера обмена. Например, если у вас есть приложение, которое в основном обрабатывает текст, вы можете игнорировать объекты clip, содержащие URI или намерение.
Вы также можете позволить пользователям вставлять текст независимо от формы данных в буфере обмена. Для этого принудительно преобразуйте данные буфера обмена в текстовое представление, а затем вставьте этот текст. Это описано в разделе Приведение буфера обмена к тексту .
Классы буфера обмена
В этом разделе описываются классы, используемые фреймворком буфера обмена.
Менеджер буфера обмена
Системный буфер обмена Android представлен глобальным классом ClipboardManager
. Не создавайте экземпляр этого класса напрямую. Вместо этого получите ссылку на него, вызвав getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item и ClipDescription
Чтобы добавить данные в буфер обмена, создайте объект ClipData
, содержащий описание данных и сами данные. Буфер обмена содержит один ClipData
за раз. ClipData
содержит объект ClipDescription
и один или несколько объектов ClipData.Item
.
Объект ClipDescription
содержит метаданные о клипе. В частности, он содержит массив доступных типов MIME для данных клипа. Кроме того, на Android 12 (уровень API 31) и выше метаданные включают информацию о том, содержит ли объект стилизованный текст , и о типе текста в объекте . Когда вы помещаете клип в буфер обмена, эта информация становится доступной для вставки приложений, которые могут проверить, могут ли они обработать данные клипа.
Объект ClipData.Item
содержит текст, URI или данные намерения:
- Текст
-
CharSequence
. - URI
-
Uri
. Обычно содержит URI поставщика контента, хотя разрешен любой URI. Приложение, предоставляющее данные, помещает URI в буфер обмена. Приложения, которые хотят вставить данные, получают URI из буфера обмена и используют его для доступа к поставщику контента или другому источнику данных и извлечения данных. - Намерение
-
Intent
. Этот тип данных позволяет копировать ярлык приложения в буфер обмена. Затем пользователи могут вставить ярлык в свои приложения для дальнейшего использования.
Вы можете добавить более одного объекта ClipData.Item
в клип. Это позволяет пользователям копировать и вставлять несколько выделенных элементов как один клип. Например, если у вас есть виджет списка, который позволяет пользователю выбирать более одного элемента за раз, вы можете скопировать все элементы в буфер обмена одновременно. Для этого создайте отдельный ClipData.Item
для каждого элемента списка, а затем добавьте объекты ClipData.Item
в объект ClipData
.
Удобные методы ClipData
Класс ClipData
предоставляет статические удобные методы для создания объекта ClipData
с одним объектом ClipData.Item
и простым объектом ClipDescription
:
-
newPlainText(label, text)
- Возвращает объект
ClipData
, единственный объектClipData.Item
которого содержит текстовую строку. Метка объектаClipDescription
установлена наlabel
. Единственный тип MIME вClipDescription
—MIMETYPE_TEXT_PLAIN
.Используйте
newPlainText()
для создания клипа из текстовой строки. -
newUri(resolver, label, URI)
- Возвращает объект
ClipData
, чей единый объектClipData.Item
содержит URI. Метка объектаClipDescription
установлен наlabel
. Если URI - это контент URI - то есть, еслиUri.getScheme()
возвращаетcontent:
- Метод использует объектContentResolver
предоставленный вresolver
, для извлечения доступных типов MIME от поставщика контента. Затем он хранит их вClipDescription
. Для URI, который не является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 контента, а поставщик может вернуть текстовый поток,
- Намерение
- Если
ClipData.Item
являетсяIntent
, то есть, еслиgetIntent()
не null -coerceToText()
преобразует его в URI намерения и возвращает его. Представление такое же, как и возвращениеIntent.toUri(URI_INTENT_SCHEME)
.
Структура буфера обмена суммирована на рисунке 2. Чтобы скопировать данные, приложение помещает объект ClipData
в глобальный буфер обмена ClipboardManager
. ClipData
содержит один или несколько объектов ClipData.Item
и один объект ClipDescription
. Чтобы вставить данные, приложение получает ClipData
, получает свой тип MIME от ClipDescription
и получает данные из ClipData.Item
или от поставщика контента, на который ссылается ClipData.Item
.

Скопировать в буфер обмена
Чтобы скопировать данные в буфер обмена, получите ручку в Global ClipboardManager
, создайте объект ClipData
и добавьте к нему один или ClipData.Item
ClipDescription
Затем добавьте готовый объект ClipData
в объект ClipboardManager
. Это описано далее в следующей процедуре:
- Если вы копируете данные, используя URI контента, настройте поставщика контента.
- Получите системный буфер обмена:
Котлин
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 } }
Ява
... // 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
:- Для текста
Котлин
// Creates a new text clip to put on the clipboard. val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
Ява
// Creates a new text clip to put on the clipboard. ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
- Для URI
Этот фрагмент строит URI, кодируя идентификатор записи на URI контента для поставщика. Этот метод более подробно рассказывается о кодировании идентификатора в разделе URI .
Котлин
// 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)
Ява
// 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
для приложения, а затем помещает его в объект клипа:Котлин
// 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)
Ява
// 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);
- Для текста
- Поместите новый объект клипа в буфер обмена:
Котлин
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Ява
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
Дайте обратную связь при копировании в буфер обмена
Пользователи ожидают визуальной обратной связи, когда приложение копирует контент в буфер обмена. Это делается автоматически для пользователей в Android 13 и выше, но оно должно быть вручную реализовано в предыдущих версиях.
Начиная с Android 13, система отображает стандартное визуальное подтверждение, когда контент добавляется в буфер обмена. Новое подтверждение делает следующее:
- Подтверждает, что содержание было успешно скопировано.
- Предоставляет предварительный просмотр скопированного контента.

В Android 12L (API -уровне 32) и ниже пользователи могут быть не уверены, успешно ли они скопировали контент или что они скопировали. Эта функция стандартизирует различные уведомления, показанные приложениями после копирования, и предлагает пользователям больше контроля над буфером обмена.
Избегайте дублирующих уведомлений
В Android 12L (API-уровне 32) и ниже мы рекомендуем предупреждать пользователей, когда они успешно копируют, выпустив визуальную обратную связь в приложении, используя виджет, такой как Toast
или Snackbar
, после копирования.
Чтобы избежать дублирующих отображений информации, мы настоятельно рекомендуем удалять тосты или закуски, показанные после копии в приложении для 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() }
Добавить чувствительный контент в буфер обмена
Если ваше приложение позволяет пользователям копировать конфиденциальное контент в буфер обмена, например, пароли или информацию о кредитной карте, вы должны добавить флаг в ClipDescription
в ClipData
перед вызовом ClipboardManager.setPrimaryClip()
. Добавление этого флага предотвращает появление чувствительного контента в визуальном подтверждении копированного контента в Android 13 и выше.


Чтобы пометить чувствительный контент, добавьте логическое дополнительное в ClipDescription
. Все приложения должны делать это, независимо от целевого уровня 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) } }
Вставить из буфера обмена
Как описано ранее, вставьте данные из буфера обмена, получая объект глобального буфера обмена, получая объект Clip, просмотр его данных и, если возможно, копирование данных из объекта Clip в собственное хранилище. В этом разделе подробно объясняется, как вставить три формы данных буфера обмена.
Вставьте простой текст
Чтобы вставить простой текст, получите глобальный буфер обмена и убедитесь, что он может вернуть простой текст. Затем получите объект CLIP и скопируйте его текст в собственное хранилище, используя getText()
, как описано в следующей процедуре:
- Получите Global
ClipboardManager
Object, используяgetSystemService(CLIPBOARD_SERVICE)
. Кроме того, объявьте глобальную переменную для содержания вставленного текста:Котлин
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Ява
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- Определите, нужно ли вам включить или отключить опцию «вставка» в текущей деятельности. Убедитесь, что буфер обмена содержит клип и что вы можете обрабатывать тип данных, представленных клипсом:
Котлин
// 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 } }
Ява
// 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, который указывает на простой текст. Следующий фрагмент кода проверяет это, но он показывает только код для обработки простого текста:
Котлин
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 } } } }
Ява
// 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
и вызовите соответствующий метод поставщика контента для извлечения данных.
В следующей процедуре описывается, как получить данные от поставщика контента на основе URI контента в буфере обмена. Он проверяет, доступен ли тип MIME, который может использовать приложение, у поставщика.
- Объявить глобальную переменную для содержания типа MIME:
Котлин
// 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"
Ява
// 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";
- Получите глобальный буфер обмена. Также получите разрешающий контент, чтобы вы могли получить доступ к поставщику контента:
Котлин
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Gets a content resolver instance. val cr = contentResolver
Ява
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance. ContentResolver cr = getContentResolver();
- Получите основной клип от буфера обмена и получите его содержимое в качестве URI:
Котлин
// 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
Ява
// 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)
. Этот метод возвращает NULL, еслиUri
не указывает на действительный поставщик контента.Котлин
// If the clipboard contains a URI reference... pasteUri?.let { // ...is this a content URI? val uriMimeType: String? = cr.getType(it)
Ява
// If the clipboard contains a URI reference... if (pasteUri != null) { // ...is this a content URI? String uriMimeType = cr.getType(pasteUri);
- Проверьте, поддерживает ли поставщик контента тип MIME, который понимает приложение. Если это так, вызовите
ContentResolver.query()
, чтобы получить данные. Возвратное значение -Cursor
.Котлин
// 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. } } } }
Ява
// 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(); } } } }
Вставьте намерение
Чтобы вставить намерение, сначала получите глобальный буфер обмена. Изучите объект ClipData.Item
, чтобы увидеть, содержит ли он Intent
. Затем позвоните getIntent()
, чтобы скопировать намерение на собственное хранилище. Следующий фрагмент демонстрирует это:
Котлин
// 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. }
Ява
// 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()
.
Используйте поставщиков контента для копирования сложных данных
Поставщики контента поддерживают копирование сложных данных, таких как записи базы данных или потоки файлов. Чтобы скопировать данные, поместите URI контента в буфер обмена. Затем вставьте приложения, затем получите этот URI из буфера обмена и используйте его для извлечения данных базы данных или дескрипторов потока файлов.
Поскольку приложение для вставки имеет только контент URI для ваших данных, оно должно знать, какой кусок данных для извлечения. Вы можете предоставить эту информацию, кодируя идентификатор для данных на самом URI, или вы можете предоставить уникальный URI, который возвращает данные, которые вы хотите скопировать. Какой метод вы выберете, зависит от организации ваших данных.
В следующих разделах описывается, как настроить URI, предоставить сложные данные и предоставить потоки файлов. Описания предполагают, что вы знакомы с общими принципами дизайна поставщика контента.
Кодировать идентификатор на URI
Полезный метод копирования данных в буфер обмена с помощью URI - это кодировать идентификатор для данных на самом URI. Ваш поставщик контента может затем получить идентификатор из URI и использовать его для извлечения данных. Приложение вставки не должно знать, что идентификатор существует. Он просто должен получить вашу «ссылку» - URI плюс идентификатор - от буфера обмена, дайте ему поставщика контента и вернуть данные.
Обычно вы кодируете идентификатор в URI контента, объединив его до конца URI. Например, предположим, что вы определяете свой поставщик URI как следующую строку:
"content://com.example.contacts"
Если вы хотите кодировать имя в этот URI, используйте следующий фрагмент кода:
Котлин
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)
Ява
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 предназначен для копирования. Например, предположим, что у вас уже есть следующие пути 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, который является уникальным для вашего поставщика. В ответ на запрос ваш провайдер возвращает данные, которые он в настоящее время содержит.
Копировать структуры данных
Установите поставщик контента для копирования и вставки сложных данных в качестве подкласса компонента ContentProvider
. Кодируйте URI, который вы помещаете в буфер обмена, чтобы он указывал на точную запись, которую вы хотите предоставить. Кроме того, рассмотрим существующее состояние вашего заявления:
- Если у вас уже есть поставщик контента, вы можете добавить его функциональность. Возможно, вам потребуется только изменять метод
query()
для обработки URI, поступающих из приложений, которые хотят вставить данные. Вы, вероятно, хотите изменить метод для обработки «копии» шаблона URI. - Если ваше приложение поддерживает внутреннюю базу данных, вы можете переместить эту базу данных в поставщика контента, чтобы облегчить копирование из нее.
- Если вы не используете базу данных, вы можете реализовать простого поставщика контента, единственной целью которой является предложение данных для приложений, которые встают из буфера обмена.
В поставщике контента переопределите, по крайней мере, следующие методы:
-
query()
- Вставки приложений предполагают, что они могут получить ваши данные, используя этот метод с помощью URI, который вы помещаете в буфер обмена. Чтобы поддержать копирование, попросите этот метод обнаружить URI, содержащую специальный путь «копии». Затем ваше приложение может создать URI «Копировать» для размещения буфера обмена, содержащий путь копии и указатель на точную запись, которую вы хотите скопировать.
-
getType()
- Этот метод должен вернуть типы MIME для данных, которые вы собираетесь скопировать. Метод
newUri()
вызываетgetType()
чтобы поместить типы MIME в новый объектClipData
.Типы MIME для сложных данных описаны у поставщиков контента .
Вам не нужно иметь какие -либо другие методы поставщика контента, такие как insert()
или update()
. Приложение для вставки должно получить только ваши поддерживаемые типы MIME и копировать данные от вашего поставщика. Если у вас уже есть эти методы, они не будут мешать операциям копирования.
Следующие фрагменты демонстрируют, как настроить приложение для копирования сложных данных:
В глобальных константах для вашего приложения объявьте базовую строку URI и путь, который идентифицирует строки URI, которые вы используете для копирования данных. Также объявите тип MIME для скопированных данных.
Котлин
// 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"
Ява
// 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";
- В деятельности пользователи копируют данные, настройте код для копирования данных в буфер обмена. В ответ на запрос на копию поместите URI в буфер обмена.
Котлин
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) } }
Ява
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);
В глобальном масштабе вашего поставщика контента создайте сопоставление URI и добавьте шаблон URI, который соответствует URI, который вы помещаете в буфер обмена.
Котлин
// 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() { ... }
Ява
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, в зависимости от того, как вы его кодируете, но показывает только шаблон для операции копирования буфера.Котлин
// 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. } } ... }
Ява
// 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 для копирования данных:Котлин
// Sets up your provider's getType() method. override fun getType(uri: Uri): String? { ... return when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT ... } }
Ява
// 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 из буфера обмена и использовать его для получения и вставки данных.
Копировать потоки данных
Вы можете скопировать и вставить большие объемы текстовых и двоичных данных в качестве потоков. Данные могут иметь такие формы, как следующие:
- Файлы, хранящиеся на фактическом устройстве
- Потоки из розетков
- Большие объемы данных, хранящихся в системе базы данных, провайдера
Поставщик контента для потоков данных предоставляет доступ к своим данным с помощью объекта дескриптора файла, такого как AssetFileDescriptor
, вместо объекта Cursor
. Приложение вставки считывает поток данных, используя этот дескриптор файла.
Чтобы настроить приложение для копирования потока данных с поставщиком, выполните следующие действия:
- Установите URI контента для потока данных, который вы помещаете в буфер обмена. Варианты для этого включают следующее:
- Кодируйте идентификатор для потока данных на URI, как описано в кодировании идентификатора в разделе URI , а затем поддерживайте таблицу в вашем поставщике, который содержит идентификаторы и соответствующее имя потока.
- Кодируйте имя потока непосредственно на URI.
- Используйте уникальный URI, который всегда возвращает текущий поток от поставщика. Если вы используете эту опцию, не забудьте обновить своего провайдера, чтобы указать на другой поток, когда вы копируете поток в буфер обмена с помощью URI.
- Предоставьте тип MIME для каждого типа потока данных, который вы планируете предложить. Вставка приложений нуждается в этой информации, чтобы определить, могут ли они вставить данные в буфер обмена.
- Реализуйте один из методов
ContentProvider
, который возвращает дескриптор файла для потока. Если вы кодируете идентификаторы в URI содержимого, используйте этот метод, чтобы определить, какой поток открыть. - Чтобы скопировать поток данных в буфер обмена, постройте URI содержимого и поместите его в буфер обмена.
Чтобы вставить поток данных, приложение получает зажим из буфера обмена, получает URI и использует его в вызове метода дескриптора файла ContentResolver
, который открывает поток. Метод ContentResolver
вызывает соответствующий метод ContentProvider
, передавая его URI Content. Ваш провайдер возвращает дескриптор файла в метод ContentResolver
. Приложение для вставки несет ответственность за чтение данных из потока.
В следующем списке показаны наиболее важные методы дескриптора файла для поставщика контента. Каждый из них имеет соответствующий метод ContentResolver
с строкой «дескриптор», добавленный к имени метода. Например, ContentResolver
-аналог openAssetFile()
openAssetFileDescriptor()
.
-
openTypedAssetFile()
Этот метод возвращает дескриптор файла актива, но только если поставщик поддерживается тип приготовления MIME. Вызывающий абонент - приложение, выполняющее вставку - обеспечивает шаблон типа MIME. Поставщик контента приложения, который копирует URI в буфер обмена, возвращает ручку файла
AssetFileDescriptor
, если он может предоставить этот тип MIME и бросает исключение, если он не может.Этот метод обрабатывает подразделы файлов. Вы можете использовать его для чтения активов, которые поставщик контента скопировал в буфер обмена.
-
openAssetFile()
- Этот метод является более общей формой
openTypedAssetFile()
. Он не фильтрует для разрешенных типов MIME, но может читать подразделы файлов. -
openFile()
- Это более общая форма
openAssetFile()
. Он не может читать подразделы файлов.
При желании вы можете использовать метод openPipeHelper()
с помощью метода дескриптора файла. Это позволяет вставленному приложению считывать данные потока в фоновом потоке, используя трубу. Чтобы использовать этот метод, реализуйте интерфейс ContentProvider.PipeDataWriter
.
Проектирование эффективной функциональности копирования и вставки
Чтобы разработать эффективную функциональность копирования и вставки для вашего приложения, запомните эти точки:
- В любое время в буфер обмена есть только один клип. Новая операция копирования любым приложением в системе перезаписывает предыдущий клип. Поскольку пользователь может отойти от вашего приложения и скопировать перед возвратом, вы не можете предположить, что буфер обмена содержит клип, который пользователь ранее скопировал в вашем приложении.
- Предполагаемой целью нескольких объектов
ClipData.Item
на клип является поддержка копирования и вставки нескольких выборов, а не различных форм ссылки на один выбор. Обычно вы хотите, чтобы все объектыClipData.Item
в клипе имели одну и ту же форму. То есть все они должны быть простым текстом, контентом URI илиIntent
, а не смешанными. - Когда вы предоставляете данные, вы можете предложить разные представления MIME. Добавьте типы MIME, которые вы поддерживаете в
ClipDescription
, а затем реализуйте типы MIME в вашем поставщике контента. - Когда вы получаете данные из буфера обмена, ваше приложение отвечает за проверку доступных типов MIME, а затем для решения, какой, если таковой имеется, использовать. Даже если в буфере обмена есть клип, и пользователь запрашивает вставку, ваше приложение не требуется для вставки. Сделайте вставку, если тип MIME совместим. Вы можете принудить данные в буфер обмена, чтобы текст, используя
coerceToText()
. Если ваше приложение поддерживает более одного из доступных типов MIME, вы можете позволить пользователю выбрать, какой из них использовать.