Kopiuj i wklej

Android udostępnia zaawansowane środowisko oparte 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 tekstowe i binarne oraz zasoby aplikacji. Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone dane są przechowywane jako odwołanie, które aplikacja do wklejania rozwiązuje z dostawcą treści. Kopiowanie i wklejanie działa zarówno w ramach aplikacji, jak i między aplikacjami, które implementują ten framework.

Część tej platformy korzysta z dostawców treści, dlatego zakładamy, że znasz interfejs API dostawcy treści Androida, który jest opisany w artykule Dostawcy treści.

Użytkownicy oczekują informacji zwrotnych podczas kopiowania treści do schowka, dlatego oprócz platformy obsługującej kopiowanie i wklejanie Android wyświetla domyślne UI użytkownikom podczas kopiowania w Androidzie 13 (API na poziomie 33) i nowszych. Z powodu tej funkcji istnieje ryzyko wysłania duplikatu powiadomienia. Więcej informacji o tym szczególnym przypadku znajdziesz w sekcji Unikanie zduplikowanych powiadomień.

Animacja pokazująca powiadomienie o schowku na Androidzie 13
Rysunek 1. Interfejs wyświetlany, gdy treści trafiają do schowka w Androidzie 13 lub nowszym.

Ręczne wyświetlanie opinii użytkownikom podczas kopiowania w Androidzie 12L (poziom interfejsu API 32) i starszych. W tym dokumencie znajdziesz rekomendacje dotyczące tego.

Platforma schowka

Gdy używasz ramki interfejsu skopiowanych danych, umieść dane w obiekcie clip, a następnie umieść obiekt clip na skopiowanych danych w całym systemie. Obiekt klipu może mieć jedną z 3 form:

Tekst
Ciąg tekstowy. Umieść ciąg znaków bezpośrednio w obiekcie klipu, a następnie umieść go na klipbordzie. Aby wkleić ciąg znaków, pobierz obiekt klipu ze schowka i skopiuj ciąg znaków do pamięci aplikacji.
URI
Obiekt Uri reprezentujący dowolną formę identyfikatora URI. Jest to głównie sposób na kopiowanie złożonych danych od dostawcy treści. Aby skopiować dane, umieść obiekt Uri w obiekcie klipu, a następnie umieść obiekt klipu na schowku. Aby wkleić dane, pobierz obiekt klipu, pobierz obiekt Uri, przeanalizuj go jako źródło danych, np. dostawcę treści, i skopiuj dane ze źródła do pamięci aplikacji.
Zamiar
An Intent. Umożliwia to kopiowanie skrótów aplikacji. Aby skopiować dane, utwórz Intent, umieść je w obiekcie klipu, a potem umieść obiekt klipu na schowku. Aby wkleić dane, pobierz obiekt klipu, a potem skopiuj obiekt Intent do obszaru pamięci aplikacji.

Schowek może przechowywać tylko 1 obiekt klipu naraz. Gdy aplikacja umieszcza obiekt klipu na schowku, poprzedni obiekt klipu znika.

Jeśli chcesz, aby użytkownicy mogli wklejać dane do aplikacji, nie musisz obsługiwać wszystkich typów danych. Zanim umożliwisz użytkownikom wklejanie danych, możesz je sprawdzić w schowku. Obiekt klipu zawiera nie tylko określony format danych, ale też metadane, które określają, jakie typy MIME są dostępne. Te metadane pomagają Ci zdecydować, czy Twoja aplikacja może zrobić coś przydatnego z danymi z Schowka. Jeśli na przykład masz aplikację, która głównie obsługuje tekst, możesz zignorować obiekty klipu zawierające identyfikator URI lub intencję.

Możesz też zezwolić użytkownikom na wklejanie tekstu niezależnie od formatu danych na platformie. Aby to zrobić, skopiuj dane ze schowka do pliku tekstowego, a potem je wklej. Opisano to w sekcji Przekształcanie schowka na tekst.

Zajęcia na schowku

W tej sekcji opisujemy klasy używane przez interfejs skopiowania.

ClipboardManager

Schowek systemu Androida jest reprezentowany przez globalną klasę ClipboardManager. Nie twórz instancji tej klasy bezpośrednio. Zamiast tego odwołuj się do niego, wywołując funkcję getSystemService(CLIPBOARD_SERVICE).

