Kopiuj i wklej

Android to zaawansowana platforma oparta na schowku do kopiowania i wklejania. Obsługuje proste i złożone typy danych, w tym ciągi tekstowe, złożone struktury danych, dane ze strumieni tekstowych i binarnych oraz zasoby aplikacji. Proste dane tekstowe są przechowywane bezpośrednio w schowku, a złożone jako odniesienia, które aplikacja wklejająca korzysta z usług dostawcy treści. Kopiowanie i wklejanie działa zarówno w obrębie aplikacji, jak i między aplikacjami, które implementują platformę.

Ponieważ część platformy korzysta z dostawców treści, w tym dokumencie zakładamy, że znamy interfejs Android Content Provider API, który został opisany w sekcji Dostawcy treści.

Użytkownicy oczekują opinii, gdy kopiują treści do schowka. Dlatego oprócz platformy umożliwiającej kopiowanie i wklejanie Android wyświetla użytkownikom domyślny interfejs podczas kopiowania w Androidzie 13 (poziom API 33) i nowszych. Ze względu na tę funkcję istnieje ryzyko, że powiadomienie zostanie zduplikowane. Więcej informacji o tym przypadku brzegowym znajdziesz w sekcji Unikanie zduplikowanych powiadomień.

Animacja pokazująca powiadomienie o schowku w Androidzie 13
Rysunek 1. Interfejs wyświetlany, gdy treść trafi do schowka na Androidzie 13 i nowszych.

Podczas kopiowania na Androidzie 12L (poziom API 32) i starszych wersjach ręcznie przekazuj opinie użytkownikom. Zapoznaj się z zaleceniami na ten temat w tym dokumencie.

Platforma schowka

Jeśli używasz struktury schowka, umieść dane w obiekcie klipsa, a potem umieść go w schowku dostępnym dla całego systemu. Obiekt klipu może mieć jeden z 3 postaci:

Tekst
Ciąg tekstowy. Umieść ciąg bezpośrednio w obiekcie przycinania, który następnie umieszczasz w schowku. Aby wkleić ciąg, pobierz obiekt klipu ze schowka i skopiuj go do pamięci aplikacji.
URI
Obiekt Uri reprezentujący dowolną formę identyfikatora URI. Służy on głównie do kopiowania złożonych danych od dostawcy treści. Aby skopiować dane, umieść obiekt Uri w obiekcie przycinania i umieść go w schowku. Aby wkleić dane, pobierz obiekt clip, pobierz obiekt Uri, przenieś go do źródła danych, takiego jak dostawca treści, i skopiuj dane ze źródła do pamięci aplikacji.
Podobne zamiary
Intent. Umożliwia to kopiowanie skrótów aplikacji. Aby skopiować dane, utwórz obiekt Intent, umieść go w obiekcie przycinania i umieść go w schowku. Aby wkleić dane, pobierz obiekt clip, a następnie skopiuj obiekt Intent do obszaru pamięci aplikacji.

W schowku jest tylko jeden obiekt klipu. Gdy aplikacja umieści obiekt klipu w schowku, poprzedni obiekt klipu zniknie.

Jeśli chcesz umożliwić użytkownikom wklejanie danych do aplikacji, nie musisz obsługiwać wszystkich typów danych. Zanim umożliwisz użytkownikom wklejenie danych, możesz przejrzeć te dane w schowku. Obiekt clip nie tylko zawiera określony formularz danych, ale zawiera również metadane informujące, jakie typy MIME są dostępne. Te metadane pomagają określić, czy aplikacja może wykorzystać dane ze schowka. Jeśli na przykład masz aplikację, która obsługuje głównie tekst, możesz zignorować obiekty przycinania, które zawierają identyfikator URI lub intencję.

Możesz też zezwolić użytkownikom na wklejanie tekstu niezależnie od rodzaju danych w schowku. Aby to zrobić, wymuś dane ze schowka w formie tekstowej, a następnie wklej ten tekst. Dokładniejsze informacje znajdziesz w sekcji Przekształcanie schowka w tekst.

Klasy schowka

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

Menedżer schowka

Schowek systemowy Androida jest reprezentowany przez globalną klasę ClipboardManager. Nie twórz bezpośrednio instancji tej klasy. Zamiast tego możesz uzyskać do niego odwołanie, wywołując getSystemService(CLIPBOARD_SERVICE).

ClipData, ClipData.Item i ClipDescription

