Kopiuj i wklej

Wypróbuj Compose
Jetpack Compose to zalecany zestaw narzędzi interfejsu na Androida. Dowiedz się, jak korzystać z funkcji kopiowania i wklejania w Compose.

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 strumieni tekstowych i binarnych oraz komponenty aplikacji. Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone dane są przechowywane jako odwołanie, które aplikacja wklejająca rozwiązuje za pomocą dostawcy treści. Kopiowanie i wklejanie działa zarówno w ramach aplikacji, jak i między aplikacjami, które implementują ten framework.

Część struktury korzysta z dostawców treści, dlatego w tym dokumencie zakłada się pewną znajomość interfejsu Android Content Provider API, który jest opisany w artykule Dostawcy treści.

Użytkownicy oczekują informacji zwrotnej podczas kopiowania treści do schowka, więc oprócz platformy, która obsługuje kopiowanie i wklejanie, Android wyświetla domyślny interfejs użytkownika podczas kopiowania w Androidzie 13 (API na poziomie 33) i nowszym. Ze względu na tę funkcję istnieje ryzyko duplikowania powiadomień. Więcej informacji o tym przypadku znajdziesz w sekcji Unikanie zduplikowanych powiadomień.

Animacja pokazująca powiadomienie o schowku w Androidzie 13
Rysunek 1. Interfejs użytkownika wyświetlany, gdy zawartość trafia do schowka na urządzeniach z Androidem 13 lub nowszym.

Ręczne przekazywanie opinii użytkownikom podczas kopiowania w Androidzie 12L (poziom API 32) i starszych wersjach. Rekomendacje dotyczące tego znajdziesz w tym dokumencie.

Platforma schowka

Gdy używasz platformy schowka, umieść dane w obiekcie klipu, a następnie umieść ten obiekt w schowku systemowym. Obiekt klipu może przyjmować jedną z 3 form:

Tekst
Ciąg tekstowy. Umieść ciąg znaków bezpośrednio w obiekcie klipu, który następnie umieścisz w schowku. Aby wkleić ciąg znaków, pobierz obiekt 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 schowka, a następnie umieść obiekt schowka w schowku. Aby wkleić dane, pobierz obiekt schowka, pobierz obiekt Uri, rozwiąż go do źródła danych, takiego jak dostawca treści, i skopiuj dane ze źródła do pamięci aplikacji.
Zamiar
An Intent. Umożliwia to kopiowanie skrótów do aplikacji. Aby skopiować dane, utwórz Intent, umieść go w obiekcie klipu i umieść obiekt klipu w schowku. Aby wkleić dane, pobierz obiekt schowka, a następnie skopiuj obiekt Intent do obszaru pamięci aplikacji.

Schowek może przechowywać tylko 1 obiekt klipu naraz. 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. Możesz sprawdzić dane w schowku, zanim udostępnisz użytkownikom opcję wklejenia. Oprócz określonej formy danych obiekt klipu zawiera też metadane, które informują o dostępnych typach MIME. Te metadane pomagają zdecydować, czy aplikacja może w jakiś sposób wykorzystać dane ze schowka. Jeśli na przykład masz aplikację, która obsługuje głównie tekst, możesz zignorować obiekty schowka zawierające identyfikator URI lub intencję.

Możesz też zezwolić użytkownikom na wklejanie tekstu niezależnie od formatu danych w schowku. W tym celu wymuś przekształcenie danych ze schowka w tekst, a następnie wklej ten tekst. Opisano to w sekcji Wymuszanie przekształcenia schowka w tekst.

Klasy schowka

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

ClipboardManager

Schowek systemowy Androida jest reprezentowany przez klasę globalną ClipboardManager. Nie twórz bezpośrednio instancji tej klasy. Zamiast tego uzyskaj do niego odwołanie, 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. W schowku może się znajdować tylko 1 ClipData naraz. Obiekt ClipData zawiera obiekt ClipDescription i co najmniej 1 obiekt ClipData.Item.