ClipData, ClipData.Item i ClipDescription

Aby dodać dane do schowka, utwórz obiekt ClipData zawierający opis danych i same dane. Schowek może pomieścić tylko 1 ClipData. Obiekt ClipData zawiera obiekt ClipDescription oraz co najmniej 1 obiekt ClipData.Item.

Obiekt ClipDescription zawiera metadane klipu. Zawiera ona m.in. 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 tekst stylizowany, oraz o typie tekstu w obiekcie. Gdy umieszczasz klip na schowku, te informacje są dostępne dla aplikacji do wklejania, które mogą sprawdzić, czy mogą obsłużyć dane klipu.

Obiekt ClipData.Item zawiera tekst, identyfikator URI lub dane o intencji:

Tekst
A CharSequence.
URI
A Uri. Zwykle zawiera identyfikator URI dostawcy treści, ale dozwolony jest dowolny identyfikator URI. Aplikacja, która udostępnia dane, umieszcza identyfikator URI na schowku. Aplikacje, które chcą wkleić dane, pobierają identyfikator URI z Schowka i korzystają z niego, aby uzyskać dostęp do treści od dostawcy lub innego źródła danych.
Zamiar
An Intent. Ten typ danych umożliwia skopiowanie skrótu aplikacji do schowka. Użytkownicy mogą następnie 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 jeden klip. Jeśli na przykład masz widżet listy, który pozwala użytkownikowi wybrać więcej niż 1 element naraz, możesz skopiować wszystkie elementy naraz do schowka. Aby to zrobić, utwórz osobny element ClipData.Item dla każdego elementu listy, a potem dodaj obiekty ClipData.Item do obiektu ClipData.

Metody ClipData

Klasa ClipData udostępnia statyczne metody ułatwiające tworzenie obiektu ClipData z jednym obiektem ClipData.Item i jednym prostym obiektem ClipDescription:

newPlainText(label, text)
Zwraca obiekt ClipData, którego pojedynczy obiekt ClipData.Item zawiera ciąg tekstowy. Etykieta obiektu ClipDescription ma wartość label. Pojedynczy typ MIME w ClipDescription to:MIMETYPE_TEXT_PLAIN.

Użyj newPlainText(), aby utworzyć klip z ciągu tekstowego.

newUri(resolver, label, URI)
Zwraca obiekt ClipData, którego jedyny obiekt ClipData.Item zawiera identyfikator URI. Etykieta obiektu ClipDescription ma wartość label. Jeśli identyfikator URI jest identyfikatorem URI treści, czyli zwraca wartość Uri.getScheme()content:, metoda używa obiektu ContentResolver podawanego w parametrze resolver, aby pobrać dostępne typy MIME od dostawcy treści. Następnie przechowuje je w ClipDescription. W przypadku identyfikatora URI, który nie jest identyfikatorem content:, metoda ustawia typ MIME na MIMETYPE_TEXT_URILIST.

Użyj funkcji newUri(), aby utworzyć klip z identyfikatora URI, zwłaszcza identyfikatora content:.

newIntent(label, intent)
Zwraca obiekt ClipData, którego pojedynczy obiekt ClipData.Item zawiera obiekt Intent. Etykieta obiektu ClipDescription ma wartość label. Typ MIME ma wartość MIMETYPE_TEXT_INTENT.

Użyj newIntent(), aby utworzyć klip z obiektu Intent.

Przekształcanie danych ze schowka na tekst

Nawet jeśli Twoja aplikacja obsługuje tylko tekst, możesz skopiować dane nietekstowe ze schowka, konwertując je za pomocą metody ClipData.Item.coerceToText().

Ta metoda konwertuje dane w ClipData.Item na tekst i zwraca obiekt CharSequence. Wartość zwracana przez funkcję ClipData.Item.coerceToText() zależy od formatu danych w parametrye ClipData.Item:

Tekst
Jeśli ClipData.Item to tekst, czyli jeśli getText() nie jest null, funkcja coerceToText() zwraca tekst.
URI
Jeśli ClipData.Item to identyfikator URI, czyli getUri() nie jest równe null, coerceToText() pró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ń tekstowy, coerceToText() zwraca strumień tekstowy.
  • Jeśli identyfikator URI jest identyfikatorem URI treści, ale dostawca nie oferuje strumienia tekstowego, coerceToText() zwraca reprezentacje identyfikatora URI. Reprezentacja jest taka sama jak zwracana przez funkcję Uri.toString().
  • Jeśli identyfikator URI nie jest identyfikatorem URI treści, funkcja coerceToText() zwraca reprezentację identyfikatora URI. Reprezentacja jest taka sama jak zwracana przez funkcję Uri.toString().