Aby dodać dane do schowka, utwórz obiekt ClipData zawierający opis danych i samych danych. W schowku jest tylko 1 ClipData naraz. ClipData zawiera obiekt ClipDescription i co najmniej 1 obiekt ClipData.Item.

Obiekt ClipDescription zawiera metadane dotyczące klipu. Zawiera on w szczególności tablicę dostępnych typów MIME danych klipu. Dodatkowo w Androidzie 12 (poziom interfejsu API 31) i nowszych metadane zawierają informacje o tym, czy obiekt zawiera stylizowany tekst, oraz o typie tekstu w obiekcie. Gdy umieścisz klip w schowku, informacje te będą dostępne dla aplikacji do wklejania, które mogą sprawdzać, czy obsługują dane klipu.

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

Tekst
CharSequence.
URI
Uri. Zwykle zawiera on identyfikator URI dostawcy treści, ale dozwolony jest dowolny identyfikator URI. Aplikacja dostarczająca dane umieszcza identyfikator URI w schowku. Aplikacje, które chcą wkleić dane, pozyskują identyfikator URI ze schowka i mogą za jego pomocą uzyskać dostęp do dostawcy treści lub innego źródła danych oraz pobrać dane.
Podobne zamiary
Intent. Ten typ danych umożliwia skopiowanie skrótu do aplikacji do schowka. Użytkownicy mogą wkleić skrót do aplikacji, aby użyć go później.

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

Przydatne metody ClipData

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

newPlainText(label, text)
Zwraca obiekt ClipData, którego pojedynczy 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 funkcji 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 Uri.getScheme() zwraca wartość content: – metoda używa obiektu ContentResolver podanego w resolver do pobrania dostępnych typów MIME od dostawcy treści. Następnie zapisuje je w: ClipDescription. W przypadku identyfikatora URI, który nie jest identyfikatorem URI content:, metoda ustawia typ MIME na MIMETYPE_TEXT_URILIST.

Użyj newUri(), aby utworzyć klip na podstawie identyfikatora URI, a w szczególności 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 polecenia newIntent(), aby utworzyć klip z obiektu Intent.

Wymuszanie danych ze schowka na tekst

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

Ta metoda przekształca dane z tabeli ClipData.Item w tekst i zwraca CharSequence. Wartość zwracana przez ClipData.Item.coerceToText() zależy od postaci danych w funkcji ClipData.Item:

Tekst
Jeśli ClipData.Item to tekst, czyli jeśli getText() nie ma wartości null, coerceToText() zwraca tekst.
URI
Jeśli ClipData.Item to identyfikator URI, czyli getUri() nie ma wartości null, coerceToText() spróbuje użyć go jako identyfikatora URI treści.
  • Jeśli identyfikator URI jest identyfikatorem URI treści, a dostawca może zwrócić strumień tekstu, coerceToText() zwraca strumień tekstu.
  • Jeśli identyfikator URI jest identyfikatorem URI treści, ale dostawca nie udostępnia strumienia tekstu, coerceToText() zwraca reprezentację identyfikatora URI. Reprezentacja jest taka sama jak zwracana przez funkcję Uri.toString().
  • Jeśli identyfikator URI nie jest identyfikatorem URI treści, coerceToText() zwraca reprezentację tego identyfikatora. Reprezentacja jest taka sama jak zwracana przez funkcję Uri.toString().
Podobne zamiary
Jeśli ClipData.Item to obiekt Intent, czyli jeśli getIntent() nie ma wartości null, coerceToText() przekształca go w identyfikator URI intencji i zwraca go. Reprezentacja jest taka sama jak zwracana przez funkcję Intent.toUri(URI_INTENT_SCHEME).

Podsumowanie struktury schowka podsumowano na rys. 2. Aby skopiować dane, aplikacja umieszcza obiekt ClipData w globalnym schowku ClipboardManager. ClipData zawiera co najmniej 1 obiekt ClipData.Item i 1 obiekt ClipDescription. Aby wkleić dane, aplikacja pobiera ClipData, pobiera swój typ MIME z serwera ClipDescription i pobiera dane z ClipData.Item lub od dostawcy treści wskazanego przez ClipData.Item.

Obraz przedstawiający schemat blokowy platformy kopiowania i wklejania
Rysunek 2. Platforma schowka Androida.

