Android to zaawansowana platforma oparta na schowku do kopiowania i wklejania. Obsługuje proste i złożone typy danych, w tym ciągi tekstowe, złożone struktury danych, dane ze strumieni tekstowych i binarnych oraz zasoby aplikacji. Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone jako odniesienia, które aplikacja wklejająca korzysta z usług dostawcy treści. Kopiowanie i wklejanie działa zarówno w obrębie aplikacji, jak i między aplikacjami, które implementują platformę.
Ponieważ część platformy korzysta z dostawców treści, w tym dokumencie zakładamy, że znamy interfejs Android Content Provider API, który został opisany w sekcji Dostawcy treści.
Użytkownicy oczekują opinii, gdy kopiują treści do schowka. Dlatego oprócz platformy umożliwiającej kopiowanie i wklejanie Android wyświetla użytkownikom domyślny interfejs podczas kopiowania w Androidzie 13 (poziom API 33) i nowszych. Ze względu na tę funkcję istnieje ryzyko, że powiadomienie zostanie zduplikowane. Więcej informacji o tym przypadku brzegowym znajdziesz w sekcji Unikanie zduplikowanych powiadomień.
Podczas kopiowania na Androidzie 12L (poziom API 32) i starszych wersjach ręcznie przekazuj opinie użytkownikom. Zapoznaj się z zaleceniami na ten temat w tym dokumencie.
Platforma schowka
Jeśli używasz struktury schowka, umieść dane w obiekcie klipsa, a potem umieść go w schowku dostępnym dla całego systemu. Obiekt klipu może mieć jeden z 3 postaci:
- Tekst
- Ciąg tekstowy. Umieść ciąg bezpośrednio w obiekcie przycinania, który następnie umieszczasz w schowku. Aby wkleić ciąg, pobierz obiekt klipu ze schowka i skopiuj go do pamięci aplikacji.
- URI
- Obiekt
Uri
reprezentujący dowolną formę identyfikatora URI. Służy on głównie do kopiowania złożonych danych od dostawcy treści. Aby skopiować dane, umieść obiektUri
w obiekcie przycinania i umieść go w schowku. Aby wkleić dane, pobierz obiekt clip, pobierz obiektUri
, przenieś go do źródła danych, takiego jak dostawca treści, i skopiuj dane ze źródła do pamięci aplikacji. - Podobne zamiary
Intent
. Umożliwia to kopiowanie skrótów aplikacji. Aby skopiować dane, utwórz obiektIntent
, umieść go w obiekcie przycinania i umieść go w schowku. Aby wkleić dane, pobierz obiekt clip, a następnie skopiuj obiektIntent
do obszaru pamięci aplikacji.
W schowku jest tylko jeden obiekt klipu. Gdy aplikacja umieści obiekt klipu w schowku, poprzedni obiekt klipu zniknie.
Jeśli chcesz umożliwić użytkownikom wklejanie danych do aplikacji, nie musisz obsługiwać wszystkich typów danych. Zanim umożliwisz użytkownikom wklejenie danych, możesz przejrzeć te dane w schowku. Obiekt clip nie tylko zawiera określony formularz danych, ale zawiera również metadane informujące, jakie typy MIME są dostępne. Te metadane pomagają określić, czy aplikacja może wykorzystać dane ze schowka. Jeśli na przykład masz aplikację, która obsługuje głównie tekst, możesz zignorować obiekty przycinania, które zawierają identyfikator URI lub intencję.
Możesz też zezwolić użytkownikom na wklejanie tekstu niezależnie od rodzaju danych w schowku. Aby to zrobić, wymuś dane ze schowka w formie tekstowej, a następnie wklej ten tekst. Dokładniejsze informacje znajdziesz w sekcji Przekształcanie schowka w tekst.
Klasy schowka
W tej sekcji opisujemy klasy używane przez platformę schowka.
Menedżer schowka
Schowek systemowy Androida jest reprezentowany przez globalną klasę ClipboardManager
.
Nie twórz bezpośrednio instancji tej klasy. Zamiast tego możesz uzyskać do niego odwołanie, wywołując getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item i ClipDescription
Aby dodać dane do schowka, utwórz obiekt ClipData
zawierający opis danych i samych danych. W schowku jest tylko 1 ClipData
naraz. ClipData
zawiera obiekt ClipDescription
i co najmniej 1 obiekt ClipData.Item
.
Obiekt ClipDescription
zawiera metadane dotyczące klipu. Zawiera on w szczególności tablicę dostępnych typów MIME danych klipu. Dodatkowo w Androidzie 12 (poziom interfejsu API 31) i nowszych metadane zawierają informacje o tym, czy obiekt zawiera stylizowany tekst, oraz o typie tekstu w obiekcie.
Gdy umieścisz klip w schowku, informacje te będą dostępne dla aplikacji do wklejania, które mogą sprawdzać, czy obsługują dane klipu.
Obiekt ClipData.Item
zawiera tekst, identyfikator URI lub dane intencji:
- Tekst
-
CharSequence
. - URI
-
Uri
. Zwykle zawiera on identyfikator URI dostawcy treści, ale dozwolony jest dowolny identyfikator URI. Aplikacja dostarczająca dane umieszcza identyfikator URI w schowku. Aplikacje, które chcą wkleić dane, pozyskują identyfikator URI ze schowka i mogą za jego pomocą uzyskać dostęp do dostawcy treści lub innego źródła danych oraz pobrać dane. - Podobne zamiary
Intent
. Ten typ danych umożliwia skopiowanie skrótu do aplikacji do schowka. Użytkownicy mogą wkleić skrót do aplikacji, aby użyć go później.
Do klipu możesz dodać więcej niż 1 obiekt ClipData.Item
. Dzięki temu użytkownicy mogą kopiować i wklejać wiele elementów jako pojedynczy klip. Jeśli na przykład masz widżet listy, który umożliwia użytkownikowi wybranie więcej niż jednego elementu naraz, możesz skopiować do schowka wszystkie elementy naraz. Aby to zrobić, utwórz oddzielny ClipData.Item
dla każdego elementu listy, a potem dodaj obiekty ClipData.Item
do obiektu ClipData
.
Przydatne metody ClipData
Klasa ClipData
udostępnia statyczne metody ułatwiające tworzenie obiektu ClipData
z pojedynczym obiektem ClipData.Item
i prostym obiektem ClipDescription
:
-
newPlainText(label, text)
- Zwraca obiekt
ClipData
, którego pojedynczy obiektClipData.Item
zawiera ciąg tekstowy. Etykieta obiektuClipDescription
jest ustawiona nalabel
. Pojedynczy typ MIME wClipDescription
toMIMETYPE_TEXT_PLAIN
.Użyj funkcji
newPlainText()
, aby utworzyć klip z ciągu tekstowego. -
newUri(resolver, label, URI)
- Zwraca obiekt
ClipData
, którego pojedynczy obiektClipData.Item
zawiera identyfikator URI. Etykieta obiektuClipDescription
jest ustawiona nalabel
. Jeśli identyfikator URI jest identyfikatorem URI treści – czyliUri.getScheme()
zwraca wartośćcontent:
– metoda używa obiektuContentResolver
podanego wresolver
do pobrania dostępnych typów MIME od dostawcy treści. Następnie zapisuje je w:ClipDescription
. W przypadku identyfikatora URI, który nie jest identyfikatorem URIcontent:
, metoda ustawia typ MIME naMIMETYPE_TEXT_URILIST
.Użyj
newUri()
, aby utworzyć klip na podstawie identyfikatora URI, a w szczególności identyfikatora URIcontent:
. -
newIntent(label, intent)
- Zwraca obiekt
ClipData
, którego pojedynczy obiektClipData.Item
zawieraIntent
. Etykieta obiektuClipDescription
jest ustawiona nalabel
. Typ MIME jest ustawiony naMIMETYPE_TEXT_INTENT
.Użyj polecenia
newIntent()
, aby utworzyć klip z obiektuIntent
.
Wymuszanie danych ze schowka na tekst
Nawet jeśli Twoja aplikacja obsługuje tylko tekst, możesz skopiować ze schowka dane inne niż tekstowe, konwertując je za pomocą metody ClipData.Item.coerceToText()
.
Ta metoda przekształca dane z tabeli ClipData.Item
w tekst i zwraca CharSequence
. Wartość zwracana przez ClipData.Item.coerceToText()
zależy od postaci danych w funkcji ClipData.Item
:
- Tekst
- Jeśli
ClipData.Item
to tekst, czyli jeśligetText()
nie ma wartości null, coerceToText() zwraca tekst. - URI
- Jeśli
ClipData.Item
to identyfikator URI, czyligetUri()
nie ma wartości null,coerceToText()
spróbuje użyć go jako identyfikatora URI treści.- Jeśli identyfikator URI jest identyfikatorem URI treści, a dostawca może zwrócić strumień tekstu,
coerceToText()
zwraca strumień tekstu. - Jeśli identyfikator URI jest identyfikatorem URI treści, ale dostawca nie udostępnia strumienia tekstu,
coerceToText()
zwraca reprezentację identyfikatora URI. Reprezentacja jest taka sama jak zwracana przez funkcjęUri.toString()
. - Jeśli identyfikator URI nie jest identyfikatorem URI treści,
coerceToText()
zwraca reprezentację tego identyfikatora. Reprezentacja jest taka sama jak zwracana przez funkcjęUri.toString()
.
- Jeśli identyfikator URI jest identyfikatorem URI treści, a dostawca może zwrócić strumień tekstu,
- Podobne zamiary
- Jeśli
ClipData.Item
to obiektIntent
, czyli jeśligetIntent()
nie ma wartości null,coerceToText()
przekształca go w identyfikator URI intencji i zwraca go. Reprezentacja jest taka sama jak zwracana przez funkcjęIntent.toUri(URI_INTENT_SCHEME)
.
Podsumowanie struktury schowka podsumowano na rys. 2. Aby skopiować dane, aplikacja umieszcza obiekt ClipData
w globalnym schowku ClipboardManager
. ClipData
zawiera co najmniej 1 obiekt ClipData.Item
i 1 obiekt ClipDescription
. Aby wkleić dane, aplikacja pobiera ClipData
, pobiera swój typ MIME z serwera ClipDescription
i pobiera dane z ClipData.Item
lub od dostawcy treści wskazanego przez ClipData.Item
.
Skopiuj do schowka
Aby skopiować dane do schowka, uzyskaj uchwyt do globalnego obiektu ClipboardManager
, utwórz obiekt ClipData
i dodaj do niego ClipDescription
oraz co najmniej 1 obiekt ClipData.Item
. Następnie dodaj gotowy obiekt ClipData
do obiektu ClipboardManager
. Zostało to szczegółowo opisane w tej procedurze:
- Jeśli kopiujesz dane, korzystając z identyfikatora URI treści, skonfiguruj dostawcę treści.
- Pobierz schowek systemowy:
Kotlin
when(menuItem.itemId) { ... R.id.menu_copy -> { // if the user selects copy // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager } }
Java
... // If the user selects copy. case R.id.menu_copy: // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
-
Skopiuj dane do nowego obiektu
ClipData
:-
Tekst
Kotlin
// Creates a new text clip to put on the clipboard. val clip: ClipData = ClipData.newPlainText("simple text", "Hello, World!")
Java
// Creates a new text clip to put on the clipboard. ClipData clip = ClipData.newPlainText("simple text", "Hello, World!");
-
Identyfikator URI
Ten fragment kodu tworzy identyfikator URI przez kodowanie identyfikatora rekordu w identyfikatorze URI treści dla dostawcy. Tę metodę znajdziesz bardziej szczegółowo w sekcji o kodowaniu identyfikatora URI.
Kotlin
// Creates a Uri using a base Uri and a record ID based on the contact's last // name. Declares the base URI string. const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs, used to copy data. const val COPY_PATH = "/copy" // Declares the Uri to paste to the clipboard. val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName") ... // Creates a new URI clip object. The system uses the anonymous // getContentResolver() object to get MIME types from provider. The clip object's // label is "URI", and its data is the Uri previously created. val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
Java
// Creates a Uri using a base Uri and a record ID based on the contact's last // name. Declares the base URI string. private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs, used to copy data. private static final String COPY_PATH = "/copy"; // Declares the Uri to paste to the clipboard. Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName); ... // Creates a new URI clip object. The system uses the anonymous // getContentResolver() object to get MIME types from provider. The clip object's // label is "URI", and its data is the Uri previously created. ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
-
Dla intencji
Ten fragment kodu tworzy obiekt
Intent
na potrzeby aplikacji, a potem umieszcza go w obiekcie klipu:Kotlin
// Creates the Intent. val appIntent = Intent(this, com.example.demo.myapplication::class.java) ... // Creates a clip object with the Intent in it. Its label is "Intent" // and its data is the Intent object created previously. val clip: ClipData = ClipData.newIntent("Intent", appIntent)
Java
// Creates the Intent. Intent appIntent = new Intent(this, com.example.demo.myapplication.class); ... // Creates a clip object with the Intent in it. Its label is "Intent" // and its data is the Intent object created previously. ClipData clip = ClipData.newIntent("Intent", appIntent);
-
Tekst
-
Umieść nowy obiekt klipu w schowku:
Kotlin
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Java
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
Prześlij opinię podczas kopiowania do schowka
Użytkownicy oczekują wizualnego potwierdzenia, gdy aplikacja skopiuje treść do schowka. Na urządzeniach z Androidem 13 i nowszymi wersjami jest ona stosowana automatycznie, ale w poprzednich wersjach trzeba ją zaimplementować ręcznie.
Od Androida 13 po dodaniu treści do schowka system wyświetla standardowe potwierdzenie wizualne. Nowe potwierdzenie ma następujące funkcje:
- Potwierdza, że treść została skopiowana.
- Wyświetla podgląd skopiowanej treści.
W Androidzie 12L (poziom interfejsu API 32) i starszych użytkownicy mogą nie być pewni, czy udało się skopiować treść czy to, co zostało skopiowane. Ta funkcja ustandaryzuje różne powiadomienia wyświetlane po skopiowaniu przez aplikacje i zapewnia użytkownikom większą kontrolę nad schowkiem.
Unikanie powielania powiadomień
Na Androidzie 12L (poziom interfejsu API 32) i starszych zalecamy powiadamianie użytkowników o operacji kopiowania. Aby to zrobić, wyślij wizualną opinię w aplikacji z użyciem widżetu takiego jak Toast
lub Snackbar
po skopiowaniu.
Aby uniknąć powielania informacji, na Androidzie 13 i nowszych zalecamy usunięcie paska powiadomień i komunikatów w aplikacji.
Oto przykład:
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() }
Dodaj do schowka treści poufne
Jeśli Twoja aplikacja umożliwia użytkownikom kopiowanie do schowka poufnych treści, takich jak hasła czy dane kart kredytowych, musisz dodać flagę do ClipDescription
w dyrektywie ClipData
przed wywołaniem metody ClipboardManager.setPrimaryClip()
. Dodanie tego oznaczenia zapobiega wyświetlaniu treści poufnych w wizualnym potwierdzeniu skopiowania treści na Androidzie 13 i nowszych.
Aby oznaczyć treści poufne, dodaj do ClipDescription
dodatkową wartość logiczną. Muszą to robić wszystkie aplikacje, niezależnie od docelowego poziomu interfejsu 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) } }
Wklej ze schowka
Jak pisaliśmy wcześniej, dane ze schowka wklej w postaci globalnego obiektu schowka, pobierz obiekt clip, zapoznaj się z jego danymi i – jeśli to możliwe – skopiuj je z obiektu do przechowywania. W tej sekcji wyjaśniamy szczegółowo, jak wkleić 3 rodzaje danych ze schowka.
Wklej zwykły tekst
Aby wkleić zwykły tekst, użyj schowka globalnego i sprawdź, czy może zwracać zwykły tekst. Następnie pobierz obiekt clip i skopiuj jego tekst do własnej pamięci za pomocą getText()
zgodnie z tą procedurą:
- Pobierz globalny obiekt
ClipboardManager
za pomocągetSystemService(CLIPBOARD_SERVICE)
. Dodatkowo zadeklaruj zmienną globalną, która będzie zawierać wklejony tekst:Kotlin
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Java
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- Określ, czy musisz włączyć, czy wyłączyć opcję „wklej” w bieżącym działaniu. Sprawdź, czy schowek zawiera klip i czy możesz obsłużyć typ danych reprezentowanych przez ten klip:
Kotlin
// Gets the ID of the "paste" menu item. val pasteItem: MenuItem = menu.findItem(R.id.menu_paste) // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide whether you can handle the data. pasteItem.isEnabled = when { !clipboard.hasPrimaryClip() -> { false } !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> { // Disables the paste menu item, since the clipboard has data but it // isn't plain text. false } else -> { // Enables the paste menu item, since the clipboard contains plain text. true } }
Java
// Gets the ID of the "paste" menu item. MenuItem pasteItem = menu.findItem(R.id.menu_paste); // If the clipboard doesn't contain data, disable the paste menu item. // If it does contain data, decide whether you can handle the data. if (!(clipboard.hasPrimaryClip())) { pasteItem.setEnabled(false); } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) { // Disables the paste menu item, since the clipboard has data but // it isn't plain text. pasteItem.setEnabled(false); } else { // Enables the paste menu item, since the clipboard contains plain text. pasteItem.setEnabled(true); }
- Skopiuj dane ze schowka. Ten punkt kodu jest dostępny tylko wtedy, gdy w menu jest włączona opcja „Wklej”, więc możesz założyć, że schowek zawiera zwykły tekst. Nie wiesz jeszcze, czy zawiera ona ciąg tekstowy czy identyfikator URI wskazujący zwykły tekst.
Poniższy fragment kodu pozwala przetestować tę funkcję, ale pokazuje tylko kod do obsługi zwykłego tekstu:
Kotlin
when (menuItem.itemId) { ... R.id.menu_paste -> { // Responds to the user selecting "paste". // Examines the item on the clipboard. If getText() doesn't return null, // the clip item contains the text. Assumes that this application can only // handle one item at a time. val item = clipboard.primaryClip.getItemAt(0) // Gets the clipboard as text. pasteData = item.text return if (pasteData != null) { // If the string contains data, then the paste operation is done. true } else { // The clipboard doesn't contain text. If it contains a URI, // attempts to get data from it. val pasteUri: Uri? = item.uri if (pasteUri != null) { // If the URI contains something, try to get text from it. // Calls a routine to resolve the URI and get data from it. // This routine isn't presented here. pasteData = resolveUri(pasteUri) true } else { // Something is wrong. The MIME type was plain text, but the // clipboard doesn't contain text or a Uri. Report an error. Log.e(TAG,"Clipboard contains an invalid data type") false } } } }
Java
// Responds to the user selecting "paste". case R.id.menu_paste: // Examines the item on the clipboard. If getText() does not return null, // the clip item contains the text. Assumes that this application can only // handle one item at a time. ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0); // Gets the clipboard as text. pasteData = item.getText(); // If the string contains data, then the paste operation is done. if (pasteData != null) { return true; // The clipboard doesn't contain text. If it contains a URI, attempts to get // data from it. } else { Uri pasteUri = item.getUri(); // If the URI contains something, try to get text from it. if (pasteUri != null) { // Calls a routine to resolve the URI and get data from it. // This routine isn't presented here. pasteData = resolveUri(Uri); return true; } else { // Something is wrong. The MIME type is plain text, but the // clipboard doesn't contain text or a Uri. Report an error. Log.e(TAG, "Clipboard contains an invalid data type"); return false; } }
Wklej dane z identyfikatora URI treści
Jeśli obiekt ClipData.Item
zawiera identyfikator URI treści i stwierdzisz, że możesz obsługiwać jeden z jego typów MIME, utwórz ContentResolver
i wywołaj odpowiednią metodę dostawcy treści, aby pobrać dane.
Poniżej opisano, jak uzyskać dane od dostawcy treści na podstawie identyfikatora URI treści w schowku. Sprawdza, czy dostawca udostępnia typ MIME, którego może używać aplikacja.
-
Zadeklaruj zmienną globalną zawierającą typ MIME:
Kotlin
// Declares a MIME type constant to match against the MIME types offered // by the provider. const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares a MIME type constant to match against the MIME types offered by // the provider. public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
- Pobierz globalny schowek. Pobierz też narzędzie do rozpoznawania treści, które pozwoli Ci uzyskać dostęp do dostawcy treści:
Kotlin
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Gets a content resolver instance. val cr = contentResolver
Java
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Gets a content resolver instance. ContentResolver cr = getContentResolver();
- Pobierz klip główny ze schowka i pobierz jego zawartość jako identyfikator URI:
Kotlin
// Gets the clipboard data from the clipboard. val clip: ClipData? = clipboard.primaryClip clip?.run { // Gets the first item from the clipboard data. val item: ClipData.Item = getItemAt(0) // Tries to get the item's contents as a URI. val pasteUri: Uri? = item.uri
Java
// Gets the clipboard data from the clipboard. ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { // Gets the first item from the clipboard data. ClipData.Item item = clip.getItemAt(0); // Tries to get the item's contents as a URI. Uri pasteUri = item.getUri();
- Sprawdź, czy identyfikator URI jest identyfikatorem URI treści, wywołując
getType(Uri)
. Ta metoda zwraca wartość null, jeśliUri
nie wskazuje prawidłowego dostawcy treści.Kotlin
// If the clipboard contains a URI reference... pasteUri?.let { // ...is this a content URI? val uriMimeType: String? = cr.getType(it)
Java
// If the clipboard contains a URI reference... if (pasteUri != null) { // ...is this a content URI? String uriMimeType = cr.getType(pasteUri);
- Sprawdź, czy dostawca treści obsługuje typ MIME rozpoznawany przez aplikację. Jeśli tak, wywołaj
ContentResolver.query()
, aby pobrać dane. Zwracana wartość toCursor
.Kotlin
// If the return value isn't null, the Uri is a content Uri. uriMimeType?.takeIf { // Does the content provider offer a MIME type that the current // application can use? it == MIME_TYPE_CONTACT }?.apply { // Get the data from the content provider. cr.query(pasteUri, null, null, null, null)?.use { pasteCursor -> // If the Cursor contains data, move to the first record. if (pasteCursor.moveToFirst()) { // Get the data from the Cursor here. // The code varies according to the format of the data model. } // Kotlin `use` automatically closes the Cursor. } } } }
Java
// If the return value isn't null, the Uri is a content Uri. if (uriMimeType != null) { // Does the content provider offer a MIME type that the current // application can use? if (uriMimeType.equals(MIME_TYPE_CONTACT)) { // Get the data from the content provider. Cursor pasteCursor = cr.query(uri, null, null, null, null); // If the Cursor contains data, move to the first record. if (pasteCursor != null) { if (pasteCursor.moveToFirst()) { // Get the data from the Cursor here. // The code varies according to the format of the data model. } } // Close the Cursor. pasteCursor.close(); } } } }
Wklejanie intencji
Aby wkleić intencję, najpierw pobierz globalny schowek. Sprawdź obiekt ClipData.Item
, czy zawiera obiekt Intent
. Następnie wywołaj getIntent()
, aby skopiować intencję do własnego pamięci. Można to zademonstrować za pomocą tego fragmentu kodu:
Kotlin
// Gets a handle to the Clipboard Manager. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager // Checks whether the clip item contains an Intent by testing whether // getIntent() returns null. val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent if (pasteIntent != null) { // Handle the Intent. } else { // Ignore the clipboard, or issue an error if // you expect an Intent to be on the clipboard. }
Java
// Gets a handle to the Clipboard Manager. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); // Checks whether the clip item contains an Intent, by testing whether // getIntent() returns null. Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent(); if (pasteIntent != null) { // Handle the Intent. } else { // Ignore the clipboard, or issue an error if // you expect an Intent to be on the clipboard. }
Powiadomienie systemowe wyświetlane, gdy aplikacja uzyskuje dostęp do danych ze schowka
Na Androidzie 12 (poziom interfejsu API 31) i nowszych system zwykle wyświetla toast, gdy aplikacja wywołuje metodę getPrimaryClip()
.
Tekst wiadomości ma następujący format:
APP pasted from your clipboard
System nie wyświetli tego komunikatu, gdy aplikacja wykona jedną z tych czynności:
- Uzyskuje dostęp do
ClipData
z Twojej aplikacji. - Wielokrotne korzystanie z
ClipData
z określonej aplikacji. Ten komunikat wyświetla się tylko wtedy, gdy aplikacja po raz pierwszy uzyskuje dostęp do jej danych. - Pobiera metadane obiektu klipu, np. wywołując
getPrimaryClipDescription()
zamiastgetPrimaryClip()
.
Korzystanie z usług dostawców treści w celu kopiowania złożonych danych
Dostawcy treści obsługują kopiowanie złożonych danych, takich jak rekordy bazy danych czy strumienie plików. Aby skopiować dane, umieść w schowku identyfikator URI treści. Aplikacje do wklejania pobierają ten identyfikator URI ze schowka i używają go do pobierania danych z bazy danych lub deskryptorów strumieni plików.
Aplikacja do wklejania ma tylko identyfikator URI treści Twoich danych, dlatego musi wiedzieć, który fragment danych pobrać. Informacje te możesz podać, zakodując identyfikator danych w samym identyfikatorze URI, lub możesz podać unikalny identyfikator URI, który zwraca dane, które chcesz skopiować. Wybór metody zależy od organizacji danych.
W kolejnych sekcjach opisano, jak skonfigurować identyfikatory URI, podać złożone dane i podać strumienie plików. W opisach zakładamy, że znasz ogólne zasady projektowania dostawców treści.
Zakoduj identyfikator w identyfikatorze URI
Przydatną metodą kopiowania danych do schowka za pomocą identyfikatora URI jest zakodowanie identyfikatora danych w samym identyfikatorze URI. Dostawca treści może pobrać identyfikator z identyfikatora URI i użyć go do pobrania danych. Aplikacja do wklejania nie musi wiedzieć, że identyfikator istnieje. Musi tylko pobrać ze schowka Twój „dokument referencyjny” (identyfikator URI oraz identyfikator), przekazać je dostawcy treści i odzyskać dane.
Identyfikator URI treści zwykle koduje się na końcu identyfikatora URI treści. Załóżmy na przykład, że określisz identyfikator URI dostawcy jako następujący ciąg:
"content://com.example.contacts"
Jeśli chcesz zakodować nazwę w tym identyfikatorze URI, użyj tego fragmentu kodu:
Kotlin
val uriString = "content://com.example.contacts/Smith" // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation. val copyUri = Uri.parse(uriString)
Java
String uriString = "content://com.example.contacts" + "/" + "Smith"; // uriString now contains content://com.example.contacts/Smith. // Generates a uri object from the string representation. Uri copyUri = Uri.parse(uriString);
Jeśli korzystasz już z dostawcy treści, możesz dodać nową ścieżkę identyfikatora URI, która będzie wskazywać, że identyfikator URI jest przeznaczony do kopiowania. Załóżmy na przykład, że masz już te ścieżki identyfikatorów URI:
"content://com.example.contacts/people" "content://com.example.contacts/people/detail" "content://com.example.contacts/people/images"
Możesz dodać inną ścieżkę do kopiowania identyfikatorów URI:
"content://com.example.contacts/copying"
Następnie możesz wykryć identyfikator URI „kopiuj” przez dopasowanie do wzorca i przetworzyć go za pomocą kodu przeznaczonego do kopiowania i wklejania.
Tę metodę kodowania zwykle używasz wtedy, gdy do porządkowania danych korzystasz już z dostawcy treści, wewnętrznej bazy danych lub wewnętrznej tabeli. W takich przypadkach mamy wiele danych, które chcesz skopiować, i przypuszczalnie każdy z nich ma unikalny identyfikator. W odpowiedzi na zapytanie z aplikacji do wklejania możesz wyszukać dane według ich identyfikatora i zwrócić je.
Jeśli nie masz wielu danych, prawdopodobnie nie musisz kodować identyfikatora. Możesz użyć identyfikatora URI, który jest unikalny dla Twojego dostawcy. W odpowiedzi na zapytanie dostawca zwraca dane, które obecnie zawiera.
Kopiowanie struktur danych
Skonfiguruj dostawcę treści do kopiowania i wklejania złożonych danych jako podklasy komponentu ContentProvider
. Zakoduj identyfikator URI umieszczony w schowku tak, aby kierował on dokładnie do rekordu, który chcesz podać. Weź też pod uwagę obecny stan aplikacji:
- Jeśli masz już dostawcę treści, możesz dodać do niego dodatkowe funkcje. Być może wystarczy zmodyfikować jego metodę
query()
, aby obsługiwać identyfikatory URI pochodzące z aplikacji, które chcą wkleić dane. Prawdopodobnie zechcesz zmodyfikować metodę tak, aby obsługiwała wzorzec „kopiowania” identyfikatora URI. - Jeśli aplikacja utrzymuje wewnętrzną bazę danych, możesz ją przenieść do dostawcy treści, aby ułatwić kopiowanie z niej.
- Jeśli nie korzystasz z bazy danych, możesz wdrożyć prostego dostawcę treści, którego jedynym celem jest udostępnianie danych aplikacjom wklejanym ze schowka.
U dostawcy treści zastąp co najmniej te metody:
-
query()
- Podczas wklejania aplikacji zakłada się, że mogą uzyskiwać Twoje dane, korzystając z tej metody z identyfikatorem URI umieszczonym w schowku. Aby umożliwić kopiowanie, dopilnuj, aby ta metoda wykrywała identyfikatory URI zawierające specjalną ścieżkę „copy”. Aplikacja może następnie utworzyć identyfikator URI „copy” umieszczony w schowku zawierający ścieżkę kopiowania i wskaźnik do dokładnego rekordu, który chcesz skopiować.
-
getType()
- Ta metoda musi zwracać typy MIME danych, które chcesz skopiować. Metoda
newUri()
wywołuje metodęgetType()
, aby umieścić typy MIME w nowym obiekcieClipData
.Typy MIME złożonych danych zostały opisane w sekcji Dostawcy treści.
Nie musisz używać żadnych innych metod dostawcy treści, takich jak insert()
czy update()
.
Aplikacja do wklejania wymaga jedynie obsługiwanych typów MIME i kopiowania danych od dostawcy.
Jeśli masz już te metody, nie będą one zakłócać operacji kopiowania.
Poniższe fragmenty kodu pokazują, jak skonfigurować aplikację, aby kopiować złożone dane:
-
W stałych globalnych swojej aplikacji zadeklaruj podstawowy ciąg identyfikatora URI i ścieżkę identyfikującą ciągi URI, których używasz do kopiowania danych. Zadeklaruj też typ MIME dla kopiowanych danych.
Kotlin
// Declares the base URI string. private const val CONTACTS = "content://com.example.contacts" // Declares a path string for URIs that you use to copy data. private const val COPY_PATH = "/copy" // Declares a MIME type for the copied data. const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
Java
// Declares the base URI string. private static final String CONTACTS = "content://com.example.contacts"; // Declares a path string for URIs that you use to copy data. private static final String COPY_PATH = "/copy"; // Declares a MIME type for the copied data. public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
- W aktywności, z której użytkownicy kopiują dane, skonfiguruj kod do kopiowania danych do schowka.
W odpowiedzi na żądanie kopiowania umieść identyfikator URI w schowku.
Kotlin
class MyCopyActivity : Activity() { ... when(item.itemId) { R.id.menu_copy -> { // The user has selected a name and is requesting a copy. // Appends the last name to the base URI. // The name is stored in "lastName". uriString = "$CONTACTS$COPY_PATH/$lastName" // Parses the string into a URI. val copyUri: Uri? = Uri.parse(uriString) // Gets a handle to the clipboard service. val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri) // Sets the clipboard's primary clip. clipboard.setPrimaryClip(clip) } }
Java
public class MyCopyActivity extends Activity { ... // The user has selected a name and is requesting a copy. case R.id.menu_copy: // Appends the last name to the base URI. // The name is stored in "lastName". uriString = CONTACTS + COPY_PATH + "/" + lastName; // Parses the string into a URI. Uri copyUri = Uri.parse(uriString); // Gets a handle to the clipboard service. ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri); // Sets the clipboard's primary clip. clipboard.setPrimaryClip(clip);
-
W zakresie globalnym dostawcy treści utwórz dopasowanie identyfikatorów URI i dodaj wzorzec URI odpowiadający identyfikatorom URI umieszczonym w schowku.
Kotlin
// A Uri Match object that simplifies matching content URIs to patterns. private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { // Adds a matcher for the content URI. It matches. // "content://com.example.contacts/copy/*" addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT) } // An integer to use in switching based on the incoming URI pattern. private const val GET_SINGLE_CONTACT = 0 ... class MyCopyProvider : ContentProvider() { ... }
Java
public class MyCopyProvider extends ContentProvider { ... // A Uri Match object that simplifies matching content URIs to patterns. private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); // An integer to use in switching based on the incoming URI pattern. private static final int GET_SINGLE_CONTACT = 0; ... // Adds a matcher for the content URI. It matches // "content://com.example.contacts/copy/*" sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
-
Skonfiguruj metodę
query()
. Ta metoda może obsługiwać różne wzorce identyfikatorów URI w zależności od kodu, ale widoczny jest tylko wzorzec operacji kopiowania schowka.Kotlin
// Sets up your provider's query() method. override fun query( uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { ... // When based on the incoming content URI: when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> { // Queries and returns the contact for the requested name. Decodes // the incoming URI, queries the data model based on the last name, // and returns the result as a Cursor. } } ... }
Java
// Sets up your provider's query() method. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... // Switch based on the incoming content URI. switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: // Queries and returns the contact for the requested name. Decodes the // incoming URI, queries the data model based on the last name, and // returns the result as a Cursor. ... }
-
Skonfiguruj metodę
getType()
, aby zwracać odpowiedni typ MIME w przypadku skopiowanych danych:Kotlin
// Sets up your provider's getType() method. override fun getType(uri: Uri): String? { ... return when(sUriMatcher.match(uri)) { GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT ... } }
Java
// Sets up your provider's getType() method. public String getType(Uri uri) { ... switch (sUriMatcher.match(uri)) { case GET_SINGLE_CONTACT: return (MIME_TYPE_CONTACT); ... } }
W sekcji Wklejanie danych z identyfikatora URI treści opisano, jak pobrać ze schowka identyfikator URI treści i za jego pomocą pobierać i wklejać dane.
Kopiowanie strumieni danych
Możesz kopiować i wklejać duże ilości danych tekstowych i binarnych w postaci strumieni. Dane mogą mieć następujące formularze:
- Pliki przechowywane na konkretnym urządzeniu
- Przesyłanie strumieniowe z gniazdek
- Duże ilości danych przechowywanych w bazie danych dostawcy
Dostawca treści strumieni danych zapewnia dostęp do danych za pomocą obiektu deskryptora plików, np. AssetFileDescriptor
, a nie obiektu Cursor
. Aplikacja do wklejania odczytuje strumień danych za pomocą tego deskryptora pliku.
Aby skonfigurować w aplikacji kopiowanie strumienia danych u dostawcy, wykonaj te czynności:
-
Skonfiguruj identyfikator URI treści dla strumienia danych umieszczanego w schowku. Możesz to zrobić na kilka sposobów:
- Zakoduj identyfikator strumienia danych w identyfikatorze URI zgodnie z opisem w sekcji Zakoduj identyfikator w identyfikatorze URI, a następnie utwórz u dostawcy tabelę zawierającą identyfikatory i odpowiednią nazwę strumienia.
- Zakoduj nazwę strumienia bezpośrednio w identyfikatorze URI.
- Użyj unikalnego identyfikatora URI, który zawsze zwraca bieżący strumień od dostawcy. Jeśli korzystasz z tej opcji, pamiętaj, aby zaktualizować dostawcę tak, aby wskazywał inny strumień za każdym razem, gdy kopiujesz strumień do schowka za pomocą identyfikatora URI.
- Podaj typ MIME dla każdego typu strumienia danych, który chcesz oferować. Aplikacje do wklejania potrzebują tych informacji, aby określić, czy mogą wkleić dane w schowku.
- Zaimplementuj jedną z metod
ContentProvider
, która zwraca deskryptor pliku dla strumienia. Jeśli kodujesz identyfikatory w identyfikatorze URI treści, użyj tej metody, aby określić, który strumień należy otworzyć. - Aby skopiować strumień danych do schowka, utwórz identyfikator URI treści i umieść go w schowku.
Aby wkleić strumień danych, aplikacja pobiera klip ze schowka, pobiera identyfikator URI i używa go w wywołaniu metody deskryptora pliku ContentResolver
, która otwiera strumień. Metoda ContentResolver
wywołuje odpowiednią metodę ContentProvider
, przekazując jej identyfikator URI treści. Dostawca zwraca deskryptor pliku do metody ContentResolver
. Za odczytanie danych ze strumienia odpowiada aplikacja wklejająca.
Na liście poniżej znajdziesz najważniejsze metody deskryptora plików dla dostawcy treści. Każdy z nich ma odpowiadającą jej metodę ContentResolver
z ciągiem znaków „Descriptor” dołączonym do nazwy metody. Na przykład ContentResolver
analog danych openAssetFile()
to openAssetFileDescriptor()
.
-
openTypedAssetFile()
-
Ta metoda zwraca deskryptor pliku zasobu, ale tylko wtedy, gdy podany typ MIME jest obsługiwany przez dostawcę. Element wywołujący – aplikacja wykonująca wklejanie – udostępnia wzorzec typu MIME. Dostawca treści aplikacji, która kopiuje identyfikator URI do schowka, zwraca uchwyt pliku
AssetFileDescriptor
, jeśli może podać ten typ MIME, a w razie potrzeby tworzy wyjątek.Ta metoda obsługuje podsekcje plików. Możesz go używać do odczytywania zasobów, które dostawca treści skopiował do schowka.
-
openAssetFile()
- Jest to bardziej ogólna forma
openTypedAssetFile()
. Nie filtruje dozwolonych typów MIME, ale może odczytywać podsekcje plików. -
openFile()
- To bardziej ogólna forma
openAssetFile()
. Nie może odczytywać podsekcji plików.
Opcjonalnie możesz używać metody openPipeHelper()
z metodą deskryptora pliku. Dzięki temu aplikacja wklejająca dane może odczytywać dane strumienia w wątku w tle za pomocą potoku. Aby użyć tej metody, zaimplementuj interfejs ContentProvider.PipeDataWriter
.
Zaprojektuj skuteczną funkcję kopiowania i wklejania
Aby zaprojektować skuteczną funkcję kopiowania i wklejania w aplikacji, pamiętaj o tych kwestiach:
- W danym momencie w schowku jest tylko 1 klip. Nowa operacja kopiowania podejmowana przez dowolną aplikację w systemie zastępuje poprzedni klip. Użytkownik może wyjść z aplikacji i skopiować ją, zanim z niej wróci, dlatego nie możesz zakładać, że schowek zawiera klip, który został wcześniej skopiowany do Twojej aplikacji.
-
Używanie wielu obiektów
ClipData.Item
w jednym klipie ma na celu umożliwienie kopiowania i wklejania wielu wybranych elementów, a nie różnych form odniesień do pojedynczego wyboru. Zwykle wszystkie obiektyClipData.Item
w klipie mają mieć tę samą postać. Oznacza to, że muszą to być zwykły tekst, identyfikator URI treści lub atrybutIntent
. Nie mogą one być mieszane. -
Podając dane, możesz udostępnić różne reprezentacje MIME. Dodaj obsługiwane typy MIME do pola
ClipDescription
, a następnie zaimplementuj je u dostawcy treści. -
Gdy pobierasz dane ze schowka, aplikacja odpowiada za sprawdzenie dostępnych typów MIME i wybór odpowiedniego, którego chcesz użyć. Nawet jeśli w schowku znajduje się klip, a użytkownik poprosi o wklejenie, Twoja aplikacja nie musi tego robić. Wklej, jeśli typ MIME jest zgodny. Możesz wymusić wymuszenie wyświetlania danych w schowku na tekst za pomocą funkcji
coerceToText()
. Jeśli Twoja aplikacja obsługuje kilka dostępnych typów MIME, możesz pozwolić użytkownikowi na wybór.