Zamiar
Jeśli ClipData.Item jest Intent, czyli jeśli getIntent() nie jest równy null, funkcja coerceToText() konwertuje go na identyfikator URI intencji i zwraca go. Reprezentacja jest taka sama jak zwracana przez funkcję Intent.toUri(URI_INTENT_SCHEME).

Ramy korzystania ze schowka opisane są na rysunku 2. Aby skopiować dane, aplikacja umieszcza obiekt ClipData na globalnym schowku ClipboardManager. Obiekt ClipData zawiera co najmniej 1 obiekt ClipData.Item i 1 obiekt ClipDescription. Aby wkleić dane, aplikacja pobiera ClipData, pobiera typ MIME z ClipDescription, a następnie pobiera dane z ClipData.Item lub od dostawcy treści, do którego odwołuje się ClipData.Item.

Ilustracja przedstawiająca schemat blokowy mechanizmu kopiowania i wklejania
Rysunek 2. Platforma schowka Androida.

Kopiowanie do schowka

Aby skopiować dane do schowka, uzyskaj uchwyt globalnego obiektu ClipboardManager, utwórz obiekt ClipData i dodaj do niego obiekty ClipDescription oraz co najmniej 1 obiekt ClipData.Item. Następnie dodaj gotowy obiekt ClipData do obiektu ClipboardManager. Więcej informacji znajdziesz w tej procedurze:

  1. Jeśli kopiujesz dane za pomocą identyfikatora URI treści, skonfiguruj dostawcę treści.
  2. Aby uzyskać dostęp do schowka systemowego:

    Kotlin

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

    Java

    ...
    // If the user selects copy.
    case R.id.menu_copy:
    
    // Gets a handle to the clipboard service.
    ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
  3. 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, kodując identyfikator rekordu w identyfikatorze URI treści dostawcy. Więcej informacji o tej metodzie znajdziesz w sekcji Kodowanie identyfikatora w identyfikatorze 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);
    • Intencja

      Ten fragment kodu tworzy obiekt Intent dla aplikacji, a następnie 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);
  4. Umieść nowy obiekt klipu na schowku:

    Kotlin

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

    Java

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

Przesyłanie opinii podczas kopiowania do schowka

Użytkownicy oczekują wizualnego potwierdzenia, gdy aplikacja kopiuje dane do schowka. W przypadku użytkowników korzystających z Androida 13 lub nowszej wersji odbywa się to automatycznie, ale w wersjach starszych należy to zrobić ręcznie.

Od Androida 13 system wyświetla standardowe potwierdzenie wizualne, gdy do schowka dodawane są treści. Nowa weryfikacja:

  • Potwierdza, że treści zostały skopiowane.
  • Wyświetla podgląd skopiowanych treści.

Animacja pokazująca powiadomienie o schowku na Androidzie 13
Rysunek 3. Interfejs wyświetlany, gdy treści trafiają do schowka w Androidzie 13 lub nowszym.

W Androidzie 12L (poziom interfejsu API 32) i starszych użytkownicy mogą nie mieć pewności, czy udało im się skopiować treści lub co zostało skopiowane. Ta funkcja standaryzuje różne powiadomienia wyświetlane przez aplikacje po skopiowaniu i daje użytkownikom większą kontrolę nad schowek.

Unikanie powielonych powiadomień

W systemie Android 12L (poziom interfejsu API 32) i starszych zalecamy powiadamianie użytkowników o udanym skopiowaniu danych, wyświetlając wizualne informacje w aplikacji za pomocą widżetu, np. Toast lub Snackbar, po skopiowaniu.

Aby uniknąć wyświetlania duplikatów informacji, zdecydowanie zalecamy usunięcie toastów lub pasków informacji wyświetlanych po wyświetleniu kopii w aplikacji w przypadku Androida 13 i wyższych.