Skopiuj do schowka

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

  1. Jeśli kopiujesz dane, korzystając z 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 przez kodowanie identyfikatora rekordu w identyfikatorze URI treści dla dostawcy. Tę metodę znajdziesz bardziej szczegółowo w sekcji o kodowaniu identyfikatora URI.

      Kotlin

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      const val CONTACTS = "content://com.example.contacts"
      
      // Declares a path string for URIs, used to copy data.
      const val COPY_PATH = "/copy"
      
      // Declares the Uri to paste to the clipboard.
      val copyUri: Uri = Uri.parse("$CONTACTS$COPY_PATH/$lastName")
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      val clip: ClipData = ClipData.newUri(contentResolver, "URI", copyUri)
      

      Java

      // Creates a Uri using a base Uri and a record ID based on the contact's last
      // name. Declares the base URI string.
      private static final String CONTACTS = "content://com.example.contacts";
      
      // Declares a path string for URIs, used to copy data.
      private static final String COPY_PATH = "/copy";
      
      // Declares the Uri to paste to the clipboard.
      Uri copyUri = Uri.parse(CONTACTS + COPY_PATH + "/" + lastName);
      ...
      // Creates a new URI clip object. The system uses the anonymous
      // getContentResolver() object to get MIME types from provider. The clip object's
      // label is "URI", and its data is the Uri previously created.
      ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
      
    • Dla intencji

      Ten fragment kodu tworzy obiekt Intent na potrzeby aplikacji, a potem umieszcza go w obiekcie klipu:

      Kotlin

      // Creates the Intent.
      val appIntent = Intent(this, com.example.demo.myapplication::class.java)
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      val clip: ClipData = ClipData.newIntent("Intent", appIntent)
      

      Java

      // Creates the Intent.
      Intent appIntent = new Intent(this, com.example.demo.myapplication.class);
      ...
      // Creates a clip object with the Intent in it. Its label is "Intent"
      // and its data is the Intent object created previously.
      ClipData clip = ClipData.newIntent("Intent", appIntent);
      
  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 treść do schowka. Na urządzeniach z Androidem 13 i nowszymi wersjami jest ona stosowana automatycznie, ale w poprzednich wersjach trzeba ją zaimplementować ręcznie.

Od Androida 13 po dodaniu treści do schowka system wyświetla standardowe potwierdzenie wizualne. Nowe potwierdzenie ma następujące funkcje:

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

Animacja pokazująca powiadomienie o schowku w Androidzie 13
Rysunek 3. Interfejs wyświetlany, gdy treść trafi do schowka na Androidzie 13 i nowszych.

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

Unikanie powielania powiadomień

Na Androidzie 12L (poziom interfejsu API 32) i starszych zalecamy powiadamianie użytkowników o operacji kopiowania. Aby to zrobić, wyślij wizualną opinię w aplikacji z użyciem widżetu takiego jak Toast lub Snackbar po skopiowaniu.

Aby uniknąć powielania informacji, na Androidzie 13 i nowszych zalecamy usunięcie paska powiadomień i komunikatów w aplikacji.

Opublikuj pasek powiadomień po tekście w aplikacji.
Rysunek 4. Jeśli w Androidzie 13 wyświetlisz pasek powiadomień z potwierdzeniem skopiowania, użytkownik zobaczy zduplikowane wiadomości.
Opublikuj w taki sposób toast po treści w aplikacji.
Rysunek 5. Jeśli w Androidzie 13 wyświetli się komunikat z potwierdzeniem skopiowania, 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 do schowka treści poufne

Jeśli Twoja aplikacja umożliwia użytkownikom kopiowanie do schowka poufnych treści, takich jak hasła czy dane kart kredytowych, musisz dodać flagę do ClipDescription w dyrektywie ClipData przed wywołaniem metody ClipboardManager.setPrimaryClip(). Dodanie tego oznaczenia zapobiega wyświetlaniu treści poufnych w wizualnym potwierdzeniu skopiowania treści na Androidzie 13 i nowszych.

Podgląd tekstu został skopiowany bez zgłaszania treści poufnych
Rysunek 6. Podgląd tekstu został skopiowany bez oznaczenia treści poufnych.
Podgląd tekstu został skopiowany i oznacza treści poufne.
Rysunek 7. Podgląd tekstu został skopiowany z flagą treści poufnych.

Aby oznaczyć treści poufne, dodaj do ClipDescription dodatkową wartość logiczną. Muszą to robić wszystkie aplikacje, niezależnie od docelowego poziomu interfejsu API.

// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
    }
}

