Penyedia konten mengelola akses ke repositori data pusat. Penyedia merupakan bagian dari aplikasi Android, yang sering kali menyediakan UI-nya sendiri untuk menggunakan data. Namun, penyedia konten utamanya digunakan oleh aplikasi, yang mengakses penyedia menggunakan objek klien penyedia. Bersama-sama, penyedia dan penyedia layanan menawarkan antarmuka standar yang konsisten untuk data yang juga menangani komunikasi antar-proses (IPC) dan akses data yang aman.
Biasanya Anda bekerja sama dengan penyedia konten dalam salah satu dari dua skenario berikut: menerapkan kode tertentu untuk mengakses penyedia materi yang ada di aplikasi lain atau membuat penyedia konten baru di aplikasi Anda untuk berbagi data dengan aplikasi lain.
Halaman ini membahas dasar-dasar bekerja dengan penyedia konten yang ada. Untuk mempelajari cara menerapkan penyedia konten di aplikasi Anda sendiri, lihat Buat penyedia konten.
Topik ini menjelaskan hal-hal berikut:
- Cara kerja penyedia konten.
- API yang Anda gunakan untuk mengambil data dari penyedia konten.
- API yang Anda gunakan untuk menyisipkan, memperbarui, atau menghapus data dalam penyedia konten.
- Fitur API lainnya yang memudahkan kita menggunakan penyedia.
Ringkasan
Penyedia konten menyajikan data ke aplikasi eksternal dalam bentuk satu atau beberapa tabel yang serupa dengan tabel yang terdapat di database relasional. Sebuah baris menggambarkan instance dari beberapa jenis data yang dikumpulkan oleh penyedia, dan setiap kolom dalam baris menggambarkan satu bagian data yang dikumpulkan untuk sebuah instance.
Penyedia konten mengoordinasikan akses ke lapisan penyimpanan data di aplikasi Anda untuk jumlah API dan komponen yang berbeda. Seperti yang diilustrasikan dalam gambar 1, hal ini mencakup hal berikut:
- Membagikan akses ke data aplikasi Anda dengan aplikasi lain
- Mengirim data ke widget
- Menampilkan saran penelusuran khusus untuk aplikasi Anda melalui framework
penelusuran menggunakan
SearchRecentSuggestionsProvider
- Menyinkronkan data aplikasi dengan server Anda menggunakan implementasi
AbstractThreadedSyncAdapter
- Memuat data di UI Anda menggunakan
CursorLoader
Mengakses penyedia
Jika ingin mengakses data di sebuah penyedia konten, gunakan objek
ContentResolver
dalam
Context
aplikasi Anda untuk berkomunikasi dengan penyedia sebagai klien. Objek
ContentResolver
berkomunikasi dengan objek penyedia, yaitu
instance class yang menerapkan ContentProvider
.
Objek penyedia
menerima permintaan data dari klien, melakukan tindakan yang diminta, dan menampilkan
hasilnya. Objek ini memiliki metode yang memanggil metode bernama identik dalam objek penyedia,
instance salah satu subclass konkret dari ContentProvider
. Tujuan
Metode ContentResolver
menyediakan atribut dasar
"CRUD" (membuat, mengambil, memperbarui, dan menghapus) dari penyimpanan persisten.
Pola umum untuk mengakses ContentProvider
dari UI Anda menggunakan
CursorLoader
untuk menjalankan kueri asinkron di latar belakang. Tujuan
Activity
atau Fragment
di UI Anda akan memanggil
CursorLoader
ke kueri, yang akan menghasilkan
ContentProvider
menggunakan ContentResolver
.
Hal ini memungkinkan UI terus tersedia bagi pengguna saat kueri sedang berjalan. Pola ini melibatkan interaksi dari sejumlah objek yang berbeda, serta mekanisme penyimpanan yang mendasarinya, seperti yang ditunjukkan dalam gambar 2.
Catatan: Untuk mengakses penyedia, aplikasi Anda biasanya harus meminta izin tertentu dalam file manifes. Pola pengembangan ini dijelaskan lebih detail dalam Bagian Izin penyedia konten.
Salah satu penyedia bawaan di platform Android adalah Penyedia Kamus Pengguna, yang menyimpan kata-kata tidak standar yang ingin disimpan pengguna. Tabel 1 menunjukkan seperti apa data tersebut di tabel penyedia ini:
word | app id | frequency | locale | _ID |
---|---|---|---|---|
mapreduce |
user1 | 100 | en_US | 1 |
precompiler |
user14 | 200 | fr_FR | 2 |
applet |
user2 | 225 | fr_CA | 3 |
const |
user1 | 255 | pt_BR | 4 |
int |
user5 | 100 | en_UK | 5 |
Pada tabel 1, setiap baris mewakili
instance dari sebuah kata yang
yang ditemukan dalam kamus standar. Setiap kolom mewakili sepotong data untuk kata tersebut, seperti
lokal tempat{i> <i}pertama kali ditemukan. Header kolom merupakan nama-nama kolom yang disimpan di dalam
penyedia. Jadi, untuk merujuk ke lokalitas baris, misalnya, Anda merujuk ke kolom locale
. Sebagai
penyedia ini, kolom _ID
berfungsi sebagai kolom kunci utama yang
dikelola oleh penyedia secara otomatis.
Untuk mendapatkan daftar kata dan lokalnya dari Penyedia Kamus Pengguna,
panggil ContentResolver.query()
.
Metode query()
ini memanggil
metode ContentProvider.query()
yang ditentukan oleh
Penyedia Kamus Pengguna. Baris kode berikut menunjukkan
panggilan ContentResolver.query()
:
Kotlin
// Queries the UserDictionary and returns results cursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs.toTypedArray(), // Selection criteria sortOrder // The sort order for the returned rows )
Java
// Queries the UserDictionary and returns results cursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Selection criteria selectionArgs, // Selection criteria sortOrder); // The sort order for the returned rows
Tabel 2 menunjukkan bagaimana argumen untuk
query(Uri,projection,selection,selectionArgs,sortOrder)
cocok dengan pernyataan SQL SELECT:
Argumen query() |
Kata kunci/parameter SELECT | Catatan |
---|---|---|
Uri |
FROM table_name |
Uri dipetakan ke tabel dalam penyedia yang bernama table_name. |
projection |
col,col,col,... |
projection adalah array kolom yang disertakan untuk setiap baris
diambil.
|
selection |
WHERE col = value |
selection menentukan kriteria dalam pemilihan baris. |
selectionArgs |
Tidak ada padanan persis. Argumen pemilihan menggantikan placeholder ? di
klausa pemilihan.
|
|
sortOrder |
ORDER BY col,col,... |
sortOrder menentukan urutan baris yang muncul dalam
Cursor yang ditampilkan.
|
URI Konten
URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI Konten menyertakan nama simbolis seluruh penyedia—otoritas-nya—dan nama yang mengarah ke tabel—jalur. Saat Anda memanggil metode klien untuk mengakses tabel dalam penyedia, URI konten dalam tabel merupakan salah satu argumen.
Dalam baris kode sebelumnya, konstanta
CONTENT_URI
berisi URI konten dari
tabel Words
Penyedia Kamus Pengguna. ContentResolver
mengurai otoritas URI dan menggunakannya untuk menyelesaikan penyedia dengan
membandingkan otoritas dengan tabel
sistem berisi penyedia yang dikenal. Tujuan
Selanjutnya, ContentResolver
dapat mengirim argumen kueri ke nilai
penyedia layanan.
ContentProvider
menggunakan bagian jalur URI konten untuk memilih
tabel yang akan diakses. Penyedia biasanya memiliki jalur untuk setiap tabel yang diekspos.
Pada baris kode sebelumnya, URI lengkap untuk tabel Words
adalah:
content://user_dictionary/words
- String
content://
adalah skema, yang selalu ada dan mengidentifikasinya sebagai URI konten. - String
user_dictionary
adalah otoritas penyedia. - String
words
adalah jalur tabel.
Banyak penyedia yang memungkinkan Anda mengakses satu baris dalam tabel dengan menambahkan nilai ID
ke akhir URI. Misalnya, untuk mengambil baris yang _ID
-nya adalah
4
dari Penyedia Kamus Pengguna, Anda dapat menggunakan URI konten ini:
Kotlin
val singleUri: Uri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI, 4)
Java
Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);
Anda sering menggunakan nilai ID saat mengambil satu set baris, lalu ingin memperbarui atau menghapus salah satunya.
Catatan: Class Uri
dan Uri.Builder
berisi metode sederhana untuk membuat objek URI yang diformat dengan benar dari string. Tujuan
Class ContentUris
berisi metode praktis untuk menambahkan nilai ID ke
URI. Cuplikan sebelumnya menggunakan withAppendedId()
untuk menambahkan ID ke URI konten Penyedia Kamus Pengguna.
Mengambil data dari penyedia
Bagian ini menjelaskan cara mengambil data dari penyedia, menggunakan Penyedia Kamus Pengguna sebagai contoh.
Agar lebih jelas, cuplikan kode di bagian ini memanggil
ContentResolver.query()
pada UI thread. Di beberapa
kode yang sebenarnya, bagaimanapun, melakukan kueri
secara asinkron pada sebuah utas terpisah. Anda dapat
menggunakan class CursorLoader
, yang dijelaskan
secara lebih detail dalam
Panduan Loader. Selain itu, baris kode tersebut hanyalah cuplikan. Aset tidak menampilkan
aplikasi.
Untuk mengambil data dari penyedia, ikuti langkah-langkah dasar berikut:
- Minta izin akses baca untuk penyedia.
- Tentukan kode yang mengirim kueri ke penyedia.
Meminta izin akses baca
Untuk mengambil data dari penyedia, aplikasi Anda memerlukan izin akses baca untuk
penyedia layanan. Anda tidak dapat meminta izin ini pada runtime. Sebaliknya, Anda harus menentukan bahwa
Anda memerlukan izin ini dalam manifes, menggunakan
<uses-permission>
dan nama izin persis yang didefinisikan oleh
penyedia layanan.
Jika menentukan elemen ini dalam manifes, Anda akan meminta elemen ini untuk aplikasi Anda. Saat menginstal aplikasi Anda, pengguna secara implisit memberikan permintaan ini.
Guna menemukan nama persis dari izin akses baca untuk penyedia yang digunakan beserta nama untuk izin akses lain yang digunakan oleh penyedia, cari di dokumentasi penyedia.
Peran izin dalam mengakses penyedia dijelaskan secara lebih mendetail dalam Bagian Izin penyedia konten.
Penyedia Kamus Pengguna menentukan izin android.permission.READ_USER_DICTIONARY
dalam file manifesnya, sehingga
aplikasi yang ingin membaca dari penyedia harus meminta izin ini.
Membuat kueri
Langkah berikutnya dalam mengambil data dari penyedia adalah membuat kueri. Cuplikan berikut mendefinisikan beberapa variabel untuk mengakses Penyedia Kamus Pengguna:
Kotlin
// A "projection" defines the columns that are returned for each row private val mProjection: Array<String> = arrayOf( UserDictionary.Words._ID, // Contract class constant for the _ID column name UserDictionary.Words.WORD, // Contract class constant for the word column name UserDictionary.Words.LOCALE // Contract class constant for the locale column name ) // Defines a string to contain the selection clause private var selectionClause: String? = null // Declares an array to contain selection arguments private lateinit var selectionArgs: Array<String>
Java
// A "projection" defines the columns that are returned for each row String[] mProjection = { UserDictionary.Words._ID, // Contract class constant for the _ID column name UserDictionary.Words.WORD, // Contract class constant for the word column name UserDictionary.Words.LOCALE // Contract class constant for the locale column name }; // Defines a string to contain the selection clause String selectionClause = null; // Initializes an array to contain selection arguments String[] selectionArgs = {""};
Cuplikan berikutnya menampilkan cara menggunakan
ContentResolver.query()
, dengan Penyedia Kamus Pengguna
sebagai contoh. Kueri klien penyedia serupa dengan kueri SQL, dan berisi
satu set kolom yang akan dikembalikan, satu set kriteria pemilihan, dan tata urutan.
Kumpulan kolom yang dihasilkan oleh kueri disebut dengan proyeksi, dan
variabelnya adalah mProjection
.
Ekspresi yang menentukan baris yang harus diambil dibagi menjadi klausa pemilihan dan
argumen pemilihan. Klausa pemilihan adalah kombinasi
dari ekspresi logika dan boolean,
nama kolom, dan nilai. Variabelnya adalah mSelectionClause
. Jika Anda menentukan
parameter yang dapat diganti ?
, bukan nilai, metode kueri akan mengambil nilai
dari array argumen pemilihan, yang merupakan variabel mSelectionArgs
.
Dalam cuplikan berikutnya, jika pengguna tidak memasukkan kata, klausa pemilihan akan disetel ke
null
dan kueri akan menampilkan semua kata dalam penyedia. Jika pengguna memasukkan
sebuah kata, klausa pemilihan disetel ke UserDictionary.Words.WORD + " = ?"
dan
elemen pertama array argumen pemilihan disetel ke kata yang dimasukkan pengguna.
Kotlin
/* * This declares a String array to contain the selection arguments. */ private lateinit var selectionArgs: Array<String> // Gets a word from the UI searchString = searchWord.text.toString() // Insert code here to check for invalid or malicious input // If the word is the empty string, gets everything selectionArgs = searchString?.takeIf { it.isNotEmpty() }?.let { selectionClause = "${UserDictionary.Words.WORD} = ?" arrayOf(it) } ?: run { selectionClause = null emptyArray<String>() } // Does a query against the table and returns a Cursor object mCursor = contentResolver.query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder // The sort order for the returned rows ) // Some providers return null if an error occurs, others throw an exception when (mCursor?.count) { null -> { /* * Insert code here to handle the error. Be sure not to use the cursor! * You might want to call android.util.Log.e() to log this error. */ } 0 -> { /* * Insert code here to notify the user that the search is unsuccessful. This isn't * necessarily an error. You might want to offer the user the option to insert a new * row, or re-type the search term. */ } else -> { // Insert code here to do something with the results } }
Java
/* * This defines a one-element String array to contain the selection argument. */ String[] selectionArgs = {""}; // Gets a word from the UI searchString = searchWord.getText().toString(); // Remember to insert code here to check for invalid or malicious input // If the word is the empty string, gets everything if (TextUtils.isEmpty(searchString)) { // Setting the selection clause to null returns all words selectionClause = null; selectionArgs[0] = ""; } else { // Constructs a selection clause that matches the word that the user entered selectionClause = UserDictionary.Words.WORD + " = ?"; // Moves the user's input string to the selection arguments selectionArgs[0] = searchString; } // Does a query against the table and returns a Cursor object mCursor = getContentResolver().query( UserDictionary.Words.CONTENT_URI, // The content URI of the words table projection, // The columns to return for each row selectionClause, // Either null or the word the user entered selectionArgs, // Either empty or the string the user entered sortOrder); // The sort order for the returned rows // Some providers return null if an error occurs, others throw an exception if (null == mCursor) { /* * Insert code here to handle the error. Be sure not to use the cursor! You can * call android.util.Log.e() to log this error. * */ // If the Cursor is empty, the provider found no matches } else if (mCursor.getCount() < 1) { /* * Insert code here to notify the user that the search is unsuccessful. This isn't necessarily * an error. You can offer the user the option to insert a new row, or re-type the * search term. */ } else { // Insert code here to do something with the results }
Kueri ini setara dengan pernyataan SQL berikut:
SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC;
Dalam Pernyataan SQL ini, nama kolom yang sesungguhnya digunakan sebagai ganti konstanta kelas kontrak.
Melindungi dari input berbahaya
Jika data yang dikelola oleh penyedia konten berada dalam database SQL, termasuk data eksternal yang tidak data menjadi pernyataan SQL mentah dapat menyebabkan injeksi SQL.
Perhatikan klausa pemilihan berikut:
Kotlin
// Constructs a selection clause by concatenating the user's input to the column name var selectionClause = "var = $mUserInput"
Java
// Constructs a selection clause by concatenating the user's input to the column name String selectionClause = "var = " + userInput;
Jika Anda melakukannya, pengguna akan berpotensi menyambungkan SQL berbahaya ke pernyataan SQL Anda.
Misalnya, pengguna dapat memasukkan "nothing; DROP TABLE *;" untuk mUserInput
, yang
menghasilkan klausa pemilihan var = nothing; DROP TABLE *;
.
Karena klausa pilihan diperlakukan sebagai pernyataan SQL, ini dapat menyebabkan penyedia menghapus tabel dalam database SQLite yang mendasarinya, kecuali jika penyedia disiapkan untuk menangkap injeksi SQL.
Untuk menghindari masalah ini, gunakan klausa pemilihan yang menggunakan ?
sebagai parameter
yang dapat diganti dan array argumen pemilihan terpisah. Dengan cara ini, input pengguna
terikat langsung pada kueri daripada ditafsirkan sebagai bagian dari pernyataan SQL.
Karena tidak diperlakukan sebagai SQL, input pengguna tidak dapat menginjeksikan SQL yang berbahaya. Sebagai ganti
menggunakan penyambungan untuk menyertakan input pengguna, gunakan klausa pemilihan ini:
Kotlin
// Constructs a selection clause with a replaceable parameter var selectionClause = "var = ?"
Java
// Constructs a selection clause with a replaceable parameter String selectionClause = "var = ?";
Buat array argumen pemilihan seperti ini:
Kotlin
// Defines a mutable list to contain the selection arguments var selectionArgs: MutableList<String> = mutableListOf()
Java
// Defines an array to contain the selection arguments String[] selectionArgs = {""};
Masukkan nilai dalam array argumen pemilihan seperti ini:
Kotlin
// Adds the user's input to the selection argument selectionArgs += userInput
Java
// Sets the selection argument to the user's input selectionArgs[0] = userInput;
Klausa pemilihan yang menggunakan ?
sebagai parameter yang dapat diganti dan array
Array argumen pemilihan adalah cara yang lebih disukai untuk menentukan pilihan, bahkan jika penyedia
berdasarkan {i>database<i} SQL.
Menampilkan hasil kueri
Metode klien ContentResolver.query()
selalu
menampilkan Cursor
berisi kolom-kolom yang ditentukan oleh proyeksi
kueri untuk baris yang cocok dengan kriteria pemilihan kueri. Objek
Cursor
memberikan akses baca acak ke baris dan kolom yang
dimuatnya.
Dengan metode Cursor
, Anda dapat melakukan iterasi pada baris dalam
menentukan tipe data dari setiap kolom, mengambil data dari kolom, dan memeriksa
properti hasil.
Beberapa penerapan Cursor
secara otomatis
mengupdate objek saat data penyedia berubah, memicu metode dalam objek observer
saat Cursor
berubah, atau keduanya.
Catatan: Penyedia dapat membatasi akses ke kolom berdasarkan sifat pembuatan kueri. Misalnya, Penyedia Kontak membatasi akses untuk beberapa kolom untuk adaptor sinkronisasi, sehingga tidak mengembalikannya ke aktivitas atau layanan.
Jika tidak ada baris yang cocok dengan kriteria pemilihan, penyedia layanan
menampilkan objek Cursor
yang
Cursor.getCount()
sama dengan
0—yaitu, kursor kosong.
Jika terjadi kesalahan internal, hasil kueri akan bergantung pada penyedia tertentu. Mungkin
menampilkan null
, atau dapat menampilkan Exception
.
Karena Cursor
adalah daftar baris, cara yang baik untuk menampilkan
isi Cursor
adalah menautkannya ke ListView
menggunakan SimpleCursorAdapter
.
Cuplikan berikut melanjutkan kode dari cuplikan sebelumnya. Ini menciptakan
Objek SimpleCursorAdapter
yang berisi Cursor
yang diambil oleh kueri, dan menyetel objek ini menjadi adaptor bagi
ListView
.
Kotlin
// Defines a list of columns to retrieve from the Cursor and load into an output row val wordListColumns : Array<String> = arrayOf( UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name ) // Defines a list of View IDs that receive the Cursor columns for each row val wordListItems = intArrayOf(R.id.dictWord, R.id.locale) // Creates a new SimpleCursorAdapter cursorAdapter = SimpleCursorAdapter( applicationContext, // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0 // Flags (usually none are needed) ) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter)
Java
// Defines a list of columns to retrieve from the Cursor and load into an output row String[] wordListColumns = { UserDictionary.Words.WORD, // Contract class constant containing the word column name UserDictionary.Words.LOCALE // Contract class constant containing the locale column name }; // Defines a list of View IDs that receive the Cursor columns for each row int[] wordListItems = { R.id.dictWord, R.id.locale}; // Creates a new SimpleCursorAdapter cursorAdapter = new SimpleCursorAdapter( getApplicationContext(), // The application's Context object R.layout.wordlistrow, // A layout in XML for one row in the ListView mCursor, // The result from the query wordListColumns, // A string array of column names in the cursor wordListItems, // An integer array of view IDs in the row layout 0); // Flags (usually none are needed) // Sets the adapter for the ListView wordList.setAdapter(cursorAdapter);
Catatan: Untuk mendukung ListView
dengan
Cursor
, kursor harus berisi kolom bernama _ID
.
Oleh karena itu, kueri yang ditampilkan sebelumnya mengambil kolom _ID
untuk
Words
, meskipun ListView
tidak menampilkannya.
Pembatasan ini juga menjelaskan mengapa sebagian besar penyedia memiliki kolom _ID
untuk masing-masing
tabel mereka.
Mendapatkan data dari hasil kueri
Selain untuk menampilkan hasil kueri, Anda dapat menggunakannya untuk tugas lain. Sebagai
misalnya, Anda dapat mengambil ejaan dari Penyedia Kamus Pengguna kemudian mencarinya di
penyedia lain. Caranya, ulangi baris dalam Cursor
, seperti ditunjukkan pada contoh berikut:
Kotlin
/* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers might throw an Exception instead of returning null. */ mCursor?.apply { // Determine the column index of the column named "word" val index: Int = getColumnIndex(UserDictionary.Words.WORD) /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you get an * exception. */ while (moveToNext()) { // Gets the value from the column newWord = getString(index) // Insert code here to process the retrieved word ... // End of while loop } }
Java
// Determine the column index of the column named "word" int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); /* * Only executes if the cursor is valid. The User Dictionary Provider returns null if * an internal error occurs. Other providers might throw an Exception instead of returning null. */ if (mCursor != null) { /* * Moves to the next row in the cursor. Before the first movement in the cursor, the * "row pointer" is -1, and if you try to retrieve data at that position you get an * exception. */ while (mCursor.moveToNext()) { // Gets the value from the column newWord = mCursor.getString(index); // Insert code here to process the retrieved word ... // End of while loop } } else { // Insert code here to report an error if the cursor is null or the provider threw an exception }
Implementasi Cursor
berisi beberapa metode "get" untuk
mengambil berbagai jenis data dari objek. Misalnya, cuplikan sebelumnya
menggunakan getString()
. Implementasi juga memiliki
metode getType()
yang menampilkan nilai yang menunjukkan
jenis data kolom.
Merilis resource hasil kueri
Cursor
objek harus
ditutup jika tidak diperlukan lagi, sehingga sumber daya yang terkait dengannya dilepaskan
lebih cepat. Hal ini dapat dilakukan
dengan memanggil
close()
, atau dengan menggunakan
pernyataan try-with-resources
dalam bahasa pemrograman Java atau
Fungsi use()
dalam bahasa pemrograman Kotlin.
Izin penyedia konten
Aplikasi penyedia bisa menetapkan izin akses yang harus dimiliki aplikasi lain mengakses data penyedia. Izin ini memungkinkan pengguna mengetahui data apa diakses oleh sebuah aplikasi. Berdasarkan persyaratan penyedia, aplikasi lain meminta izin yang diperlukannya untuk mengakses penyedia. Pengguna akhir akan melihat izin yang diminta saat menginstal aplikasi.
Jika aplikasi penyedia tidak menetapkan izin apa pun, maka aplikasi lain tidak memiliki akses ke data penyedia, kecuali jika penyedia itu diekspor. Selain itu, komponen dalam aplikasi penyedia selalu memiliki akses baca dan tulis penuh, terlepas dari izin yang ditentukan.
Penyedia Kamus Pengguna mewajibkan
android.permission.READ_USER_DICTIONARY
untuk mengambil data darinya.
Penyedia memiliki android.permission.WRITE_USER_DICTIONARY
terpisah
izin untuk menyisipkan, memperbarui, atau menghapus data.
Guna mendapatkan izin yang diperlukan untuk mengakses penyedia, aplikasi memintanya dengan elemen
<uses-permission>
dalam file manifesnya. Saat Android Package Manager menginstal aplikasi, pengguna
harus menyetujui semua izin
yang diminta aplikasi. Jika pengguna
menyetujuinya,
Package Manager melanjutkan instalasi. Jika pengguna tidak menyetujuinya, Pengelola Paket
akan menghentikan penginstalan.
Contoh berikut
<uses-permission>
elemen meminta akses baca ke Penyedia Kamus Pengguna:
<uses-permission android:name="android.permission.READ_USER_DICTIONARY">
Dampak izin pada akses penyedia dijelaskan secara lebih mendetail dalam Tips keamanan.
Menyisipkan, memperbarui, dan menghapus data
Dengan cara yang sama seperti mengambil data dari penyedia, Anda juga menggunakan interaksi antara
klien penyedia dan ContentProvider
penyedia untuk memodifikasi data.
Anda memanggil metode ContentResolver
dengan argumen yang diteruskan ke
metode ContentProvider
yang sesuai. Penyedia dan penyedia
klien secara otomatis menangani keamanan
dan komunikasi antar-proses.
Sisipkan data
Untuk menyisipkan data ke penyedia, Anda memanggil
metode
ContentResolver.insert()
. Metode ini menyisipkan sebuah baris baru ke penyedia dan menampilkan URI konten untuk baris tersebut.
Cuplikan berikut menampilkan cara menyisipkan kata baru ke Penyedia Kamus Pengguna:
Kotlin
// Defines a new Uri object that receives the result of the insertion lateinit var newUri: Uri ... // Defines an object to contain the new values to insert val newValues = ContentValues().apply { /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ put(UserDictionary.Words.APP_ID, "example.user") put(UserDictionary.Words.LOCALE, "en_US") put(UserDictionary.Words.WORD, "insert") put(UserDictionary.Words.FREQUENCY, "100") } newUri = contentResolver.insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert )
Java
// Defines a new Uri object that receives the result of the insertion Uri newUri; ... // Defines an object to contain the new values to insert ContentValues newValues = new ContentValues(); /* * Sets the values of each column and inserts the word. The arguments to the "put" * method are "column name" and "value". */ newValues.put(UserDictionary.Words.APP_ID, "example.user"); newValues.put(UserDictionary.Words.LOCALE, "en_US"); newValues.put(UserDictionary.Words.WORD, "insert"); newValues.put(UserDictionary.Words.FREQUENCY, "100"); newUri = getContentResolver().insert( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI newValues // The values to insert );
Data untuk baris baru masuk ke dalam satu objek ContentValues
, yang
bentuknya serupa dengan kursor satu-baris. Kolom dalam objek ini tidak perlu memiliki
jenis data yang sama. Serta, jika Anda tidak ingin menentukan nilai sama sekali, Anda dapat menyetel kolom
ke null
menggunakan ContentValues.putNull()
.
Cuplikan sebelumnya tidak menambahkan kolom _ID
, karena kolom ini dipertahankan
secara otomatis. Penyedia menetapkan nilai _ID
yang unik ke setiap baris yang
ditambahkan. Penyedia biasanya menggunakan nilai ini sebagai kunci utama tabel.
URI konten yang ditampilkan di newUri
mengidentifikasi baris yang baru ditambahkan dengan
dalam format berikut:
content://user_dictionary/words/<id_value>
<id_value>
adalah konten _ID
untuk baris baru.
Sebagian besar penyedia dapat mendeteksi format URI konten ini secara otomatis, dan kemudian melakukan operasi
yang diminta pada baris tersebut.
Untuk mendapatkan nilai _ID
dari Uri
yang ditampilkan, panggil
ContentUris.parseId()
.
Memperbarui data
Untuk memperbarui baris, gunakan objek ContentValues
dengan kode
seperti yang Anda lakukan pada penyisipan, dan kriteria pemilihan, seperti pada kueri.
Metode klien yang digunakan adalah
ContentResolver.update()
. Anda hanya perlu menambahkan
nilai ke objek ContentValues
untuk kolom yang sedang diperbarui. Jika Anda
ingin mengosongkan isi kolom, setel nilai ke null
.
Cuplikan berikut mengubah semua baris yang kolom lokalnya memiliki bahasa "en"
menjadi
memiliki lokalitas null
. Nilai hasil adalah jumlah baris yang diperbarui.
Kotlin
// Defines an object to contain the updated values val updateValues = ContentValues().apply { /* * Sets the updated value and updates the selected words. */ putNull(UserDictionary.Words.LOCALE) } // Defines selection criteria for the rows you want to update val selectionClause: String = UserDictionary.Words.LOCALE + "LIKE ?" val selectionArgs: Array<String> = arrayOf("en_%") // Defines a variable to contain the number of updated rows var rowsUpdated: Int = 0 ... rowsUpdated = contentResolver.update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines an object to contain the updated values ContentValues updateValues = new ContentValues(); // Defines selection criteria for the rows you want to update String selectionClause = UserDictionary.Words.LOCALE + " LIKE ?"; String[] selectionArgs = {"en_%"}; // Defines a variable to contain the number of updated rows int rowsUpdated = 0; ... /* * Sets the updated value and updates the selected words. */ updateValues.putNull(UserDictionary.Words.LOCALE); rowsUpdated = getContentResolver().update( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI updateValues, // The columns to update selectionClause, // The column to select on selectionArgs // The value to compare to );
Bersihkan input pengguna saat Anda menelepon
ContentResolver.update()
. Untuk mempelajari lebih lanjut tentang
baca bagian Melindungi dari input berbahaya.
Menghapus data
Menghapus baris mirip dengan mengambil data baris. Anda menentukan kriteria pemilihan untuk baris
yang ingin dihapus, dan metode klien akan
mengembalikan jumlah baris yang dihapus.
Cuplikan berikut menghapus baris yang ID aplikasinya cocok dengan "user"
. Metode tersebut menampilkan
jumlah baris yang dihapus.
Kotlin
// Defines selection criteria for the rows you want to delete val selectionClause = "${UserDictionary.Words.APP_ID} LIKE ?" val selectionArgs: Array<String> = arrayOf("user") // Defines a variable to contain the number of rows deleted var rowsDeleted: Int = 0 ... // Deletes the words that match the selection criteria rowsDeleted = contentResolver.delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to )
Java
// Defines selection criteria for the rows you want to delete String selectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; String[] selectionArgs = {"user"}; // Defines a variable to contain the number of rows deleted int rowsDeleted = 0; ... // Deletes the words that match the selection criteria rowsDeleted = getContentResolver().delete( UserDictionary.Words.CONTENT_URI, // The UserDictionary content URI selectionClause, // The column to select on selectionArgs // The value to compare to );
Bersihkan input pengguna saat Anda menelepon
ContentResolver.delete()
. Untuk mempelajari lebih lanjut tentang
ini, baca bagian Melindungi dari input berbahaya.
Jenis data penyedia
Penyedia konten dapat menawarkan berbagai macam jenis data. Penyedia Kamus Pengguna hanya menawarkan teks, tetapi penyedia juga dapat menawarkan format berikut:
- integer
- long integer (long)
- floating point
- long floating point (double)
Tipe data lain yang sering digunakan penyedia adalah objek besar biner (BLOB) yang diimplementasikan sebagai
Array byte 64 KB. Anda dapat melihat jenis data yang tersedia dengan melihat metode "get" class Cursor
.
Jenis data setiap kolom dalam penyedia biasanya tercantum dalam dokumentasinya.
Jenis data untuk Penyedia Kamus Pengguna tercantum dalam dokumentasi referensi
untuk kelas kontraknya, UserDictionary.Words
. Kelas kontrak adalah
yang dijelaskan di bagian Kelas kontrak.
Anda juga dapat menentukan jenis data dengan memanggil Cursor.getType()
.
Penyedia juga menyimpan informasi jenis data MIME untuk setiap URI konten yang ditetapkannya. Anda dapat gunakan informasi jenis MIME untuk mengetahui apakah aplikasi Anda bisa menangani data yang penyedia menawarkan atau memilih jenis penanganan berdasarkan jenis MIME. Anda biasanya memerlukan Jenis MIME jika Anda bekerja dengan penyedia yang berisi struktur data atau file.
Misalnya, ContactsContract.Data
tabel dalam Penyedia Kontak menggunakan jenis MIME untuk memberi label jenis data kontak yang disimpan di setiap
baris. Untuk mendapatkan jenis MIME yang sesuai dengan URI konten, panggil
ContentResolver.getType()
.
Bagian Referensi jenis MIME menjelaskan sintaksis jenis MIME standar dan kustom.
Bentuk-bentuk alternatif akses penyedia
Tiga bentuk alternatif akses penyedia adalah hal penting dalam development aplikasi:
-
Akses batch: Anda dapat membuat batch panggilan akses dengan metode di
class
ContentProviderOperation
, lalu menerapkannya denganContentResolver.applyBatch()
. -
Kueri asinkron: lakukan kueri di thread terpisah. Anda dapat
gunakan objek
CursorLoader
. Contoh dalam panduan Loader menunjukkan cara melakukannya. - Akses data menggunakan intent: meskipun Anda tidak dapat mengirim intent langsung ke penyedia, Anda bisa mengirim intent ke aplikasi penyedia, yang biasanya memiliki perlengkapan terbaik untuk memodifikasi data penyedia.
Akses dan modifikasi batch menggunakan intent dijelaskan di bagian berikut.
Akses batch
Akses batch ke penyedia berguna untuk menyisipkan baris dalam jumlah besar, untuk menyisipkan baris di beberapa tabel dalam panggilan metode yang sama, dan secara umum untuk melakukan satu set operasi di seluruh batas proses sebagai transaksi, yang disebut operasi atomik.
Untuk mengakses penyedia
dalam mode batch,
buat array objek ContentProviderOperation
, lalu
mengirimkannya ke
penyedia konten dengan
ContentResolver.applyBatch()
. Anda meneruskan
otoritas penyedia konten untuk metode ini, bukan URI konten tertentu.
Hal ini memungkinkan setiap objek ContentProviderOperation
dalam tugas array
terhadap tabel yang berbeda. Panggilan ke ContentResolver.applyBatch()
menampilkan array hasil.
Deskripsi kelas kontrak ContactsContract.RawContacts
menyertakan cuplikan kode yang menunjukkan penyisipan batch.
Akses data menggunakan intent
Intent dapat menyediakan akses tidak langsung ke penyedia konten. Anda dapat mengizinkan pengguna mengakses data dalam penyedia meskipun aplikasi Anda tidak memiliki izin akses dengan mendapatkan intent yang dihasilkan kembali dari aplikasi yang memiliki izin atau dengan mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaan di dalamnya.
Mendapatkan akses dengan izin sementara
Anda dapat mengakses data di penyedia konten, sekalipun Anda tidak memiliki akses yang sesuai izin akses, dengan mengirimkan maksud ke aplikasi yang memiliki izin dan menerima kembali intent yang dihasilkan yang berisi izin URI. Ini adalah izin untuk URI konten tertentu yang berlaku hingga aktivitas yang menerima izin selesai. Aplikasi yang memiliki izin tetap akan memberikan izin sementara dengan menandai intent yang dihasilkan:
-
Izin baca:
FLAG_GRANT_READ_URI_PERMISSION
-
Izin tulis:
FLAG_GRANT_WRITE_URI_PERMISSION
Catatan: Tanda ini tidak memberikan akses baca atau tulis umum ke penyedia yang otoritasnya dimuat dalam URI konten. Akses ini hanya ditujukan untuk URI itu sendiri.
Saat Anda mengirim URI konten ke aplikasi lain, sertakan setidaknya salah satu URI ini penanda. Tanda memberikan kemampuan berikut pada aplikasi apa pun yang menerima intent dan menargetkan Android 11 (API level 30) atau yang lebih baru:
- Membaca dari, atau menulis ke, data yang diwakili URI konten, bergantung pada flag yang disertakan dalam intent.
- Dapatkan paket visibilitas ke dalam aplikasi yang berisi penyedia konten yang sesuai dengan Otoritas URI. Aplikasi yang mengirimkan intent dan aplikasi yang berisi penyedia konten mungkin dua aplikasi yang berbeda.
Penyedia mendefinisikan izin URI untuk URI konten dalam manifesnya, dengan menggunakan
android:grantUriPermissions
dari
<provider>
serta
<grant-uri-permission>
elemen turunan dari
<provider>
. Mekanisme izin URI dijelaskan secara lebih mendetail dalam
Panduan Izin di Android.
Misalnya, Anda dapat mengambil data untuk sebuah kontak di Penyedia Kontak, meskipun tidak
memiliki izin READ_CONTACTS
. Anda mungkin ingin melakukannya
dalam aplikasi yang mengirim kartu ucapan elektronik ke seseorang di kontak pada hari ulang tahunnya. Daripada fokus pada
meminta READ_CONTACTS
, yang memberi Anda akses ke semua
kontak pengguna dan semua informasi mereka, biarkan pengguna mengontrol
kontak yang digunakan aplikasi Anda. Untuk melakukannya, gunakan proses berikut:
-
Dalam aplikasi Anda, kirim intent yang berisi tindakan
ACTION_PICK
dan "kontak" Jenis MIMECONTENT_ITEM_TYPE
, menggunakan metodestartActivityForResult()
. - Karena intent ini cocok dengan filter intent untuk "Pemilihan" aplikasi Orang aktivitas, aktivitas akan muncul di latar depan.
-
Dalam aktivitas pemilihan, pengguna memilih
kontak yang ingin diperbarui. Saat ini terjadi, aktivitas pemilihan memanggil
setResult(resultcode, intent)
untuk menyiapkan intent yang akan diberikan kembali ke aplikasi Anda. Intent berisi URI konten kontak yang dipilih pengguna dan "tambahan" tandaFLAG_GRANT_READ_URI_PERMISSION
. Semua tanda ini memberikan izin URI ke aplikasi Anda untuk membaca data kontak yang ditunjukkan oleh URI konten. Kemudian, aktivitas pemilihan memanggilfinish()
untuk menampilkan kontrol ke aplikasi. -
Aktivitas Anda akan ditampilkan di latar depan, dan sistem akan memanggil metode
onActivityResult()
aktivitas Anda. Metode ini menerima intent hasil yang dibuat oleh aktivitas pemilihan dalam aplikasi Orang. - Dengan URI konten dari intent yang dihasilkan, Anda dapat membaca data kontak dari Penyedia Kontak, meskipun Anda tidak meminta izin akses baca tetap ke penyedia dalam manifes Anda. Anda kemudian bisa mendapatkan informasi tanggal lahir kontak tersebut atau alamat email dan kemudian mengirim {i>e-greeting<i}.
Gunakan aplikasi lain
Cara lain agar pengguna dapat memodifikasi data yang izin aksesnya tidak Anda miliki adalah dengan mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaan di sana.
Misalnya, aplikasi Kalender menerima
ACTION_INSERT
yang memungkinkan Anda mengaktifkan
UI penyisipan aplikasi Anda. Anda dapat meneruskan "tambahan" data dalam intent ini, yang akan
digunakan untuk mengisi otomatis UI. Karena acara berulang memiliki
{i>syntax<i} yang kompleks, metode
cara menyisipkan acara ke dalam Penyedia Kalender adalah dengan mengaktifkan aplikasi Kalender dengan
ACTION_INSERT
, lalu biarkan pengguna menyisipkan peristiwa di sana.
Menampilkan data menggunakan aplikasi bantuan
Jika aplikasi Anda memiliki izin akses, Anda mungkin masih menggunakan
untuk menampilkan data dalam aplikasi lain. Misalnya, aplikasi Kalender menerima
Intent ACTION_VIEW
yang menampilkan tanggal atau peristiwa tertentu.
Hal ini memungkinkan Anda menampilkan informasi kalender tanpa harus membuat UI sendiri.
Untuk mempelajari fitur ini lebih lanjut, lihat
Ringkasan penyedia kalender.
Aplikasi yang Anda kirimi intent tidak harus aplikasi
yang terkait dengan penyedia. Misalnya, Anda dapat mengambil kontak dari
Penyedia Kontak, lalu mengirimkan intent ACTION_VIEW
berisi URI konten untuk gambar kontak tersebut ke penampil gambar.
Kelas kontrak
Kelas kontrak menentukan konstanta yang membantu aplikasi menggunakan URI konten, nama
kolom, tindakan intent, dan fitur lain pada penyedia konten. Kelas kontrak tidak
disertakan secara otomatis dengan penyedia. Pengembang penyedia harus menentukannya dan kemudian
membuatnya tersedia untuk pengembang lain. Sejumlah penyedia yang disertakan pada platform Android
memiliki kelas kontrak yang sesuai dalam paket android.provider
.
Misalnya, Penyedia Kamus Pengguna memiliki UserDictionary
kelas kontrak
yang berisi URI konten dan konstanta nama kolom. Tujuan
URI konten untuk tabel Words
ditentukan dalam konstanta
UserDictionary.Words.CONTENT_URI
.
Class UserDictionary.Words
juga berisi konstanta nama kolom,
yang digunakan dalam contoh cuplikan di panduan ini. Misalnya, proyeksi kueri dapat
didefinisikan seperti berikut:
Kotlin
val projection : Array<String> = arrayOf( UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE )
Java
String[] projection = { UserDictionary.Words._ID, UserDictionary.Words.WORD, UserDictionary.Words.LOCALE };
Kelas kontrak lain adalah ContactsContract
untuk Penyedia Kontak.
Dokumentasi referensi untuk class ini menyertakan contoh cuplikan kode. Salah satu
subclass-nya, ContactsContract.Intents.Insert
, adalah kelas
kontrak yang berisi konstanta untuk intent dan data intent.
Referensi jenis MIME
Penyedia konten dapat menampilkan jenis media MIME standar, string jenis MIME kustom, atau keduanya.
Jenis MIME memiliki format berikut:
type/subtype
Misalnya, jenis MIME text/html
yang terkenal memiliki jenis text
dan
subjenis html
. Jika penyedia mengembalikan tipe ini untuk sebuah URI, itu berarti bahwa sebuah
kueri yang menggunakan URI tersebut akan menampilkan teks yang berisi tag HTML.
String jenis MIME kustom, yang juga disebut jenis MIME khusus vendor, memiliki lebih nilai type dan subtype yang kompleks. Untuk beberapa baris, nilai jenisnya selalu berikut ini:
vnd.android.cursor.dir
Untuk satu baris, nilai jenisnya selalu berikut ini:
vnd.android.cursor.item
subtype bersifat khusus penyedia. Penyedia bawaan Android biasanya memiliki subjenis sederhana. Misalnya, saat aplikasi Kontak membuat baris untuk nomor telepon, aplikasi akan menyetel jenis MIME berikut di baris tersebut:
vnd.android.cursor.item/phone_v2
Nilai subjenisnya adalah phone_v2
.
Developer penyedia lain bisa membuat pola subjenis sendiri berdasarkan
{i>authority<i} dan nama tabel. Misalnya, perhatikan penyedia yang berisi jadwal kereta api.
Otoritas penyedianya adalah com.example.trains
, dan berisi tabel
Line1, Line2, dan Line3. Sebagai respons terhadap URI konten berikut untuk tabel Line1:
content://com.example.trains/Line1
penyedia akan mengembalikan tipe MIME berikut:
vnd.android.cursor.dir/vnd.example.line1
Sebagai respons terhadap URI konten berikut untuk baris 5 dalam tabel Line2:
content://com.example.trains/Line2/5
penyedia akan mengembalikan tipe MIME berikut:
vnd.android.cursor.item/vnd.example.line2
Sebagian besar penyedia konten menentukan konstanta kelas kontrak untuk jenis MIME yang digunakannya. Kelas kontrak
ContactsContract.RawContacts
pada Penyedia Kontak
misalnya, menentukan konstanta
CONTENT_ITEM_TYPE
untuk jenis MIME
baris kontak mentah tunggal.
URI konten untuk baris tunggal dijelaskan dalam URI konten.