Opublikuj snackbar po treści w aplikacji.
Rysunek 4. Jeśli na Androidzie 13 wyświetlisz pasek informacji o kopiowaniu, użytkownik zobaczy zduplikowane wiadomości.
Opublikuj powiadomienie po wyświetleniu komunikatu w aplikacji.
Rysunek 5. Jeśli na Androidzie 13 wyświetlisz potwierdzenie kopiowania, użytkownik zobaczy zduplikowane wiadomości.

Oto przykład implementacji:

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()
}

Dodawanie poufnych treści do schowka

Jeśli Twoja aplikacja umożliwia użytkownikom kopiowanie treści poufnych do schowka, takich jak hasła czy informacje o karcie kredytowej, przed wywołaniem metody ClipboardManager.setPrimaryClip() musisz dodać flagę do elementu ClipDescription w pliku ClipData. Dodanie tej flagi zapobiega wyświetlaniu treści poufnych w wizualnym potwierdzeniu skopiowanych treści w Androidzie 13 i nowszych.

Podgląd skopiowanego tekstu bez oznaczenia treści poufnych
Rysunek 6. Podgląd skopiowanego tekstu bez flagi treści poufnych.
Podgląd skopiowanego tekstu z oznaczeniem treści poufnych.
Rysunek 7. Skopiowany podgląd tekstu z oznaczeniem treści poufnych.

Aby oznaczyć treści o charakterze kontrowersyjnym, dodaj do elementu ClipDescription dodatkowy element logiczny. Wszystkie aplikacje muszą to zrobić niezależnie od poziomu interfejsu API, na który są kierowane.

// 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)
    }
}

Wklejanie ze schowka

Jak opisano wcześniej, wklej dane z podkładki, pobierając globalny obiekt podkładki, pobierając obiekt klipu, przeglądając jego dane i w miarę możliwości kopiując dane z obiektu klipu do własnego magazynu. Z tej sekcji dowiesz się, jak wkleić 3 rodzaje danych z Schowka.

Wklej zwykły tekst

Aby wkleić zwykły tekst, pobierz globalny schowek i sprawdź, czy może zwrócić zwykły tekst. Następnie pobierz obiekt klipu i skopiuj jego tekst do własnego miejsca na dane za pomocą funkcji getText(), postępując zgodnie z tą procedurą:

  1. Pobierz globalny obiekt ClipboardManager za pomocą parametru getSystemService(CLIPBOARD_SERVICE). Zadeklaruj też zmienną globalną, która będzie przechowywać wklejony tekst:

    Kotlin

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

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
  2. Określ, czy w bieżącej aktywności musisz włączyć lub wyłączyć opcję „Wklej”. Sprawdź, czy schowek zawiera klip i czy możesz obsługiwać typ danych reprezentowany 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);
    }
  3. Skopiuj dane ze schowka. Ten punkt kodu jest dostępny tylko wtedy, gdy opcja „Wklej” jest włączona, więc możesz założyć, że schowek zawiera zwykły tekst. Nie wiesz jeszcze, czy zawiera ciąg tekstowy, czy URI, który wskazuje na tekst zwykły. Poniższy fragment kodu testuje to, 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;
        }
    }

Wklejanie danych z identyfikatora URI treści

Jeśli obiekt ClipData.Item zawiera URI treści i określasz, że możesz obsłużyć jeden z typów MIME, utwórz obiekt ContentResolver i wywołaj odpowiednią metodę dostawcy treści, aby pobrać dane.

Z tej procedury dowiesz się, jak pobrać dane od dostawcy treści na podstawie URI treści w schowku. Sprawdza, czy typ MIME, którego aplikacja może używać, jest dostępny u dostawcy.

  1. Zadeklaruj zmienną globalną, która będzie zawierać 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";
  2. Pobierz skopiowany tekst. Uzyskaj też rozwiązywanie treści, aby 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();
  3. Pobierz główny klip ze schowka i uzyskaj 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();
  4. Sprawdź, czy identyfikator URI jest identyfikatorem URI treści, wywołując metodę getType(Uri). Ta metoda zwraca wartość null, jeśli Uri 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);
  5. Sprawdź, czy dostawca treści obsługuje typ MIME, który rozumie aplikacja. Jeśli tak, wywołaj metodę ContentResolver.query(), aby pobrać dane. Zwracana wartość to Cursor.

    Kotlin

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

    Java

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