// If your app is compiled with a lower SDK.
clipData.apply {
    description.extras = PersistableBundle().apply {
        putBoolean("android.content.extra.IS_SENSITIVE", true)
    }
}

Wklej ze schowka

Jak pisaliśmy wcześniej, dane ze schowka wklej w postaci globalnego obiektu schowka, pobierz obiekt clip, zapoznaj się z jego danymi i – jeśli to możliwe – skopiuj je z obiektu do przechowywania. W tej sekcji wyjaśniamy szczegółowo, jak wkleić 3 rodzaje danych ze schowka.

Wklej zwykły tekst

Aby wkleić zwykły tekst, użyj schowka globalnego i sprawdź, czy może zwracać zwykły tekst. Następnie pobierz obiekt clip i skopiuj jego tekst do własnej pamięci za pomocą getText() zgodnie z tą procedurą:

  1. Pobierz globalny obiekt ClipboardManager za pomocą getSystemService(CLIPBOARD_SERVICE). Dodatkowo zadeklaruj zmienną globalną, która będzie zawierać wklejony tekst:

    Kotlin

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

    Java

    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    String pasteData = "";
    
  2. Określ, czy musisz włączyć, czy wyłączyć opcję „wklej” w bieżącym działaniu. Sprawdź, czy schowek zawiera klip i czy możesz obsłużyć typ danych reprezentowanych przez ten klip:

    Kotlin

    // Gets the ID of the "paste" menu item.
    val pasteItem: MenuItem = menu.findItem(R.id.menu_paste)
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    pasteItem.isEnabled = when {
        !clipboard.hasPrimaryClip() -> {
            false
        }
        !(clipboard.primaryClipDescription.hasMimeType(MIMETYPE_TEXT_PLAIN)) -> {
            // Disables the paste menu item, since the clipboard has data but it
            // isn't plain text.
            false
        }
        else -> {
            // Enables the paste menu item, since the clipboard contains plain text.
            true
        }
    }
    

    Java

    // Gets the ID of the "paste" menu item.
    MenuItem pasteItem = menu.findItem(R.id.menu_paste);
    
    // If the clipboard doesn't contain data, disable the paste menu item.
    // If it does contain data, decide whether you can handle the data.
    if (!(clipboard.hasPrimaryClip())) {
    
        pasteItem.setEnabled(false);
    
    } else if (!(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN))) {
    
        // Disables the paste menu item, since the clipboard has data but
        // it isn't plain text.
        pasteItem.setEnabled(false);
    } else {
    
        // Enables the paste menu item, since the clipboard contains plain text.
        pasteItem.setEnabled(true);
    }
    
  3. Skopiuj dane ze schowka. Ten punkt kodu jest dostępny tylko wtedy, gdy w menu jest włączona opcja „Wklej”, więc możesz założyć, że schowek zawiera zwykły tekst. Nie wiesz jeszcze, czy zawiera ona ciąg tekstowy czy identyfikator URI wskazujący zwykły tekst. Poniższy fragment kodu pozwala przetestować tę funkcję, ale pokazuje tylko kod do obsługi zwykłego tekstu:

    Kotlin

    when (menuItem.itemId) {
        ...
        R.id.menu_paste -> {    // Responds to the user selecting "paste".
            // Examines the item on the clipboard. If getText() doesn't return null,
            // the clip item contains the text. Assumes that this application can only
            // handle one item at a time.
            val item = clipboard.primaryClip.getItemAt(0)
    
            // Gets the clipboard as text.
            pasteData = item.text
    
            return if (pasteData != null) {
                // If the string contains data, then the paste operation is done.
                true
            } else {
                // The clipboard doesn't contain text. If it contains a URI,
                // attempts to get data from it.
                val pasteUri: Uri? = item.uri
    
                if (pasteUri != null) {
                    // If the URI contains something, try to get text from it.
    
                    // Calls a routine to resolve the URI and get data from it.
                    // This routine isn't presented here.
                    pasteData = resolveUri(pasteUri)
                    true
                } else {
    
                    // Something is wrong. The MIME type was plain text, but the
                    // clipboard doesn't contain text or a Uri. Report an error.
                    Log.e(TAG,"Clipboard contains an invalid data type")
                    false
                }
            }
        }
    }
    

    Java

    // Responds to the user selecting "paste".
    case R.id.menu_paste:
    
    // Examines the item on the clipboard. If getText() does not return null,
    // the clip item contains the text. Assumes that this application can only
    // handle one item at a time.
     ClipData.Item item = clipboard.getPrimaryClip().getItemAt(0);
    
    // Gets the clipboard as text.
    pasteData = item.getText();
    
    // If the string contains data, then the paste operation is done.
    if (pasteData != null) {
        return true;
    
    // The clipboard doesn't contain text. If it contains a URI, attempts to get
    // data from it.
    } else {
        Uri pasteUri = item.getUri();
    
        // If the URI contains something, try to get text from it.
        if (pasteUri != null) {
    
            // Calls a routine to resolve the URI and get data from it.
            // This routine isn't presented here.
            pasteData = resolveUri(Uri);
            return true;
        } else {
    
            // Something is wrong. The MIME type is plain text, but the
            // clipboard doesn't contain text or a Uri. Report an error.
            Log.e(TAG, "Clipboard contains an invalid data type");
            return false;
        }
    }
    

