Kopiuj i wklej

Android zapewnia zaawansowaną platformę opartą 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 strumieni tekstowych i binarnych oraz zasoby aplikacji. Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone dane są przechowywane jako dane referencyjne, które aplikacja do wklejania obsługuje dostawcę treści. Kopiowanie i wklejanie działa zarówno w 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 zna ona interfejs Android Content Provider API, który został opisany w artykule Dostawcy treści.

Użytkownicy oczekują informacji zwrotnych podczas kopiowania treści do schowka, więc oprócz platformy umożliwiającej kopiowanie i wklejanie Android pokazuje użytkownikom domyślny interfejs użytkownika podczas kopiowania w Androidzie 13 (poziom API 33) i nowszych. Dzięki tej funkcji istnieje ryzyko zduplikowania powiadomienia. Więcej informacji o tym skrajnym przypadku znajdziesz w sekcji Unikanie zduplikowanych powiadomień.

Animacja pokazująca powiadomienie ze schowka na Androidzie 13
Rysunek 1. Interfejs widoczny, gdy zawartość znajduje się w schowku na Androidzie w wersji 13 lub nowszej.

Ręcznie przekazuj użytkownikom opinie podczas kopiowania w Androidzie 12L (poziom API 32) lub starszym. Zapoznaj się z zaleceniami na ten temat w tym dokumencie.

Struktura schowka

Jeśli korzystasz ze struktury schowka, umieść dane w obiekcie klipu, a następnie umieść obiekt klipu w schowku systemowym. Obiekt klipu może mieć jedną z 3 form:

Tekst
Ciąg tekstowy. Umieść ciąg znaków bezpośrednio w obiekcie klipu, który następnie umieszczasz w schowku. 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. Służy to głównie do kopiowania złożonych danych od dostawcy treści. Aby skopiować dane, umieść obiekt Uri w obiekcie klipu i umieść obiekt klipu w schowku. Aby wkleić dane, pobierz obiekt klipu, pobierz obiekt Uri, zastosuj go do źródła danych, np. dostawcy treści, i skopiuj dane ze źródła do pamięci aplikacji.
Zamiar
Intent. Obsługuje to kopiowanie skrótów aplikacji. Aby skopiować dane, utwórz obiekt Intent, umieść go w obiekcie klipu i umieść obiekt klipu w schowku. Aby wkleić dane, pobierz obiekt klipu, a następnie skopiuj obiekt Intent do obszaru pamięci aplikacji.

Schowek zawiera tylko 1 obiekt klipu naraz. Gdy aplikacja umieszcza w schowku obiekt klipu, poprzedni obiekt klipu znika.

Jeśli chcesz pozwolić użytkownikom na wklejanie danych do Twojej aplikacji, nie musisz obsługiwać wszystkich typów danych. Możesz przejrzeć dane w schowku, zanim umożliwisz użytkownikom ich wklejenie. Obiekt klipu nie tylko ma określony format danych, ale zawiera również metadane informujące o dostępnych typach MIME. Pomagają one określić, czy aplikacja może zrobić coś przydatnego z danymi schowka. Jeśli np. Twoja aplikacja obsługuje głównie tekst, warto ignorować obiekty przycinania zawierające identyfikator URI lub intencję.

Możesz też zezwolić użytkownikom na wklejanie tekstu niezależnie od postaci danych w schowku. Aby to zrobić, wymuś dane ze schowka jako tekst, a następnie wklej ten tekst. Zostało to opisane w sekcji Skieruj schowek na tekst.

Klasy w schowku

W tej sekcji opisano klasy używane przez platformę schowka.

Menedżer schowka

Schowek systemu Android jest reprezentowany przez globalną klasę ClipboardManager. Nie twórz bezpośrednio tej klasy. Aby się do niego odwoływać, wywołaj 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 znajduje się jeden element ClipData naraz. ClipData zawiera obiekt ClipDescription i co najmniej 1 obiekt ClipData.Item.

Obiekt ClipDescription zawiera metadane dotyczące klipu. W szczególności zawiera tablicę dostępnych typów MIME dla danych klipu. Dodatkowo w Androidzie 12 (poziom interfejsu API 31) i nowszych metadane zawierają informacje o tym, czy obiekt zawiera stylizowany tekst i o typie tekstu w obiekcie. Po umieszczeniu klipu w schowku informacje te są dostępne dla aplikacji wklejających, które mogą sprawdzać, czy są w stanie obsłużyć 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 każdy identyfikator URI. Aplikacja udostępniająca dane umieszcza identyfikator URI w schowku. Aplikacje, które chcą wkleić dane, pobierają identyfikator URI ze schowka i używają go do uzyskania dostępu do dostawcy treści lub innego źródła danych i pobrania danych.
Zamiar
Intent. Ten typ danych umożliwia skopiowanie skrótu aplikacji do schowka. Użytkownicy mogą wkleić ten skrót do aplikacji, aby użyć go w przyszłości.