Wklejanie intencji

Aby wkleić intencję, najpierw pobierz globalną Clipboard. Sprawdź obiekt ClipData.Item, aby zobaczyć, czy zawiera on element Intent. Następnie wywołaj getIntent(), aby skopiować intencję do własnego magazynu. Poniżej znajduje się fragment kodu, który to demonstruje:

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

W Androidzie 12 (poziom interfejsu API 31) i nowszych system zwykle wyświetla komunikat toastowy, gdy aplikacja wywołuje funkcję getPrimaryClip(). Tekst w wiadomości ma taki format:

APP pasted from your clipboard

System nie wyświetla komunikatu, gdy aplikacja:

  • Dostęp do ClipData z Twojej aplikacji.
  • wielokrotnie uzyskuje dostęp do funkcji ClipData w określonej aplikacji. Powiadomienie wyświetla się tylko wtedy, gdy aplikacja po raz pierwszy uzyskuje dostęp do danych z tej aplikacji.
  • Pobiera metadane obiektu klipu, na przykład przez wywołanie funkcji getPrimaryClipDescription() zamiast getPrimaryClip().

Kopiowanie złożonych danych za pomocą dostawców treści

Dostawcy treści obsługują kopiowanie złożonych danych, takich jak rekordy bazy danych czy strumienie plików. Aby skopiować dane, umieść identyfikator URI treści na schowku. Aplikacje do wklejania pobierają ten identyfikator URI z Schowka i używają go do pobierania danych bazy danych lub deskryptorów strumienia plików.

Aplikacja do wklejania ma tylko identyfikator URI treści, więc musi wiedzieć, które dane ma pobrać. Możesz podać te informacje, kodując identyfikator danych w samym identyfikatorze URI lub podając unikalny identyfikator URI, który zwraca dane, które chcesz skopiować. Wybór techniki zależy od organizacji danych.

W następnych sekcjach znajdziesz instrukcje konfigurowania identyfikatorów URI, przesyłania złożonych danych i strumyków plików. Opisy zakładają, że znasz ogólne zasady projektowania treści.

Kodowanie identyfikatora w identyfikatorze URI

Przydatną techniką kopiowania danych na schowek za pomocą identyfikatora URI jest zakodowanie identyfikatora danych w samym identyfikatorze URI. Twój dostawca treści może następnie pobrać identyfikator z identyfikatora URI i użyć go do pobrania danych. Aplikacja, która wkleja dane, nie musi wiedzieć, że identyfikator istnieje. Wystarczy, że pobiera „odniesienie” (identyfikator URI plus identyfikator) z Schowka, przekazuje je dostawcy treści i odbiera dane.

Zwykle identyfikator jest kodowany w identyfikatorze URI treści przez jego złączenie z końcem identyfikatora URI. Załóżmy na przykład, że zdefiniujesz identyfikator URI dostawcy jako ten ciąg znaków:

"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 wskazuje, że identyfikator URI służy do kopiowania. Załóżmy na przykład, że masz już te ścieżki 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ć URI „kopiowania” za pomocą dopasowania wzorca i obsługiwać go za pomocą kodu przeznaczonego do kopiowania i wklejania.

Techniki kodowania używa się zwykle, gdy do organizowania danych używasz już dostawcy treści, wewnętrznej bazy danych lub wewnętrznej tabeli. W takich przypadkach masz wiele elementów danych, które chcesz skopiować, oraz prawdopodobnie unikalny identyfikator dla każdego z nich. W odpowiedzi na zapytanie aplikacji do przesyłania danych możesz wyszukać dane według identyfikatora i zwrócić je.

Jeśli nie masz wielu elementów 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 aktualne dane.

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, aby wskazywał dokładnie ten rekord, który chcesz podać. Weź też pod uwagę obecny stan aplikacji:

  • Jeśli masz już dostawcę treści, możesz rozszerzyć jego funkcjonalność. Może być konieczne tylko zmodyfikowanie metody query() w celu obsługi adresów URI pochodzących z aplikacji, które chcą wkleić dane. Prawdopodobnie musisz zmodyfikować metodę, aby obsługiwała wzór „copy” URI.
  • Jeśli Twoja aplikacja obsługuje wewnętrzną bazę danych, możesz ją przenieść do dostawcy treści, aby ułatwić jej kopiowanie.
  • Jeśli nie używasz bazy danych, możesz wdrożyć prostego dostawcę treści, którego jedynym celem jest udostępnianie danych aplikacjom, które wklejają z bufora.

