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

Вручную предоставляйте пользователям обратную связь при копировании в Android 12L (уровень API 32) и ниже. См. рекомендации по этому вопросу в этом документе.
Структура буфера обмена
При использовании фреймворка буфера обмена данные помещаются в объект клипа, а затем этот объект клипа помещается в системный буфер обмена. Объект клипа может принимать одну из трёх форм:
- Текст
- Текстовая строка. Поместите строку непосредственно в объект Clip, который затем поместите в буфер обмена. Чтобы вставить строку, извлеките объект Clip из буфера обмена и скопируйте её в хранилище приложения.
- URI
- Объект
Uri
, представляющий любую форму URI. Он предназначен в первую очередь для копирования сложных данных из поставщика контента. Чтобы скопировать данные, поместите объектUri
в объект Clip и поместите его в буфер обмена. Чтобы вставить данные, получите объект Clip, получите объектUri
, свяжите его с источником данных, например, с поставщиком контента, и скопируйте данные из источника в хранилище приложения. - Намерение
- Объект
Intent
. Поддерживает копирование ярлыков приложений. Чтобы скопировать данные, создайте объектIntent
, поместите его в объект Clip и поместите объект Clip в буфер обмена. Чтобы вставить данные, получите объект Clip и скопируйте объектIntent
в область памяти приложения.
Буфер обмена может содержать только один объект-клип. Когда приложение помещает объект-клип в буфер обмена, предыдущий объект-клип исчезает.
Если вы хотите, чтобы пользователи могли вставлять данные в ваше приложение, вам не обязательно обрабатывать все типы данных. Вы можете проверить данные в буфере обмена, прежде чем предоставить пользователям возможность вставить их. Помимо определённой формы данных, объект clip также содержит метаданные, сообщающие о доступных типах MIME. Эти метаданные помогают определить, может ли ваше приложение использовать данные из буфера обмена. Например, если ваше приложение в основном обрабатывает текст, вы можете игнорировать объекты clip, содержащие URI или Intent.
Вы также можете разрешить пользователям вставлять текст независимо от формата данных в буфере обмена. Для этого принудительно преобразуйте данные из буфера обмена в текстовое представление, а затем вставьте этот текст. Это описано в разделе « Приведение данных из буфера обмена к текстовому виду» .
Классы буфера обмена
В этом разделе описываются классы, используемые фреймворком буфера обмена.
Менеджер буфера обмена
Системный буфер обмена 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 «копировать». - Если ваше приложение поддерживает внутреннюю базу данных, вы можете захотеть перенести эту базу данных в поставщик контента, чтобы облегчить копирование из нее.
- Если вы не используете базу данных, вы можете реализовать простого поставщика контента, единственной целью которого будет предоставление данных приложениям, вставляющим данные из буфера обмена.
В поставщике контента переопределите как минимум следующие методы:
-
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 совместим. Вы можете преобразовать данные из буфера обмена в текст с помощью
coerceToText()
. Если ваше приложение поддерживает несколько доступных типов MIME, вы можете предоставить пользователю возможность выбора.