Wklej dane z identyfikatora URI treści

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

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

  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ż narzędzie do rozpoznawania treści, które pozwoli Ci uzyskać dostęp do dostawcy treści:

    Kotlin

    // Gets a handle to the Clipboard Manager.
    val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    
    // Gets a content resolver instance.
    val cr = contentResolver
    

    Java

    // Gets a handle to the Clipboard Manager.
    ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
    
    // Gets a content resolver instance.
    ContentResolver cr = getContentResolver();
    
  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 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 rozpoznawany przez aplikację. 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();
                 }
             }
         }
    }
    

Wklejanie intencji

Aby wkleić intencję, najpierw pobierz globalny schowek. Sprawdź obiekt ClipData.Item, czy zawiera obiekt Intent. Następnie wywołaj getIntent(), aby skopiować intencję do własnego pamięci. Można to zademonstrować za pomocą tego fragmentu kodu:

Kotlin

// Gets a handle to the Clipboard Manager.
val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager

// Checks whether the clip item contains an Intent by testing whether
// getIntent() returns null.
val pasteIntent: Intent? = clipboard.primaryClip?.getItemAt(0)?.intent

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Java

// Gets a handle to the Clipboard Manager.
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);

// Checks whether the clip item contains an Intent, by testing whether
// getIntent() returns null.
Intent pasteIntent = clipboard.getPrimaryClip().getItemAt(0).getIntent();

if (pasteIntent != null) {

    // Handle the Intent.

} else {

    // Ignore the clipboard, or issue an error if
    // you expect an Intent to be on the clipboard.
}

Powiadomienie systemowe wyświetlane, gdy aplikacja uzyskuje dostęp do danych ze schowka

Na Androidzie 12 (poziom interfejsu API 31) i nowszych system zwykle wyświetla toast, gdy aplikacja wywołuje metodę getPrimaryClip(). Tekst wiadomości ma następujący format:

APP pasted from your clipboard

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

  • Uzyskuje dostęp do ClipData z Twojej aplikacji.
  • Wielokrotne korzystanie z ClipData z określonej aplikacji. Ten komunikat wyświetla się tylko wtedy, gdy aplikacja po raz pierwszy uzyskuje dostęp do jej danych.
  • Pobiera metadane obiektu klipu, np. wywołując getPrimaryClipDescription() zamiast getPrimaryClip().

Korzystanie z usług dostawców treści w celu kopiowania złożonych danych

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

Aplikacja do wklejania ma tylko identyfikator URI treści Twoich danych, dlatego musi wiedzieć, który fragment danych pobrać. Informacje te możesz podać, zakodując identyfikator danych w samym identyfikatorze URI, lub możesz podać unikalny identyfikator URI, który zwraca dane, które chcesz skopiować. Wybór metody zależy od organizacji danych.

W kolejnych sekcjach opisano, jak skonfigurować identyfikatory URI, podać złożone dane i podać strumienie plików. W opisach zakładamy, że znasz ogólne zasady projektowania dostawców treści.

Zakoduj identyfikator w identyfikatorze URI

Przydatną metodą kopiowania danych do schowka za pomocą identyfikatora URI jest zakodowanie identyfikatora danych w samym identyfikatorze URI. Dostawca treści może pobrać identyfikator z identyfikatora URI i użyć go do pobrania danych. Aplikacja do wklejania nie musi wiedzieć, że identyfikator istnieje. Musi tylko pobrać ze schowka Twój „dokument referencyjny” (identyfikator URI oraz identyfikator), przekazać je dostawcy treści i odzyskać dane.