Do klipu możesz dodać więcej niż 1 obiekt ClipData.Item. Dzięki temu użytkownicy mogą kopiować i wklejać wiele wybranych elementów jako jeden klip. Jeśli na przykład masz widżet listy, który umożliwia użytkownikowi wybranie więcej niż 1 elementu naraz, możesz skopiować wszystkie elementy do schowka naraz. Aby to zrobić, utwórz osobny ClipData.Item dla każdego elementu listy, a potem dodaj obiekty ClipData.Item do obiektu ClipData.

Wygodne metody ClipData

Klasa ClipData udostępnia statyczne, wygodne metody tworzenia obiektu ClipData z pojedynczym obiektem ClipData.Item i prostym obiektem ClipDescription:

newPlainText(label, text)
Zwraca obiekt ClipData, którego pojedynczy obiekt ClipData.Item zawiera ciąg tekstowy. Etykieta obiektu ClipDescription jest ustawiona na label. Pojedynczy typ MIME w kolumnie ClipDescription to MIMETYPE_TEXT_PLAIN.

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

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

Użyj newUri(), aby utworzyć klip na podstawie identyfikatora URI, a zwłaszcza identyfikatora URI content:.

newIntent(label, intent)
Zwraca obiekt ClipData, którego pojedynczy obiekt ClipData.Item zawiera Intent. Etykieta obiektu ClipDescription jest ustawiona na label. Typ MIME jest ustawiony na MIMETYPE_TEXT_INTENT.

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

Konwertuj dane ze schowka na tekst

Nawet jeśli 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 konwertuje dane w ClipData.Item na tekst i zwraca CharSequence. Wartość zwracana przez funkcję ClipData.Item.coerceToText() zależy od postaci danych w komórce ClipData.Item:

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

Struktura schowka została przedstawiona na rys. 2. Aby skopiować dane, aplikacja umieszcza obiekt ClipData w schowku globalnym 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 ClipDescription i pobiera dane z ClipData.Item lub od dostawcy treści wskazanego przez ClipData.Item.

Obraz przedstawiający schemat blokowy struktury kopiowania i wklejania
Rys. 2. Platforma schowka w Androidzie.

Skopiuj do schowka

Aby skopiować dane do schowka, pobierz uchwyt do globalnego obiektu ClipboardManager, utwórz obiekt ClipData i dodaj do niego obiekt ClipDescription oraz co najmniej 1 obiekt ClipData.Item. Następnie dodaj gotowy obiekt ClipData do obiektu ClipboardManager. Zostało to opisane bardziej szczegółowo w tej procedurze:

  1. Jeśli kopiujesz dane za pomocą identyfikatora URI treści, skonfiguruj dostawcę treści.
  2. 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);
    
  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 tworzy identyfikator URI przez kodowanie identyfikatora rekordu w identyfikatorze URI treści dostawcy. Tę metodę opisujemy szczegółowo 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);
      
    • Zamiar

      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 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 zawartość do schowka. W przypadku użytkowników Androida 13 i nowszych wersji odbywa się to automatycznie, ale w poprzednich wersjach trzeba to zrobić ręcznie.

Od Androida 13 system wyświetla standardowe wizualne potwierdzenie dodania treści do schowka. Nowe potwierdzenie wygląda tak:

  • Potwierdza, że treść została skopiowana.
  • Udostępnia podgląd skopiowanej treści.

Animacja pokazująca powiadomienie ze schowka na Androidzie 13
Rysunek 3. Interfejs widoczny, gdy zawartość znajduje się w schowku na Androidzie w wersji 13 lub nowszej.

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

Unikaj podwójnych powiadomień

W Androidzie 12L (poziom interfejsu API 32) i starszych zalecamy powiadamianie użytkowników o udanym skopiowaniu przez przesłanie opinii w aplikacji, np. Toast lub Snackbar.

Aby uniknąć powielania informacji, zdecydowanie zalecamy usunięcie tostów i pasków powiadomień wyświetlanych po tekście w aplikacji na Androida 13 lub nowszego.