Obiekt ClipDescription zawiera metadane klipu. Zawiera on w szczególności tablicę dostępnych typów MIME dla danych klipu. Dodatkowo na Androidzie 12 (poziom API 31) i nowszym metadane zawierają informacje o tym, czy obiekt zawiera stylizowany tekst, oraz o rodzaju tekstu w obiekcie. Gdy umieścisz klip w schowku, informacje te będą dostępne dla aplikacji, które mogą wklejać dane. Aplikacje te mogą sprawdzić, czy są w stanie obsłużyć dane klipu.

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

Tekst
A CharSequence.
URI
A Uri. Zwykle zawiera on identyfikator URI dostawcy treści, ale dozwolony jest dowolny identyfikator URI. Aplikacja, która udostępnia 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
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. Umożliwia to użytkownikom kopiowanie i wklejanie wielu zaznaczeń jako jednego klipu. 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 jednocześnie. Aby to zrobić, utwórz osobny element ClipData.Item dla każdego elementu listy, a następnie dodaj obiekty ClipData.Item do obiektu ClipData.

Metody wygody 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 obiekt ClipData.Item zawiera ciąg tekstowy. Etykieta obiektu ClipDescription jest ustawiona na label. Pojedynczy typ MIME w ClipDescription to MIMETYPE_TEXT_PLAIN.

Użyj kodu newPlainText(), aby utworzyć klip z 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 content:, metoda używa obiektu ContentResolver podanego w resolver, aby pobrać dostępne typy MIME od dostawcy treści. Następnie zapisuje je w 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ć wycinek z identyfikatora URI, zwłaszcza z 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 newIntent(), aby utworzyć klip z obiektu Intent.

Wymuszanie przekształcenia danych ze schowka w tekst

Nawet jeśli aplikacja obsługuje tylko tekst, możesz skopiować z schowka dane inne niż tekstowe, przekształcają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 formatu danych w parametrze ClipData.Item:

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

Architekturę schowka przedstawia rysunek 2. Aby skopiować dane, aplikacja umieszcza obiekt ClipData w 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 i pobiera dane z ClipData.Item lub od dostawcy treści, do którego odwołuje się ClipData.Item.

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

Kopiowanie do schowka

Aby skopiować dane do schowka, uzyskaj dostęp 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. Więcej informacji znajdziesz 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 kodu tworzy identyfikator URI, kodując identyfikator rekordu w identyfikatorze URI treści dostawcy. Ta technika jest szczegółowo opisana w sekcji Kodowanie identyfikatora w 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);
    • W przypadku intencji

      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);

Przesyłanie opinii podczas kopiowania do schowka

Użytkownicy oczekują wizualnego potwierdzenia, że aplikacja skopiowała treść do schowka. W przypadku użytkowników Androida 13 i nowszych wersji odbywa się to automatycznie, ale w starszych wersjach trzeba to wdrożyć ręcznie.

Od Androida 13 system wyświetla standardowe potwierdzenie wizualne, gdy treść zostanie dodana do schowka. Nowe potwierdzenie:

  • Potwierdza, że zawartość została skopiowana.
  • Wyświetla podgląd skopiowanych treści.

Animacja pokazująca powiadomienie o schowku w Androidzie 13
Rysunek 3. Interfejs użytkownika wyświetlany, gdy zawartość trafia do schowka na urządzeniach z Androidem 13 lub nowszym.

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

Unikanie duplikowania powiadomień

W Androidzie 12L (poziom interfejsu API 32) i starszych wersjach zalecamy informowanie użytkowników o pomyślnym skopiowaniu treści za pomocą wizualnych informacji zwrotnych w aplikacji, np. za pomocą widżetu Toast lub Snackbar.

Aby uniknąć zduplikowanego wyświetlania informacji, zalecamy usunięcie wyskakujących okienek lub pasków powiadomień wyświetlanych po skopiowaniu tekstu w aplikacji na urządzeniach z Androidem 13 lub nowszym.