Identyfikator URI treści zwykle koduje się na końcu identyfikatora URI treści. Załóżmy na przykład, że określisz identyfikator URI dostawcy jako następujący ciąg:

"content://com.example.contacts"

Jeśli chcesz zakodować nazwę w tym identyfikatorze URI, użyj tego fragmentu kodu:

Kotlin

val uriString = "content://com.example.contacts/Smith"

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
val copyUri = Uri.parse(uriString)

Java

String uriString = "content://com.example.contacts" + "/" + "Smith";

// uriString now contains content://com.example.contacts/Smith.

// Generates a uri object from the string representation.
Uri copyUri = Uri.parse(uriString);

Jeśli korzystasz już z dostawcy treści, możesz dodać nową ścieżkę identyfikatora URI, która będzie wskazywać, że identyfikator URI jest przeznaczony do kopiowania. Załóżmy na przykład, że masz już te ścieżki identyfikatorów URI:

"content://com.example.contacts/people"
"content://com.example.contacts/people/detail"
"content://com.example.contacts/people/images"

Możesz dodać inną ścieżkę do kopiowania identyfikatorów URI:

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

Następnie możesz wykryć identyfikator URI „kopiuj” przez dopasowanie do wzorca i przetworzyć go za pomocą kodu przeznaczonego do kopiowania i wklejania.

Tę metodę kodowania zwykle używasz wtedy, gdy do porządkowania danych korzystasz już z dostawcy treści, wewnętrznej bazy danych lub wewnętrznej tabeli. W takich przypadkach mamy wiele danych, które chcesz skopiować, i przypuszczalnie każdy z nich ma unikalny identyfikator. W odpowiedzi na zapytanie z aplikacji do wklejania możesz wyszukać dane według ich identyfikatora i zwrócić je.

Jeśli nie masz wielu danych, prawdopodobnie nie musisz kodować identyfikatora. Możesz użyć identyfikatora URI, który jest unikalny dla Twojego dostawcy. W odpowiedzi na zapytanie dostawca zwraca dane, które obecnie zawiera.

Kopiowanie struktur danych

Skonfiguruj dostawcę treści do kopiowania i wklejania złożonych danych jako podklasy komponentu ContentProvider. Zakoduj identyfikator URI umieszczony w schowku tak, aby kierował on dokładnie do rekordu, który chcesz podać. Weź też pod uwagę obecny stan aplikacji:

  • Jeśli masz już dostawcę treści, możesz dodać do niego dodatkowe funkcje. Być może wystarczy zmodyfikować jego metodę query(), aby obsługiwać identyfikatory URI pochodzące z aplikacji, które chcą wkleić dane. Prawdopodobnie zechcesz zmodyfikować metodę tak, aby obsługiwała wzorzec „kopiowania” identyfikatora URI.
  • Jeśli aplikacja utrzymuje wewnętrzną bazę danych, możesz ją przenieść do dostawcy treści, aby ułatwić kopiowanie z niej.
  • Jeśli nie korzystasz z bazy danych, możesz wdrożyć prostego dostawcę treści, którego jedynym celem jest udostępnianie danych aplikacjom wklejanym ze schowka.

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

query()
Podczas wklejania aplikacji zakłada się, że mogą uzyskiwać Twoje dane, korzystając z tej metody z identyfikatorem URI umieszczonym w schowku. Aby umożliwić kopiowanie, dopilnuj, aby ta metoda wykrywała identyfikatory URI zawierające specjalną ścieżkę „copy”. Aplikacja może następnie utworzyć identyfikator URI „copy” umieszczony w schowku zawierający ścieżkę kopiowania i wskaźnik do dokładnego rekordu, który chcesz skopiować.
getType()
Ta metoda musi zwracać typy MIME danych, które chcesz skopiować. Metoda newUri() wywołuje metodę getType(), aby umieścić typy MIME w nowym obiekcie ClipData.

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

Nie musisz używać żadnych innych metod dostawcy treści, takich jak insert() czy update(). Aplikacja do wklejania wymaga jedynie obsługiwanych typów MIME i kopiowania danych od dostawcy. Jeśli masz już te metody, nie będą one zakłócać operacji kopiowania.