Pasek powiadomień po wyświetleniu tekstu w aplikacji.
Rysunek 4. Jeśli wyświetlasz pasek powiadomień z potwierdzeniem kopiowania na Androidzie 13, użytkownik widzi zduplikowane wiadomości.
Opublikuj toast po skopiowaniu treści w aplikacji.
Rys. 5. Jeśli w Androidzie 13 wyświetlisz komunikat z potwierdzeniem kopiowania, użytkownik zobaczy zduplikowane wiadomości.

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 poufne treści do schowka

Jeśli Twoja aplikacja pozwala użytkownikom kopiować do schowka poufne treści, takie jak hasła czy dane karty kredytowej, przed wywołaniem ClipboardManager.setPrimaryClip() musisz dodać flagę do ClipDescription w ClipData. Dodanie tej flagi zapobiega wyświetlaniu treści poufnych w wizualnym potwierdzeniu skopiowanych treści na Androidzie 13 i nowszych.

Podgląd tekstu został skopiowany bez oznaczenia treści poufnych
Rysunek 6. Podgląd tekstu został skopiowany bez flagi treści poufnych.
Podgląd tekstu ze zgłoszeniem treści poufnych został skopiowany.
Rysunek 7. Podgląd tekstu został skopiowany ze flagą treści poufnych.

Aby oznaczyć treści poufne, dodaj do ClipDescription element logiczny. 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 opisano wcześniej, wklej dane ze schowka, pobierając globalny obiekt schowka, pobierając obiekt klipu, przeglądając jego dane i, w miarę możliwości kopiując dane z obiektu klipu do własnej pamięci. W tej sekcji wyjaśniamy szczegółowo, jak wklejać 3 formy danych ze schowka.

Wklej zwykły tekst

Aby wkleić zwykły tekst, pobierz schowek globalny i sprawdź, czy może on zwracać zwykły tekst. Następnie pobierz obiekt klipu i skopiuj jego tekst do swojej pamięci masowej za pomocą getText() w sposób opisany w tej procedurze:

  1. Pobierz globalny obiekt ClipboardManager za pomocą getSystemService(CLIPBOARD_SERVICE). Zadeklaruj też 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 = "";
    
  2. Określ, czy musisz włączyć lub 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);
    }
    
  3. Skopiuj dane ze schowka. Ten punkt kodu jest osiągalny tylko wtedy, gdy włączona jest pozycja menu „Wklej”, więc możesz założyć, że schowek zawiera zwykły tekst. Nie wiesz jeszcze, czy zawiera ciąg tekstowy lub identyfikator URI wskazujący zwykły tekst. Możesz to sprawdzić w poniższym fragmencie kodu, ale pokazuje on 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 ustalisz, że obsługujesz jeden z jego typów MIME, utwórz ContentResolver i wywołaj odpowiednią metodę dostawcy treści, aby pobrać dane.

Poniżej znajdziesz instrukcje pobierania danych od dostawcy treści na podstawie identyfikatora URI treści w schowku. Sprawdza, czy u dostawcy dostępny jest typ MIME, którego aplikacja może użyć.

  1. 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";
    
  2. Pobierz globalny schowek. Pobierz też program do rozpoznawania 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 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();
    
  4. Sprawdź, czy identyfikator URI jest identyfikatorem URI treści, wywołując funkcję 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 zrozumiały dla aplikacji. Jeśli tak, wywołaj 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();
                 }
             }
         }
    }
    

Wklej intencję

Aby wkleić intencję, najpierw pobierz schowek globalny. Sprawdź obiekt ClipData.Item, aby zobaczyć, czy zawiera Intent. Następnie wywołaj getIntent(), by skopiować intencję do własnego miejsca na dane. Oto przykład:

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 schowka

W Androidzie 12 (poziom interfejsu API 31) i nowszych system zwykle wyświetla komunikat ostrzegawczy, gdy aplikacja wywołuje getPrimaryClip(). Treść wiadomości ma następujący format:

APP pasted from your clipboard

System nie wyświetla komunikatu, gdy aplikacja wykona jedną z tych czynności:

  • Dostęp do ClipData z Twojej własnej aplikacji.
  • Powtarza dostęp do usługi ClipData z poziomu określonej aplikacji. Komunikat pojawia 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 getPrimaryClipDescription() zamiast getPrimaryClip().