W dostawcy treści zastąpij co najmniej te metody:

query()
Aplikacje do wklejania danych zakładają, że mogą pobrać Twoje dane za pomocą tej metody z identyfikatora URI umieszczonego w schowku. Aby umożliwić kopiowanie, ta metoda wykrywa identyfikatory URI zawierające specjalną ścieżkę „copy”. Aplikacja może utworzyć identyfikator URI „kopiowania” i przekazać go do schowka, 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 getType(), aby umieścić typy MIME w nowym obiekcie ClipData.

Typy MIME złożonych danych są opisane w sekcji Dostawcy treści.

Nie musisz używać żadnej innej metody dostawcy treści, takiej jak insert() lub update(). Aplikacja do wklejania musi tylko pobrać obsługiwane typy MIME i skopiować dane od dostawcy. Jeśli masz już te metody, nie będą one przeszkadzać w operacjach kopiowania.

Poniższe fragmenty kodu pokazują, jak skonfigurować aplikację do kopiowania danych złożonych:

  1. W konstantach globalnych aplikacji zadeklaruj ciąg znaków identyfikatora URI podstawowego i ścieżkę, która identyfikuje ciągi znaków identyfikatorów URI używanych do kopiowania danych. Zadeklaruj też typ MIME skopiowanych 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";
  2. W działalności, z której użytkownicy kopiują dane, skonfiguruj kod, aby kopiować dane do schowka. W odpowiedzi na prośbę o skopiowanie wklej identyfikator URI do schowka.

    Kotlin

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

    Java

    public class MyCopyActivity extends Activity {
        ...
    // The user has selected a name and is requesting a copy.
    case R.id.menu_copy:
    
        // Appends the last name to the base URI.
        // The name is stored in "lastName".
        uriString = CONTACTS + COPY_PATH + "/" + lastName;
    
        // Parses the string into a URI.
        Uri copyUri = Uri.parse(uriString);
    
        // Gets a handle to the clipboard service.
        ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    
        // Sets the clipboard's primary clip.
        clipboard.setPrimaryClip(clip);
  3. W globalnym zakresie dostawcy treści utwórz dopasowywacz identyfikatorów URI i dodaj wzór identyfikatorów URI, który będzie pasował do identyfikatorów URI umieszczonych 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);
  4. Skonfiguruj metodę query(). Ta metoda może obsługiwać różne wzorce identyfikatorów URI w zależności od tego, jak je zaprogramujesz, ale wyświetla się tylko wzór operacji kopiowania do 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.
        ...
    }
  5. Skonfiguruj metodę getType(), aby zwracała odpowiedni typ MIME dla 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 opisaliśmy, jak pobrać identyfikator URI treści z Schowka i użyć go do pobrania i wklejenia danych.

Kopiowanie strumieni danych

Możesz kopiować i wklejać duże ilości tekstu i danych binarnych jako strumienie. Dane mogą mieć postać:

  • pliki przechowywane na urządzeniu,
  • Strumienie z gniazd
  • duże ilości danych przechowywanych w systemie bazy danych dostawcy;

Dostawca treści dla strumieni danych udostępnia dostęp do swoich danych za pomocą obiektu deskryptora pliku, takiego jak AssetFileDescriptor, zamiast obiektu Cursor. Aplikacja do wklejania odczytuje strumień danych za pomocą tego deskryptora pliku.

Aby skonfigurować aplikację w celu kopiowania strumienia danych z dostawcą:

  1. Skonfiguruj identyfikator URI treści dla strumienia danych, który umieszczasz w schowku. Dostępne opcje:
    • Skonfiguruj kodowanie identyfikatora strumienia danych w identyfikatorze URI zgodnie z opisem w sekcji Kodowanie identyfikatora w identyfikatorze URI, a potem utwórz i utrzymywaj w swojej usłudze tabelę z identyfikatorami i odpowiednimi nazwami strumieni.
    • Kodowanie nazwy strumienia bezpośrednio w identyfikatorze URI.
    • Użyj unikalnego identyfikatora URI, który zawsze zwraca bieżący strumień od dostawcy. Jeśli używasz tej opcji, pamiętaj, aby po skopiowaniu strumienia do schowka za pomocą identyfikatora URI zaktualizować u dostawcy adres innego strumienia.
  2. 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 ze schowka.
  3. Zaimplementuj jedną z metod ContentProvider, która zwraca deskryptor pliku dla strumienia. Jeśli zidentyfikujesz identyfikatory w URI treści, użyj tej metody, aby określić, który strumień otworzyć.
  4. Aby skopiować strumień danych do schowka, utwórz identyfikator URI treści i umieść go na schowku.