Poniższe fragmenty kodu pokazują, jak skonfigurować aplikację, aby kopiować złożone dane:

  1. W stałych globalnych swojej aplikacji zadeklaruj podstawowy ciąg identyfikatora URI i ścieżkę identyfikującą ciągi URI, których używasz do kopiowania danych. Zadeklaruj też typ MIME dla kopiowanych danych.

    Kotlin

    // Declares the base URI string.
    private const val CONTACTS = "content://com.example.contacts"
    
    // Declares a path string for URIs that you use to copy data.
    private const val COPY_PATH = "/copy"
    
    // Declares a MIME type for the copied data.
    const val MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact"
    

    Java

    // Declares the base URI string.
    private static final String CONTACTS = "content://com.example.contacts";
    
    // Declares a path string for URIs that you use to copy data.
    private static final String COPY_PATH = "/copy";
    
    // Declares a MIME type for the copied data.
    public static final String MIME_TYPE_CONTACT = "vnd.android.cursor.item/vnd.example.contact";
    
  2. W aktywności, z której użytkownicy kopiują dane, skonfiguruj kod do kopiowania danych do schowka. W odpowiedzi na żądanie kopiowania umieść identyfikator URI w schowku.

    Kotlin

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

    Java

    public class MyCopyActivity extends Activity {
        ...
    // The user has selected a name and is requesting a copy.
    case R.id.menu_copy:
    
        // Appends the last name to the base URI.
        // The name is stored in "lastName".
        uriString = CONTACTS + COPY_PATH + "/" + lastName;
    
        // Parses the string into a URI.
        Uri copyUri = Uri.parse(uriString);
    
        // Gets a handle to the clipboard service.
        ClipboardManager clipboard = (ClipboardManager)
            getSystemService(Context.CLIPBOARD_SERVICE);
    
        ClipData clip = ClipData.newUri(getContentResolver(), "URI", copyUri);
    
        // Sets the clipboard's primary clip.
        clipboard.setPrimaryClip(clip);
    
  3. W zakresie globalnym dostawcy treści utwórz dopasowanie identyfikatorów URI i dodaj wzorzec URI odpowiadający identyfikatorom URI umieszczonym w schowku.

    Kotlin

    // A Uri Match object that simplifies matching content URIs to patterns.
    private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    
        // Adds a matcher for the content URI. It matches.
        // "content://com.example.contacts/copy/*"
        addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT)
    }
    
    // An integer to use in switching based on the incoming URI pattern.
    private const val GET_SINGLE_CONTACT = 0
    ...
    class MyCopyProvider : ContentProvider() {
        ...
    }
    

    Java

    public class MyCopyProvider extends ContentProvider {
        ...
    // A Uri Match object that simplifies matching content URIs to patterns.
    private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    
    // An integer to use in switching based on the incoming URI pattern.
    private static final int GET_SINGLE_CONTACT = 0;
    ...
    // Adds a matcher for the content URI. It matches
    // "content://com.example.contacts/copy/*"
    sUriMatcher.addURI(CONTACTS, "names/*", GET_SINGLE_CONTACT);
    
  4. Skonfiguruj metodę query(). Ta metoda może obsługiwać różne wzorce identyfikatorów URI w zależności od kodu, ale widoczny jest tylko wzorzec operacji kopiowania schowka.

    Kotlin

    // Sets up your provider's query() method.
    override fun query(
            uri: Uri,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        ...
        // When based on the incoming content URI:
        when(sUriMatcher.match(uri)) {
    
            GET_SINGLE_CONTACT -> {
    
                // Queries and returns the contact for the requested name. Decodes
                // the incoming URI, queries the data model based on the last name,
                // and returns the result as a Cursor.
            }
        }
        ...
    }
    

    Java

    // Sets up your provider's query() method.
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
        String sortOrder) {
        ...
        // Switch based on the incoming content URI.
        switch (sUriMatcher.match(uri)) {
    
        case GET_SINGLE_CONTACT:
    
            // Queries and returns the contact for the requested name. Decodes the
            // incoming URI, queries the data model based on the last name, and
            // returns the result as a Cursor.
        ...
    }
    
  5. Skonfiguruj metodę getType(), aby zwracać odpowiedni typ MIME w przypadku skopiowanych danych:

    Kotlin

    // Sets up your provider's getType() method.
    override fun getType(uri: Uri): String? {
        ...
        return when(sUriMatcher.match(uri)) {
            GET_SINGLE_CONTACT -> MIME_TYPE_CONTACT
            ...
        }
    }
    

    Java

    // Sets up your provider's getType() method.
    public String getType(Uri uri) {
        ...
        switch (sUriMatcher.match(uri)) {
        case GET_SINGLE_CONTACT:
            return (MIME_TYPE_CONTACT);
        ...
        }
    }
    

W sekcji Wklejanie danych z identyfikatora URI treści opisano, jak pobrać ze schowka identyfikator URI treści i za jego pomocą pobierać i wklejać dane.

Kopiowanie strumieni danych

Możesz kopiować i wklejać duże ilości danych tekstowych i binarnych w postaci strumieni. Dane mogą mieć następujące formularze:

  • Pliki przechowywane na konkretnym urządzeniu
  • Przesyłanie strumieniowe z gniazdek
  • Duże ilości danych przechowywanych w bazie danych dostawcy

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

Aby skonfigurować w aplikacji kopiowanie strumienia danych u dostawcy, wykonaj te czynności:

  1. Skonfiguruj identyfikator URI treści dla strumienia danych umieszczanego w schowku. Możesz to zrobić na kilka sposobów:
    • Zakoduj identyfikator strumienia danych w identyfikatorze URI zgodnie z opisem w sekcji Zakoduj identyfikator w identyfikatorze URI, a następnie utwórz u dostawcy tabelę zawierającą identyfikatory i odpowiednią nazwę strumienia.
    • Zakoduj nazwę strumienia bezpośrednio w identyfikatorze URI.
    • Użyj unikalnego identyfikatora URI, który zawsze zwraca bieżący strumień od dostawcy. Jeśli korzystasz z tej opcji, pamiętaj, aby zaktualizować dostawcę tak, aby wskazywał inny strumień za każdym razem, gdy kopiujesz strumień do schowka za pomocą identyfikatora URI.
  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 w schowku.
  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ń należy 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. Za odczytanie danych ze strumienia odpowiada aplikacja wklejająca.

Na liście poniżej znajdziesz najważniejsze metody deskryptora plików dla dostawcy treści. Każdy z nich ma odpowiadającą jej metodę ContentResolver z ciągiem znaków „Descriptor” dołączonym do nazwy metody. Na przykład ContentResolver analog danych openAssetFile() to openAssetFileDescriptor().

openTypedAssetFile()

Ta metoda zwraca deskryptor pliku zasobu, ale tylko wtedy, gdy podany typ MIME jest obsługiwany przez dostawcę. Element wywołujący – aplikacja wykonująca wklejanie – udostępnia wzorzec typu MIME. Dostawca treści aplikacji, która kopiuje identyfikator URI do schowka, zwraca uchwyt pliku AssetFileDescriptor, jeśli może podać ten typ MIME, a w razie potrzeby tworzy wyjątek.

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

openAssetFile()
Jest to bardziej ogólna forma openTypedAssetFile(). Nie filtruje dozwolonych typów MIME, ale może odczytywać podsekcje plików.
openFile()
To bardziej ogólna forma openAssetFile(). Nie może odczytywać podsekcji plików.

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

Zaprojektuj skuteczną funkcję kopiowania i wklejania

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

  • W danym momencie w schowku jest tylko 1 klip. Nowa operacja kopiowania podejmowana przez dowolną aplikację w systemie zastępuje poprzedni klip. Użytkownik może wyjść z aplikacji i skopiować ją, zanim z niej wróci, dlatego nie możesz zakładać, że schowek zawiera klip, który został wcześniej skopiowany do Twojej aplikacji.
  • Używanie wielu obiektów ClipData.Item w jednym klipie ma na celu umożliwienie kopiowania i wklejania wielu wybranych elementów, a nie różnych form odniesień do pojedynczego wyboru. Zwykle wszystkie obiekty ClipData.Item w klipie mają mieć tę samą postać. Oznacza to, że muszą to być zwykły tekst, identyfikator URI treści lub atrybut Intent. Nie mogą one być mieszane.
  • Podając dane, możesz udostępnić różne reprezentacje MIME. Dodaj obsługiwane typy MIME do pola ClipDescription, a następnie zaimplementuj je u dostawcy treści.
  • Gdy pobierasz dane ze schowka, aplikacja odpowiada za sprawdzenie dostępnych typów MIME i wybór odpowiedniego, którego chcesz użyć. Nawet jeśli w schowku znajduje się klip, a użytkownik poprosi o wklejenie, Twoja aplikacja nie musi tego robić. Wklej, jeśli typ MIME jest zgodny. Możesz wymusić wymuszenie wyświetlania danych w schowku na tekst za pomocą funkcji coerceToText(). Jeśli Twoja aplikacja obsługuje kilka dostępnych typów MIME, możesz pozwolić użytkownikowi na wybór.