Używanie dostawców treści do kopiowania złożonych danych

Dostawcy treści obsługują kopiowanie złożonych danych, takich jak rekordy bazy danych lub strumienie plików. Aby skopiować dane, umieść w schowku identyfikator URI treści. Wklejanie aplikacji pobiera ten identyfikator URI ze schowka i używa go do pobierania danych z bazy danych lub deskryptorów strumienia plików.

Ponieważ aplikacja do wklejania zawiera tylko identyfikator URI treści, musi wiedzieć, które dane ma pobrać. Możesz podać te informacje, zakodując identyfikator danych w samym identyfikatorze URI, lub podać unikalny identyfikator URI zwracający dane, które chcesz skopiować. Wybór metody zależy od sposobu organizacji danych.

W poniższych sekcjach opisano, jak konfigurować identyfikatory URI, podawać złożone dane i udostępniać strumienie plików. W opisach zakładamy, że znasz ogólne zasady projektowania dostawców treści.

Kodowanie identyfikatora 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 uzyskać identyfikator z identyfikatora URI i użyć go do pobrania danych. Aplikacja do wklejania nie musi wiedzieć, że identyfikator istnieje. Musi już tylko pobrać ze schowka „referencje” (identyfikator URI i identyfikator), przekazać je dostawcy treści i odzyskać dane.

Zwykle koduje się identyfikator w identyfikatorze URI treści, łącząc go z końcem identyfikatora URI. Załóżmy na przykład, że zdefiniujesz identyfikator URI dostawcy za pomocą następującego ciągu:

"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 usług dostawcy treści, możesz dodać nową ścieżkę URI wskazującą, że identyfikator URI jest przeznaczony do kopiowania. Załóżmy na przykład, że masz już następujące ścieżki identyfikatora 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ć „kopiowany” identyfikator URI, dopasowując do wzorca, i obsługiwać go za pomocą kodu przeznaczonego do kopiowania i wklejania.

Zazwyczaj metody kodowania używasz, jeśli korzystasz już z dostawcy treści, wewnętrznej bazy danych lub z wewnętrznej tabeli do porządkowania danych. W takich przypadkach masz do skopiowania wiele fragmentów danych i prawdopodobnie unikalny identyfikator dla każdego z nich. W odpowiedzi na zapytanie z aplikacji do wklejania możesz wyszukać dane według identyfikatora i je zwrócić.

Jeśli nie masz wielu danych, raczej 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 zawiera.

Kopiowanie struktur danych

Skonfiguruj dostawcę treści do kopiowania i wklejania złożonych danych jako podklasy komponentu ContentProvider. Zakoduj identyfikator URI, który umieścisz w schowku, tak aby wskazywał dokładnie rekord, który chcesz podać. Ponadto weź pod uwagę obecny stan aplikacji:

  • Jeśli masz już dostawcę treści, możesz dodać do niego nowe funkcje. Być może konieczna będzie tylko zmiana metody query(), aby obsługiwała identyfikatory URI pochodzące z aplikacji, które chcą wkleić dane. Prawdopodobnie warto zmodyfikować metodę obsługi wzorca identyfikatora URI „copy”.
  • Jeśli Twoja aplikacja utrzymuje wewnętrzną bazę danych, warto przenieść ją 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 wklejającym dane ze schowka.

W ustawieniach dostawcy treści zastąp przynajmniej te metody:

query()
Podczas wklejania aplikacji zakłada się, że mogą one pobrać Twoje dane za pomocą tej metody z identyfikatorem URI umieszczonym w schowku. Aby umożliwić kopiowanie, ta metoda wykrywa identyfikatory URI zawierające specjalną ścieżkę „copy”. Aplikacja może następnie utworzyć „kopia” identyfikator URI, który będzie umieszczony w schowku ze ścieżką kopiowania i wskaźnikiem 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 obiekcie ClipData.

Typy MIME w przypadku złożonych danych znajdziesz w sekcji Dostawcy treści.

Nie musisz korzystać z żadnych innych metod dostawcy treści, takich jak insert() czy update(). Aplikacja do wklejania musi tylko pobrać obsługiwane typy MIME i kopiować dane od dostawcy. Jeśli korzystasz już z tych metod, nie będą zakłócać operacji kopiowania.

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

  1. W stałych globalnych w 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";
    
  2. W aktywności, z której użytkownicy kopiują dane, skonfiguruj kod w celu 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);
    
  3. W zakresie globalnym dostawcy treści utwórz dopasowanie identyfikatorów URI i dodaj wzorzec identyfikatora URI pasujący 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 jest zakodowane, ale widoczny jest tylko wzorzec 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() tak, aby zwracała 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 opisujemy, jak pobrać identyfikator URI treści ze schowka oraz jak za jego pomocą pobierać i wklejać dane.

