Android menyediakan framework berbasis papan klip yang canggih untuk penyalinan dan penempelan. Alat ini mendukung jenis data sederhana dan kompleks, termasuk string teks, struktur data kompleks, data aliran data teks dan biner, serta aset aplikasi. Data teks sederhana disimpan langsung di papan klip, sedangkan data yang kompleks disimpan sebagai referensi yang diselesaikan oleh aplikasi penempelan dengan penyedia konten. Fitur menyalin dan menempelkan berfungsi di dalam aplikasi dan di antara aplikasi yang menerapkan framework.
Karena bagian dari framework menggunakan penyedia konten, dokumen ini mengasumsikan Anda telah memahami Android Content Provider API, yang dijelaskan dalam Penyedia konten.
Pengguna mengharapkan masukan saat menyalin konten ke papan klip. Jadi, selain framework yang mendukung salin dan tempel, Android akan menampilkan UI default kepada pengguna saat menyalin di Android 13 (API level 33) dan yang lebih baru. Karena fitur ini, ada risiko notifikasi duplikat. Anda dapat mempelajari kasus ekstrem ini lebih lanjut di bagian Menghindari notifikasi duplikat.
![Animasi yang menampilkan notifikasi papan klip Android 13](https://developer.android.com/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=8&hl=id)
Memberikan masukan secara manual kepada pengguna saat menyalin di Android 12L (level API 32) dan yang lebih lama. Lihat rekomendasi untuk hal ini dalam dokumen ini.
Framework papan klip
Saat Anda menggunakan framework papan klip, tempatkan data ke dalam objek klip, lalu tempatkan objek klip di papan klip seluruh sistem. Objek klip dapat menggunakan salah satu dari tiga bentuk berikut:
- Teks
- String teks. Letakkan string langsung di objek klip, yang kemudian Anda tempatkan di papan klip. Untuk menempelkan string, dapatkan objek klip dari papan klip dan salin string ke dalam penyimpanan aplikasi Anda.
- URI
- Objek
Uri
yang merepresentasikan bentuk URI apa pun. Fungsi ini utamanya untuk menyalin data yang kompleks dari penyedia konten. Untuk menyalin data, tempatkan objekUri
ke dalam objek klip dan tempatkan objek klip ke papan klip. Untuk menempelkan data, dapatkan objek klip, dapatkan objekUri
, tentukan ke sumber data, seperti penyedia konten, dan salin data dari sumber ke dalam penyimpanan aplikasi Anda. - Intent
-
Intent
. Dukungan ini mendukung penyalinan pintasan aplikasi. Untuk menyalin data, buatIntent
, tempatkan dalam objek klip, lalu tempatkan objek klip di papan klip. Untuk menempelkan data, dapatkan objek klip, lalu salin objekIntent
ke area memori aplikasi Anda.
Papan klip hanya menyimpan satu objek klip dalam satu waktu. Saat aplikasi menempatkan objek klip di papan klip, objek klip sebelumnya akan hilang.
Jika ingin mengizinkan pengguna menempelkan data ke aplikasi, Anda tidak perlu menangani semua jenis data. Anda dapat memeriksa data di papan klip sebelum memberikan opsi kepada pengguna untuk menempelkannya. Selain memiliki bentuk data tertentu, objek klip juga berisi metadata yang memberi tahu Anda jenis MIME yang tersedia. Metadata ini membantu Anda memutuskan apakah aplikasi dapat melakukan sesuatu yang berguna dengan data papan klip. Misalnya, jika Anda memiliki aplikasi yang terutama menangani teks, Anda mungkin ingin mengabaikan objek klip yang berisi URI atau intent.
Anda juga dapat mengizinkan pengguna menempelkan teks, apa pun bentuk datanya di papan klip. Untuk melakukannya, paksa data papan klip ke dalam representasi teks, lalu tempel teks tersebut. Hal ini dijelaskan di bagian Memaksa papan klip ke teks.
Class papan klip
Bagian ini menjelaskan class yang digunakan oleh framework papan klip.
ClipboardManager
Clipboard sistem Android direpresentasikan oleh class
ClipboardManager
global.
Jangan membuat instance class ini secara langsung. Sebagai gantinya, dapatkan referensi ke sana dengan memanggil
getSystemService(CLIPBOARD_SERVICE)
.
ClipData, ClipData.Item, dan ClipDescription
Untuk menambahkan data ke papan klip, buat
objek ClipData
yang berisi
deskripsi data dan data itu sendiri. Papan klip menyimpan satu ClipData
dalam satu waktu. ClipData
berisi
objek ClipDescription
dan satu atau beberapa
objek ClipData.Item
.
Objek ClipDescription
berisi metadata tentang klip. Secara khusus, objek ini
berisi array jenis MIME yang tersedia untuk data klip. Selain itu, pada
Android 12 (level API 31) dan yang lebih tinggi, metadata menyertakan informasi tentang apakah objek
berisi
teks bergaya dan tentang
jenis teks dalam objek.
Saat Anda meletakkan klip di papan klip, informasi ini akan tersedia untuk menempelkan aplikasi, yang
dapat memeriksa apakah aplikasi tersebut dapat menangani data klip.
Objek ClipData.Item
berisi data teks, URI, atau intent:
- Teks
-
CharSequence
. - URI
-
Uri
. Kolom ini biasanya berisi URI penyedia konten, meskipun URI apa pun diizinkan. Aplikasi yang menyediakan data menempatkan URI pada papan klip. Aplikasi yang ingin menempelkan data akan mendapatkan URI dari papan klip dan menggunakannya untuk mengakses penyedia konten atau sumber data lainnya dan mengambil data. - Intent
-
Intent
. Jenis data ini memungkinkan Anda menyalin pintasan aplikasi ke papan klip. Pengguna kemudian dapat menempelkan pintasan ke dalam aplikasi mereka untuk digunakan nanti.
Anda dapat menambahkan lebih dari satu objek ClipData.Item
ke klip. Hal ini memungkinkan pengguna menyalin dan
menempel beberapa pilihan sebagai satu klip. Misalnya, jika Anda memiliki widget daftar yang memungkinkan
pengguna memilih lebih dari satu item sekaligus, Anda dapat menyalin semua item ke papan klip sekaligus. Caranya, buat ClipData.Item
terpisah untuk setiap item daftar, lalu tambahkan objek ClipData.Item
ke objek ClipData
.
Metode kemudahan ClipData
Class ClipData
menyediakan metode praktis statis untuk membuat
objek ClipData
dengan satu objek ClipData.Item
dan
objek ClipDescription
sederhana:
-
newPlainText(label, text)
- Menampilkan objek
ClipData
yang objekClipData.Item
tunggalnya berisi string teks. Label objekClipDescription
ditetapkan kelabel
. Jenis MIME tunggal dalamClipDescription
adalahMIMETYPE_TEXT_PLAIN
.Gunakan
newPlainText()
untuk membuat klip dari string teks. -
newUri(resolver, label, URI)
- Menampilkan objek
ClipData
yang objekClipData.Item
tunggalnya berisi URI. Label objekClipDescription
ditetapkan kelabel
. Jika URI-nya adalah URI konten—yaitu, jikaUri.getScheme()
menampilkancontent:
—metode tersebut menggunakan objekContentResolver
yang disediakan diresolver
untuk mengambil jenis MIME yang tersedia dari penyedia konten. Kemudian, file tersebut disimpan diClipDescription
. Untuk URI yang bukan URIcontent:
, metode ini akan menetapkan jenis MIME keMIMETYPE_TEXT_URILIST
.Gunakan
newUri()
untuk membuat klip dari URI—khususnya URIcontent:
. -
newIntent(label, intent)
- Menampilkan objek
ClipData
yang objekClipData.Item
tunggalnya berisiIntent
. Label objekClipDescription
ditetapkan kelabel
. Jenis MIME ditetapkan keMIMETYPE_TEXT_INTENT
.Gunakan
newIntent()
untuk membuat klip dari objekIntent
.
Paksa data papan klip ke teks
Meskipun aplikasi hanya menangani teks, Anda dapat menyalin data non-teks dari papan klip dengan
mengonversinya menggunakan
metode
ClipData.Item.coerceToText()
.
Metode ini mengonversi data dalam ClipData.Item
menjadi teks dan menampilkan
CharSequence
. Nilai yang ditampilkan ClipData.Item.coerceToText()
didasarkan
pada bentuk data di ClipData.Item
:
- Teks
-
Jika
ClipData.Item
adalah teks—yaitu, jikagetText()
bukan null—coerceToText() akan menampilkan teks. - URI
-
Jika
ClipData.Item
adalah URI, yaitu jikagetUri()
bukan null—coerceToText()
akan mencoba menggunakannya sebagai URI konten.- Jika URI adalah URI konten dan penyedia dapat menampilkan aliran teks,
coerceToText()
akan menampilkan aliran teks. - Jika URI adalah URI konten, tetapi penyedia tidak menawarkan aliran teks,
coerceToText()
akan menampilkan representasi URI. Representasinya sama dengan yang ditampilkan olehUri.toString()
. - Jika URI bukan URI konten,
coerceToText()
akan menampilkan representasi URI. Representasinya sama dengan yang ditampilkan olehUri.toString()
.
- Jika URI adalah URI konten dan penyedia dapat menampilkan aliran teks,
- Intent
- Jika
ClipData.Item
adalahIntent
—yaitu, jikagetIntent()
bukan null—coerceToText()
akan mengonversinya menjadi URI Intent dan menampilkannya. Representasinya sama dengan yang ditampilkan olehIntent.toUri(URI_INTENT_SCHEME)
.
Framework papan klip dirangkum dalam gambar 2. Untuk menyalin data, aplikasi akan menempatkan
objek ClipData
pada papan klip global ClipboardManager
. ClipData
berisi satu atau beberapa objek ClipData.Item
dan satu
objek ClipDescription
. Untuk menempelkan data, aplikasi akan mendapatkan ClipData
,
mendapatkan jenis MIME dari ClipDescription
, dan mendapatkan data dari
ClipData.Item
atau dari penyedia konten yang dirujuk oleh
ClipData.Item
.
![Gambar yang menunjukkan diagram blok framework salin dan tempel](https://developer.android.com/static/images/ui/clipboard/copy_paste_framework.png?authuser=8&hl=id)
Salin ke clipboard
Untuk menyalin data ke papan klip, dapatkan handle untuk objek ClipboardManager
global,
buat objek ClipData
, lalu tambahkan ClipDescription
dan satu atau beberapa
objek ClipData.Item
ke dalamnya. Kemudian, tambahkan objek ClipData
yang sudah selesai ke
objek ClipboardManager
. Hal ini dijelaskan lebih lanjut dalam prosedur berikut:
- Jika Anda menyalin data menggunakan URI konten, siapkan penyedia konten.
- Dapatkan papan klip sistem:
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);
-
Salin data ke objek
ClipData
baru:-
Untuk teks
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!");
-
Untuk URI
Cuplikan ini menyusun URI dengan mengenkode ID catatan ke URI konten untuk penyedia. Teknik ini dibahas secara lebih mendetail di bagian Mengenkode ID pada 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);
-
Untuk intent
Cuplikan ini membuat
Intent
untuk aplikasi, lalu menempatkannya dalam objek klip: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);
-
Untuk teks
-
Tempatkan objek klip baru di papan klip:
Kotlin
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip)
Java
// Set the clipboard's primary clip. clipboard.setPrimaryClip(clip);
Memberikan masukan saat menyalin ke papan klip
Pengguna mengharapkan masukan visual saat aplikasi menyalin konten ke papan klip. Hal ini dilakukan secara otomatis bagi pengguna di Android 13 dan yang lebih baru, tetapi harus diterapkan secara manual di versi sebelumnya.
Mulai Android 13, sistem menampilkan konfirmasi visual standar saat konten ditambahkan ke papan klip. Konfirmasi baru akan melakukan hal berikut:
- Mengonfirmasi bahwa konten berhasil disalin.
- Memberikan pratinjau konten yang disalin.
![Animasi yang menampilkan notifikasi papan klip Android 13](https://developer.android.com/static/images/about/versions/13/new-copy-paste-UI.gif?authuser=8&hl=id)
Di Android 12L (level API 32) dan yang lebih lama, pengguna mungkin tidak yakin apakah mereka berhasil menyalin konten atau apa yang mereka salin. Fitur ini menstandarkan berbagai notifikasi yang ditampilkan oleh aplikasi setelah menyalin dan menawarkan kontrol yang lebih besar atas papan klip kepada pengguna.
Menghindari notifikasi duplikat
Di Android 12L (level API 32) dan yang lebih rendah, sebaiknya beri tahu pengguna saat mereka berhasil menyalin
dengan memberikan masukan visual dalam aplikasi, menggunakan widget seperti Toast
atau
Snackbar
, setelah menyalin.
Untuk menghindari duplikasi tampilan informasi, sebaiknya hapus toast atau snackbar yang ditampilkan setelah menyalin dalam aplikasi untuk Android 13 dan yang lebih baru.
![Menampilkan snackbar setelah menyalin dalam aplikasi.](https://developer.android.com/static/images/about/versions/13/snackbar-overlap.png?authuser=8&hl=id)
![Menampilkan toast setelah menyalin dalam aplikasi.](https://developer.android.com/static/images/about/versions/13/toast-overlap.png?authuser=8&hl=id)
Berikut ini contoh cara mengimplementasikannya:
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() }
Menambahkan konten sensitif ke papan klip
Jika aplikasi Anda mengizinkan pengguna menyalin konten sensitif ke papan klip, seperti sandi atau informasi
kartu kredit, Anda harus menambahkan tanda ke ClipDescription
di ClipData
sebelum memanggil ClipboardManager.setPrimaryClip()
. Menambahkan tanda ini akan mencegah konten
sensitif muncul dalam konfirmasi visual konten yang disalin di Android 13 dan yang lebih baru.
![Pratinjau teks yang disalin tanpa menambahkan flag pada konten sensitif](https://developer.android.com/static/images/about/versions/13/sensitive-content-before.png?authuser=8&hl=id)
![Pratinjau teks yang disalin dengan menambahkan flag pada konten sensitif.](https://developer.android.com/static/images/about/versions/13/sensitive-content-after.png?authuser=8&hl=id)
Untuk menambahkan flag pada konten sensitif, sertakan tambahan boolean ke ClipDescription
. Semua aplikasi harus melakukannya, apa pun API level yang ditargetkan.
// 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) } }
Tempel dari papan klip
Seperti yang dijelaskan sebelumnya, tempelkan data dari papan klip dengan mendapatkan objek papan klip global, mendapatkan objek klip, melihat datanya, dan jika memungkinkan, menyalin data dari objek klip ke penyimpanan Anda sendiri. Bagian ini menjelaskan secara detail cara menempelkan tiga bentuk data papan klip.
Tempelkan teks biasa
Untuk menempelkan teks biasa, dapatkan papan klip global dan verifikasi bahwa papan klip tersebut dapat menampilkan teks biasa. Kemudian, dapatkan
objek klip dan salin teksnya ke penyimpanan Anda sendiri menggunakan getText()
, seperti yang dijelaskan dalam
prosedur berikut:
- Dapatkan objek
ClipboardManager
global menggunakangetSystemService(CLIPBOARD_SERVICE)
. Selain itu, deklarasikan variabel global untuk memuat teks yang ditempelkan:Kotlin
var clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager var pasteData: String = ""
Java
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); String pasteData = "";
- Tentukan apakah Anda perlu mengaktifkan atau menonaktifkan opsi "tempel" dalam aktivitas
saat ini. Pastikan papan klip berisi klip dan Anda dapat menangani jenis data
yang direpresentasikan oleh 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); }
- Salin data dari papan klip. Titik dalam kode ini hanya dapat dijangkau jika
item menu "tempel" diaktifkan, sehingga Anda dapat mengasumsikan bahwa papan klip berisi teks
biasa. Anda belum tahu apakah {i>string<i} itu berisi string teks atau URI yang mengarah ke teks biasa.
Cuplikan kode berikut mengujinya, tetapi hanya menampilkan kode untuk menangani teks biasa:
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; } }
Menempelkan data dari URI konten
Jika objek ClipData.Item
berisi URI konten dan Anda memutuskan bahwa Anda dapat
menangani salah satu jenis MIME-nya, buat ContentResolver
dan panggil metode penyedia konten
yang sesuai untuk mengambil data.
Prosedur berikut menjelaskan cara mendapatkan data dari penyedia konten berdasarkan URI konten di papan klip. Metode ini akan memeriksa apakah jenis MIME yang dapat digunakan aplikasi tersedia dari penyedia.
-
Deklarasikan variabel global untuk memuat jenis 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";
- Dapatkan papan klip global. Dapatkan juga resolver konten agar Anda dapat mengakses penyedia
konten:
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();
- Dapatkan klip utama dari papan klip dan dapatkan kontennya sebagai 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();
- Uji apakah URI merupakan URI konten dengan memanggil
getType(Uri)
. Metode ini menampilkan null jikaUri
tidak mengarah ke penyedia konten yang valid.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);
- Menguji apakah penyedia konten mendukung tipe MIME yang dipahami aplikasi. Jika
ya, panggil
ContentResolver.query()
untuk mendapatkan data. Nilai yang ditampilkan adalahCursor
.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(); } } } }
Menempelkan Intent
Untuk menempelkan intent, dapatkan papan klip global terlebih dahulu. Periksa objek ClipData.Item
untuk melihat apakah berisi Intent
. Lalu, panggil getIntent()
untuk menyalin
intent ke penyimpanan Anda sendiri. Cuplikan berikut menunjukkan hal tersebut:
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. }
Notifikasi sistem ditampilkan saat aplikasi Anda mengakses data papan klip
Di Android 12 (API level 31) dan yang lebih baru, sistem biasanya menampilkan pesan toast saat aplikasi
memanggil
getPrimaryClip()
.
Teks di dalam pesan toast berisi format berikut:
APP pasted from your clipboard
Sistem tidak menampilkan pesan toast saat aplikasi Anda melakukan salah satu hal berikut:
- Mengakses
ClipData
dari aplikasi Anda sendiri. - Berulang kali mengakses
ClipData
dari aplikasi tertentu. Toast hanya muncul saat aplikasi Anda mengakses data dari aplikasi tersebut untuk pertama kalinya. - Mengambil metadata untuk objek klip, seperti dengan memanggil
getPrimaryClipDescription()
, bukangetPrimaryClip()
.
Menggunakan penyedia konten untuk menyalin data yang kompleks
Penyedia konten mendukung penyalinan data kompleks seperti data database atau aliran file. Untuk menyalin data, tempatkan URI konten di papan klip. Menempelkan aplikasi lalu mendapatkan URI ini dari papan klip dan menggunakannya untuk mengambil data database atau deskriptor aliran file.
Karena aplikasi penempelan hanya memiliki URI konten untuk data Anda, aplikasi perlu mengetahui bagian data mana yang akan diambil. Anda dapat memberikan informasi ini dengan mengenkode ID untuk data pada URI itu sendiri, atau Anda dapat memberikan URI unik yang menampilkan data yang ingin Anda salin. Teknik yang Anda pilih bergantung pada organisasi data Anda.
Bagian berikut menjelaskan cara menyiapkan URI, menyediakan data kompleks, dan menyediakan aliran file. Deskripsinya mengasumsikan bahwa Anda sudah memahami prinsip umum desain penyedia konten.
Mengenkode ID pada URI
Teknik yang berguna untuk menyalin data ke papan klip dengan URI adalah dengan mengenkode ID untuk data di URI itu sendiri. Penyedia konten Anda kemudian bisa mendapatkan ID dari URI dan menggunakannya untuk mengambil data. Aplikasi penempelan tidak perlu mengetahui bahwa pengenal itu ada. Aplikasi hanya perlu mendapatkan "reference" Anda—URI dan ID—dari papan klip, memberikannya kepada penyedia konten, dan mendapatkan kembali datanya.
Anda biasanya mengenkode ID ke URI konten dengan menyambungkannya ke bagian akhir URI. Misalnya, anggaplah Anda mendefinisikan URI penyedia Anda sebagai string berikut:
"content://com.example.contacts"
Jika Anda ingin mengenkode nama ke URI ini, gunakan cuplikan kode berikut:
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);
Jika sudah menggunakan penyedia konten, Anda mungkin perlu menambahkan jalur URI baru yang menunjukkan URI adalah untuk menyalin. Misalnya, anggaplah Anda sudah memiliki jalur URI berikut:
"content://com.example.contacts/people" "content://com.example.contacts/people/detail" "content://com.example.contacts/people/images"
Anda dapat menambahkan jalur lain untuk menyalin URI:
"content://com.example.contacts/copying"
Kemudian, Anda dapat mendeteksi URI "copy" dengan pencocokan pola dan menanganinya dengan kode yang khusus untuk menyalin dan menempel.
Biasanya, teknik encoding digunakan jika sudah menggunakan penyedia konten, database internal, atau tabel internal untuk mengatur data Anda. Dalam kasus ini, Anda memiliki beberapa bagian data yang ingin disalin, dan mungkin merupakan ID unik untuk setiap bagian. Untuk merespons kueri dari aplikasi sebelumnya, Anda dapat mencari data berdasarkan ID-nya dan menampilkannya.
Jika tidak memiliki beberapa data, Anda mungkin tidak perlu mengenkode ID. Anda bisa menggunakan URI yang unik untuk penyedia Anda. Sebagai respons atas kueri, penyedia Anda akan menampilkan data yang saat ini dimuatnya.
Menyalin struktur data
Siapkan penyedia konten untuk menyalin dan menempelkan data kompleks sebagai subclass
komponen
ContentProvider
. Lakukan enkode URI yang Anda tempatkan di papan klip agar mengarah ke data yang tepat yang ingin Anda
berikan. Selain itu, pertimbangkan status permohonan Anda yang ada:
- Jika sudah memiliki penyedia konten, Anda dapat menambahkan fungsinya. Anda mungkin hanya
perlu memodifikasi metode
query()
untuk menangani URI yang berasal dari aplikasi yang ingin menempelkan data. Anda mungkin ingin mengubah metode untuk menangani pola URI "copy". - Jika aplikasi Anda memiliki database internal, sebaiknya pindahkan database ini ke penyedia konten untuk mempermudah penyalinan database tersebut.
- Jika tidak menggunakan database, Anda dapat mengimplementasikan penyedia konten sederhana yang tujuannya hanya menawarkan data ke aplikasi yang ditempelkan dari papan klip.
Di penyedia konten, ganti setidaknya metode berikut:
-
query()
- Menempel aplikasi mengasumsikan bahwa aplikasi tersebut bisa mendapatkan data Anda menggunakan metode ini dengan URI yang Anda tempatkan di papan klip. Untuk mendukung penyalinan, setel metode ini mendeteksi URI yang berisi jalur "copy" khusus. Aplikasi Anda kemudian dapat membuat URI "copy" untuk ditempatkan di papan klip, yang berisi jalur penyalinan dan pointer tepat ke data yang ingin disalin.
-
getType()
- Metode ini harus menampilkan jenis MIME untuk data yang ingin Anda salin. Metode
newUri()
memanggilgetType()
untuk memasukkan jenis MIME ke dalam objekClipData
baru.Jenis MIME untuk data kompleks dijelaskan dalam Penyedia konten.
Anda tidak perlu memiliki salah satu metode penyedia konten lain, seperti
insert()
atau
update()
.
Aplikasi penempelan hanya perlu mendapatkan jenis MIME yang didukung dan menyalin data dari penyedia Anda.
Jika Anda sudah memilikinya, metode tersebut tidak akan mengganggu operasi penyalinan.
Cuplikan berikut menunjukkan cara menyiapkan aplikasi untuk menyalin data kompleks:
-
Dalam konstanta global untuk aplikasi Anda, deklarasikan string URI dasar dan jalur yang mengidentifikasi string URI yang Anda gunakan untuk menyalin data. Selain itu, deklarasikan juga jenis MIME untuk data yang disalin.
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";
- Pada aktivitas yang digunakan pengguna untuk menyalin data, siapkan kode untuk menyalin data ke papan klip.
Sebagai respons terhadap permintaan penyalinan, tempatkan URI di papan klip.
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);
-
Dalam cakupan global penyedia konten, buat pencocok URI dan tambahkan pola URI yang cocok dengan URI yang Anda tempatkan di papan klip.
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);
-
Siapkan metode
query()
. Metode ini dapat menangani pola URI yang berbeda, bergantung pada cara Anda mengodekannya, tetapi hanya pola untuk operasi penyalinan papan klip yang ditampilkan.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. ... }
-
Siapkan metode
getType()
untuk menampilkan jenis MIME yang sesuai untuk data yang disalin: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); ... } }
Bagian Menempelkan data dari URI konten menjelaskan cara mendapatkan URI konten dari papan klip dan menggunakannya untuk mendapatkan dan menempelkan data.
Salin aliran data
Anda dapat menyalin dan menempelkan teks dan data biner dalam jumlah besar sebagai aliran. Data dapat memiliki bentuk seperti berikut:
- File yang disimpan di perangkat yang sebenarnya
- Aliran dari soket
- Sejumlah besar data yang disimpan dalam sistem database yang mendasarinya pada penyedia
Penyedia konten untuk aliran data memberikan akses ke datanya dengan objek deskriptor file,
seperti
AssetFileDescriptor
,
bukan objek Cursor
. Aplikasi penempelan membaca aliran data menggunakan
deskripsi file ini.
Untuk menyiapkan aplikasi Anda guna menyalin aliran data dengan penyedia, ikuti langkah-langkah berikut:
-
Siapkan URI konten untuk aliran data yang Anda tempatkan di papan klip. Opsi
untuk melakukan langkah ini mencakup hal berikut:
- Lakukan enkode ID untuk aliran data ke URI, seperti yang dijelaskan di bagian Mengenkode ID di URI, lalu simpan tabel di penyedia Anda yang berisi ID dan nama aliran data yang sesuai.
- Lakukan enkode nama aliran data langsung di URI.
- Gunakan URI unik yang selalu menampilkan aliran saat ini dari penyedia. Jika Anda menggunakan opsi ini, ingatlah untuk memperbarui penyedia agar mengarah ke aliran data lain setiap kali Anda menyalin aliran ke papan klip menggunakan URI.
- Berikan jenis MIME untuk setiap jenis aliran data yang akan Anda tawarkan. Aplikasi penempelan memerlukan informasi ini untuk menentukan apakah penempelan data di papan klip dapat dilakukan.
- Terapkan salah satu metode
ContentProvider
yang menampilkan deskriptor file untuk aliran. Jika Anda mengenkode ID pada URI konten, gunakan metode ini untuk menentukan aliran yang akan dibuka. - Untuk menyalin aliran data ke papan klip, buat URI konten dan tempatkan di papan klip.
Untuk menempelkan aliran data, aplikasi akan mendapatkan klip dari papan klip, mendapatkan URI, dan menggunakannya
dalam panggilan ke metode deskriptor file ContentResolver
yang membuka aliran data. Metode
ContentResolver
memanggil metode ContentProvider
yang sesuai,
dengan meneruskan URI konten ke dalamnya. Penyedia Anda menampilkan deskriptor file ke
metode ContentResolver
. Aplikasi penempelan kemudian bertanggung jawab untuk membaca
data dari aliran data.
Daftar berikut menunjukkan metode deskriptor file yang paling penting untuk penyedia konten. Masing-masing
memiliki metode ContentResolver
yang sesuai dengan string
"Descriptor" yang ditambahkan ke nama metode. Misalnya, analog ContentResolver
dari
openAssetFile()
adalah
openAssetFileDescriptor()
.
-
openTypedAssetFile()
-
Metode ini menampilkan deskriptor file aset, tetapi hanya jika jenis MIME yang diberikan didukung oleh penyedia. Pemanggil—aplikasi yang melakukan penempelan—menyediakan pola jenis MIME. Penyedia konten aplikasi yang menyalin URI ke papan klip akan menampilkan handle file
AssetFileDescriptor
jika dapat menyediakan jenis MIME tersebut dan menampilkan pengecualian jika tidak dapat melakukannya.Metode ini menangani subbagian file. Anda dapat menggunakannya untuk membaca aset yang telah disalin oleh penyedia konten ke papan klip.
-
openAssetFile()
-
Metode ini adalah bentuk yang lebih umum dari
openTypedAssetFile()
. Layanan ini tidak memfilter jenis MIME yang diizinkan, tetapi dapat membaca subbagian file. -
openFile()
-
Ini adalah bentuk yang lebih umum dari
openAssetFile()
. Tidak dapat membaca subbagian file.
Secara opsional, Anda dapat menggunakan
metode
openPipeHelper()
dengan metode deskriptor file Anda. Hal ini memungkinkan aplikasi penempelan membaca data aliran di
thread latar belakang menggunakan pipe. Untuk menggunakan metode ini, implementasikan
antarmuka
ContentProvider.PipeDataWriter
.
Merancang fungsi salin dan tempel yang efektif
Untuk mendesain fungsi salin dan tempel yang efektif bagi aplikasi Anda, ingatlah hal-hal berikut:
- Kapan pun, hanya akan ada satu klip di papan klip. Operasi penyalinan baru oleh aplikasi apa pun dalam sistem akan menimpa klip sebelumnya. Karena pengguna mungkin keluar dari aplikasi Anda dan menyalin sebelum kembali, Anda tidak dapat mengasumsikan papan klip berisi klip yang sebelumnya disalin pengguna dalam aplikasi Anda.
-
Tujuan yang dimaksud dengan adanya beberapa objek
ClipData.Item
per klip adalah untuk mendukung penyalinan dan penempelan beberapa pilihan, bukan berbagai bentuk referensi untuk satu pilihan. Anda biasanya ingin semua objekClipData.Item
dalam klip memiliki bentuk yang sama. Artinya, semuanya harus berupa teks sederhana, URI konten, atauIntent
, dan tidak harus berupa campuran. -
Saat memberikan data, Anda dapat menawarkan representasi MIME yang berbeda. Tambahkan jenis MIME
yang Anda dukung ke
ClipDescription
, lalu terapkan jenis MIME tersebut di penyedia konten Anda. -
Saat Anda mendapatkan data dari papan klip, aplikasi Anda bertanggung jawab untuk memeriksa
jenis MIME yang tersedia dan kemudian memutuskan data mana, jika ada, yang akan digunakan. Meskipun ada
klip di papan klip dan pengguna meminta penempelan, aplikasi Anda tidak
perlu melakukan penyalinan. Lakukan tempel jika jenis MIME kompatibel. Anda dapat memaksa data di papan klip untuk teks menggunakan
coerceToText()
. Jika aplikasi Anda mendukung lebih dari satu jenis MIME yang tersedia, Anda dapat mengizinkan pengguna memilih mana yang akan digunakan.