Wyświetl pasek powiadomień po skopiowaniu tekstu w aplikacji.
Rysunek 4. Jeśli w Androidzie 13 wyświetlisz pasek z potwierdzeniem skopiowania, użytkownik zobaczy zduplikowane wiadomości.
Wyświetlanie powiadomienia po skopiowaniu tekstu w aplikacji.
Rysunek 5. Jeśli w Androidzie 13 wyświetlisz potwierdzenie skopiowania w formie wyskakującego okienka, 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 do schowka treści o charakterze poufnym, takich jak hasła lub dane karty kredytowej, przed wywołaniem funkcji ClipboardManager.setPrimaryClip() musisz dodać flagę do ClipDescriptionClipData. Dodanie tej flagi zapobiega wyświetlaniu treści o charakterze kontrowersyjnym w wizualnym potwierdzeniu skopiowanych treści na urządzeniach z Androidem 13 lub nowszym.

Podgląd skopiowanego tekstu bez oznaczania treści poufnych
Rysunek 6. Podgląd skopiowanego tekstu bez oznaczenia treści wrażliwych.
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 parametru ClipDescription dodatkową wartość logiczną. Wszystkie aplikacje muszą to robić niezależnie od docelowego poziomu 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)
    }
}

Wklejanie ze schowka

Jak opisano wcześniej, wklej dane ze schowka, pobierając globalny obiekt schowka, pobierając obiekt wycinka, sprawdzając jego dane i w miarę możliwości kopiując dane z obiektu wycinka do własnego miejsca na dane. W tej sekcji szczegółowo opisujemy, jak wklejać 3 rodzaje danych ze schowka.

Wklej zwykły tekst

Aby wkleić zwykły tekst, pobierz globalny schowek i sprawdź, czy może on zwrócić zwykły tekst. Następnie pobierz obiekt klipu i skopiuj jego tekst do własnego miejsca na dane za pomocą getText(), jak opisano w tej procedurze:

  1. Pobierz globalny obiekt ClipboardManager za pomocą funkcji 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 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 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 w kodzie jest osiągalny tylko wtedy, gdy element menu „Wklej” jest włączony, więc możesz założyć, że schowek zawiera zwykły tekst. Nie wiesz jeszcze, czy zawiera ciąg tekstowy czy URI wskazujący na zwykły tekst. Poniższy fragment kodu to sprawdza, ale pokazuje tylko kod obsługujący zwykły tekst:

    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 identyfikator URI treści i ustalisz, że możesz obsłużyć jeden z jego typów MIME, utwórz obiekt ContentResolver i wywołaj odpowiednią metodę dostawcy treści, aby pobrać dane.

Poniższa procedura opisuje, jak pobrać dane od dostawcy treści na podstawie identyfikatora URI treści w schowku. Sprawdza, czy dostawca udostępnia typ MIME, którego aplikacja może używać.

  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 globalny schowek. Uzyskaj też moduł do rozwiązywania problemów z treściami, aby mieć 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 getType(Uri). Jeśli Uri nie wskazuje prawidłowego dostawcy treści, ta metoda zwraca wartość null.

    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 jest zrozumiały dla aplikacji. Jeśli tak, wywołaj ContentResolver.query(), aby pobrać dane. Wartość zwracana 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 otwórz globalny schowek. Sprawdź obiekt ClipData.Item, czy zawiera element Intent. Następnie wywołaj getIntent(), aby skopiować intencję do własnego miejsca na dane. Ilustruje to ten fragment 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

W Androidzie 12 (API na poziomie 31) i nowszych system zwykle wyświetla komunikat w formie toastu, gdy aplikacja wywołuje getPrimaryClip(). Tekst w wiadomości ma ten format:

APP pasted from your clipboard

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

  • Dostęp do ClipData z własnej aplikacji.
  • Wielokrotnie uzyskuje dostęp do ClipData z określonej aplikacji. Wyskakujące okienko pojawia się tylko wtedy, gdy aplikacja po raz pierwszy uzyskuje dostęp do danych z tej aplikacji.
  • Pobiera metadane obiektu klipu, np. wywołując funkcję 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 w schowku. Aplikacje wklejające pobierają ten identyfikator URI ze schowka i używają go do pobierania danych z bazy danych lub deskryptorów strumieni plików.