Kopiowanie strumieni danych

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

  • Pliki przechowywane na rzeczywistym urządzeniu
  • Strumienie z gniazd
  • Duże ilości danych przechowywanych w bazowym systemie baz danych dostawcy

Dostawca treści strumieni danych zapewnia dostęp do danych za pomocą obiektu deskryptora pliku, np. AssetFileDescriptor, zamiast obiektu Cursor. Aplikacja do wklejania odczytuje strumień danych za pomocą tego deskryptora pliku.

Aby skonfigurować aplikację do kopiowania strumienia danych z dostawcą, wykonaj te czynności:

  1. Skonfiguruj identyfikator URI treści dla strumienia danych, który umieszczasz w schowku. Możesz to zrobić na przykład:
    • Zakoduj identyfikator strumienia danych w identyfikatorze URI zgodnie z opisem w sekcji Kodowanie identyfikatora w identyfikatorze URI, a potem u dostawcy umieść 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 kierował do innego strumienia przy każdorazowym kopiowaniu strumienia do schowka przy użyciu identyfikatora URI.
  2. Podaj typ MIME dla każdego typu strumienia danych, który planujesz oferować. Aplikacje do wklejania potrzebują tych informacji, aby określić, czy mogą wkleić dane do schowka.
  3. 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ń otworzyć.
  4. 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. Aplikacja do wklejania będzie wtedy odpowiedzialna za odczyt danych ze strumienia.

Na liście poniżej znajdziesz najważniejsze metody deskryptorów plików dla dostawcy treści. Każda z nich ma odpowiadającą metodę ContentResolver z ciągiem „Descriptor” dołączonym do nazwy metody. Na przykład ContentResolveranalogią właściwości openAssetFile() jest 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 przeciwnym razie zgłosi wyjątek.

Ta metoda obsługuje podsekcje plików. Możesz go użyć do odczytywania zasobów, które dostawca treści skopiował do schowka.

openAssetFile()
Ta metoda jest bardziej ogólna forma metody openTypedAssetFile(). Nie filtruje ona dozwolonych typów MIME, ale może odczytywać podsekcje plików.
openFile()
To bardziej ogólna forma wartości openAssetFile(). Nie może odczytywać podsekcji plików.

Opcjonalnie możesz użyć metody openPipeHelper() z metodą deskryptora pliku. Dzięki temu aplikacja do wklejania odczytuje dane strumienia w wątku w tle za pomocą potoku. Aby skorzystać z tej metody, wdróż interfejs ContentProvider.PipeDataWriter.

Zaprojektuj skuteczną funkcję kopiowania i wklejania

Aby zaprojektować skuteczną funkcję kopiowania i wklejania w swojej aplikacji, pamiętaj o następujących kwestiach:

  • W każdej chwili w schowku znajduje się tylko 1 klip. Nowa operacja kopiowania przez dowolną aplikację w systemie zastępuje poprzedni klip. Ponieważ użytkownik może opuścić aplikację i skopiować ją przed zwróceniem, nie możesz zakładać, że schowek zawiera klip, który użytkownik wcześniej skopiował w aplikacji Twoja.
  • Wiele obiektów ClipData.Item na klip jest przeznaczone do obsługi kopiowania i wklejania wielu wybranych elementów, a nie do obsługi różnych form odniesienia do pojedynczego wyboru. Zazwyczaj wszystkie obiekty ClipData.Item w klipie powinny mieć taką samą postać. Oznacza to, że nie mogą być mieszane, muszą zawierać prosty tekst, identyfikator URI treści lub identyfikator Intent.
  • Podczas dostarczania danych możesz oferować 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 podjęcie decyzji o tym, którego z nich użyć. Nawet jeśli w schowku znajduje się klip, a użytkownik poprosi o wklejenie, aplikacja nie musi wykonywać operacji. Wklej ją, jeśli typ MIME jest zgodny. Możesz wymusić przekształcenie danych w schowku w tekst za pomocą funkcji coerceToText(). Jeśli aplikacja obsługuje więcej niż jeden z dostępnych typów MIME, możesz pozwolić użytkownikom wybrać jeden z nich.