Aby wkleić strumień danych, aplikacja pobiera klip ze schowka, pobiera identyfikator URI i wykorzystuje go w wywołaniu metody opisu pliku ContentResolver, która otwiera strumień. Metoda ContentResolver wywołuje odpowiadającą jej metodę ContentProvider, przekazując jej identyfikator URI treści. Dostawca zwraca deskryptor pliku do metody ContentResolver. Odczytywanie danych ze strumienia jest wtedy zadaniem aplikacji do wklejania.

Poniższa lista zawiera najważniejsze metody opisu pliku dla dostawcy treści. Każda z nich ma odpowiadającą jej metodę ContentResolver z dodaną do nazwy metody ścieżką „Descriptor”. Na przykład analog funkcji ContentResolver do funkcji openAssetFile() to openAssetFileDescriptor().

openTypedAssetFile()

Ta metoda zwraca opis pliku zasobu, ale tylko wtedy, gdy podany typ MIME jest obsługiwany przez dostawcę. Aplikacja wywołująca funkcję wklejania podaje wzór 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 przeciwnym razie zgłasza wyjątek.

Ta metoda obsługuje podsekcje plików. Możesz go używać do odczytania zasobów skopiowanych przez dostawcę treści do schowka.

openAssetFile()
Ta metoda jest bardziej ogólną formą metody openTypedAssetFile(). Nie filtruje dozwolonych typów MIME, ale może odczytywać podsekcje plików.
openFile()
To bardziej ogólna forma openAssetFile(). Nie może odczytać podsekcje plików.

Opcjonalnie możesz użyć metody openPipeHelper() z metodą deskryptora pliku. Dzięki temu aplikacja do wklejania może odczytywać dane strumienia w wątku w tle za pomocą kanału. Aby używać tej metody, zaimplementuj interfejs ContentProvider.PipeDataWriter.

Projektowanie skutecznej funkcji kopiowania i wklejania

Aby zaprojektować skuteczną funkcję kopiowania i wklejania w aplikacji, pamiętaj o tych kwestiach:

  • W danej chwili na schowku może znajdować się tylko 1 klip. Nowa operacja kopiowania przez dowolną aplikację w systemie powoduje zastąpienie poprzedniego klipu. Użytkownik może opuścić Twoją aplikację i wkleić zawartość, zanim do niej wróci. Nie możesz więc zakładać, że schowek zawiera skopiowany wcześniej przez użytkownika fragment w Twojej aplikacji.
  • Zadaniem wielu obiektów ClipData.Item w jednym klipie jest umożliwienie kopiowania i wklejania wielu elementów, a nie różnych form odniesienia do pojedynczego elementu. Zwykle wszystkie obiekty ClipData.Item w klipie powinny mieć ten sam format. Oznacza to, że wszystkie muszą być proste teksty, identyfikatory URI treści lub Intent, a nie mieszanką tych elementów.
  • Podczas udostępniania danych możesz zaoferować różne reprezentacje MIME. Dodaj obsługiwane typy MIME do ClipDescription, a następnie zaimplementuj te typy MIME w dostawcy treści.
  • Gdy pobierasz dane z Schowka, Twoja aplikacja jest odpowiedzialna za sprawdzenie dostępnych typów MIME, a następnie za wybranie odpowiedniego typu. Nawet jeśli na komputerze użytkownika jest klip na pulpicie i użytkownik prosi o wklejenie, aplikacja nie musi tego robić. Wklej, jeśli typ MIME jest zgodny. Możesz skonwertować dane ze schowka na tekst za pomocą polecenia coerceToText(). Jeśli Twoja aplikacja obsługuje więcej niż 1 typ MIME, możesz pozwolić użytkownikowi wybrać, którego użyć.