Aplikacja, do której wklejasz dane, 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 sekcjach poniżej opisujemy, jak konfigurować identyfikatory URI, podawać złożone dane i przesyłać strumienie plików. Opisy zakładają, że znasz ogólne zasady projektowania dostawców treści.

Kodowanie identyfikatora w identyfikatorze URI

Przydatną techniką kopiowania danych do schowka za pomocą identyfikatora URI jest zakodowanie identyfikatora danych w samym identyfikatorze URI. Dostawca treści może następnie pobrać identyfikator z identyfikatora URI i użyć go do pobrania danych. Aplikacja, do której wklejasz identyfikator, nie musi wiedzieć, że on istnieje. Musi tylko pobrać z schowka „odniesienie” – identyfikator URI i identyfikator –, przekazać je dostawcy treści i otrzymać dane.

Identyfikator zwykle koduje się w identyfikatorze URI treści, łącząc go z końcem identyfikatora URI. Załóżmy na przykład, że identyfikator URI dostawcy jest zdefiniowany 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 usług dostawcy treści, możesz dodać nową ścieżkę URI, która będzie wskazywać, ż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ć kolejną ścieżkę do kopiowania identyfikatorów URI:

"content://com.example.contacts/copying"

Następnie możesz wykryć URI „copy” za pomocą dopasowywania wzorców i obsłużyć go za pomocą kodu, który jest przeznaczony do kopiowania i wklejania.

Technikę kodowania stosuje się zwykle, jeśli do porządkowania danych używasz już dostawcy treści, wewnętrznej bazy danych lub wewnętrznej tabeli. W takich przypadkach masz wiele danych, które chcesz skopiować, i prawdopodobnie unikalny identyfikator dla każdego z nich. W odpowiedzi na zapytanie z aplikacji wklejającej możesz wyszukać dane według identyfikatora i je zwrócić.

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 dane, które obecnie zawiera.

Kopiowanie struktur danych

Skonfiguruj dostawcę treści do kopiowania i wklejania złożonych danych jako podklasę komponentu ContentProvider. Zakoduj identyfikator URI skopiowany do schowka, aby wskazywał dokładnie ten rekord, który chcesz udostępnić. Weź też pod uwagę obecny stan aplikacji:

  • Jeśli masz już dostawcę treści, możesz rozszerzyć jego funkcjonalność. Może być konieczne zmodyfikowanie tylko metody query(), aby obsługiwać identyfikatory URI pochodzące z aplikacji, które chcą wklejać dane. Prawdopodobnie chcesz zmodyfikować metodę, aby obsługiwała wzorzec URI „copy”.
  • Jeśli Twoja aplikacja ma wewnętrzną bazę danych, możesz przenieść ją do dostawcy treści, aby ułatwić kopiowanie z niej danych.
  • Jeśli nie używasz bazy danych, możesz wdrożyć prosty dostawcę treści, którego jedynym celem jest udostępnianie danych aplikacjom wklejającym informacje ze schowka.

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

query()
Aplikacje wklejające zakładają, że mogą uzyskać Twoje dane za pomocą tej metody z użyciem identyfikatora URI, który został skopiowany do schowka. Aby umożliwić kopiowanie, ta metoda powinna wykrywać identyfikatory URI zawierające specjalną ścieżkę „copy”. Aplikacja może wtedy utworzyć identyfikator URI „kopiowania”, który można umieścić w schowku. Będzie on zawierać ś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 zostały opisane w sekcji Dostawcy treści.

Nie musisz mieć żadnych innych metod dostawcy treści, np. insert() ani 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 kolidować z operacjami kopiowania.

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

  1. W stałych globalnych aplikacji zadeklaruj ciąg podstawowego identyfikatora URI i ścieżkę, która identyfikuje ciągi identyfikatorów URI używane 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 aktywności, z której użytkownicy kopiują dane, skonfiguruj kod, aby kopiować dane do schowka. W odpowiedzi na żądanie skopiowania 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 dopasowywanie identyfikatorów URI i dodaj wzorzec identyfikatora URI, który pasuje 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 sposobu kodowania, ale wyświetlany jest tylko wzorzec 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 Wklej dane z identyfikatora URI treści opisujemy, jak uzyskać identyfikator URI treści ze 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ć następujące formy:

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

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

Aby skonfigurować aplikację do kopiowania strumienia danych od dostawcy, wykonaj te czynności:

  1. Skonfiguruj identyfikator URI treści dla strumienia danych, który umieszczasz w schowku. Możesz to zrobić na kilka sposobów, m.in.:
    • Zakoduj identyfikator strumienia danych w identyfikatorze URI zgodnie z opisem w sekcji Kodowanie identyfikatora w identyfikatorze URI, a następnie utwórz w swojej usłudze tabelę zawierającą identyfikatory i odpowiadające im nazwy strumieni.
    • 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ę, aby wskazywał inny strumień, gdy kopiujesz strumień do schowka za pomocą identyfikatora URI.
  2. Podaj typ MIME dla każdego rodzaju strumienia danych, który planujesz oferować. Aplikacje wklejające potrzebują tych informacji, aby określić, czy mogą wkleić dane ze schowka.
  3. Zaimplementuj jedną z ContentProvidermetod, 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, uzyskuje 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 której wklejasz dane, musi odczytać dane ze strumienia.

Poniżej znajdziesz listę najważniejszych metod deskryptora pliku dla dostawcy treści. Każda z nich ma odpowiednią metodę ContentResolver, do której nazwy dodano ciąg znaków „Descriptor”. Na przykład odpowiednikiem ContentResolver analogowym openAssetFile() jest openAssetFileDescriptor().

openTypedAssetFile()

Ta metoda zwraca deskryptor pliku zasobu, ale tylko wtedy, gdy podany typ MIME jest obsługiwany przez dostawcę. Wywołujący – aplikacja, która wkleja – podaje wzorzec typu MIME. Dostawca treści aplikacji, która kopiuje identyfikator URI do schowka, zwraca uchwyt pliku AssetFileDescriptor, jeśli może udostępnić ten typ MIME, a jeśli nie może, zgłasza wyjątek.

Ta metoda obsługuje podsekcje plików. Możesz jej używać do odczytywania komponentów, które dostawca treści skopiował 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()
Jest to bardziej ogólna forma openAssetFile(). Nie może odczytywać podsekcji plików.

Opcjonalnie możesz użyć metody openPipeHelper() z metodą deskryptora pliku. Dzięki temu aplikacja wklejająca może odczytywać dane strumieniowe w wątku w tle za pomocą potoku. 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 schowku może znajdować się tylko 1 klip. Nowa operacja kopiowania wykonana przez dowolną aplikację w systemie zastępuje poprzedni klip. Użytkownik może opuścić aplikację i skopiować coś innego, a potem wrócić. Nie możesz więc zakładać, że schowek zawiera klip, który użytkownik wcześniej skopiował w Twojej aplikacji.
  • Celem używania wielu obiektów ClipData.Item w jednym klipie jest umożliwienie kopiowania i wklejania wielu zaznaczeń, a nie różnych form odniesienia do jednego zaznaczenia. Zwykle chcesz, aby wszystkie obiekty w klipie miały tę samą formę.ClipData.Item Oznacza to, że wszystkie muszą być zwykłym tekstem, identyfikatorem URI treści lub Intent, a nie mieszanką tych typów.
  • Podczas przesyłania danych możesz oferować różne reprezentacje MIME. Dodaj obsługiwane typy MIME do elementu ClipDescription, a następnie zaimplementuj je w dostawcy treści.
  • Gdy pobierasz dane ze schowka, Twoja aplikacja jest odpowiedzialna za sprawdzenie dostępnych typów MIME, a następnie za podjęcie decyzji, którego z nich użyć (jeśli w ogóle). 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 przekształcić dane w schowku w tekst za pomocą metody coerceToText(). Jeśli aplikacja obsługuje więcej niż jeden z dostępnych typów MIME, możesz pozwolić użytkownikowi wybrać, którego z nich chce użyć.