Dasar-dasar penyedia konten

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 pada intinya dimaksudkan untuk digunakan oleh aplikasi lain, yang mengakses penyedia tersebut menggunakan objek klien penyedia. Penyedia dan klien bersama-sama menawarkan antarmuka standar yang konsisten untuk data yang juga menangani komunikasi antar-proses dan akses data yang aman.

Biasanya, penyedia konten digunakan dalam salah satu dari dua skenario, yaitu Anda mungkin ingin menerapkan kode untuk mengakses penyedia konten yang ada di aplikasi lain, atau Anda mungkin ingin membuat penyedia konten baru di aplikasi Anda untuk berbagi data dengan aplikasi lain. Topik ini membahas dasar-dasar penggunaan penyedia konten yang ada. Untuk mempelajari penerapan penyedia konten di aplikasi Anda lebih lanjut, lihat Membuat 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 sejumlah API dan komponen yang berbeda seperti ditunjukkan dalam gambar 1, yang meliputi:

  • 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
Hubungan antara penyedia konten dan komponen lain.

Gambar 1. Hubungan antara penyedia konten dan komponen lain.

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 dengan nama identik dalam objek penyedia, yaitu instance dari salah satu subclass konkret untuk ContentProvider. Metode ContentResolver menyediakan fungsi dasar "CRUD" (membuat, mengambil, memperbarui, dan menghapus) penyimpanan persisten.

Pola umum untuk mengakses ContentProvider dari UI Anda menggunakan CursorLoader untuk menjalankan kueri asinkron di latar belakang. Activity atau Fragment di UI memanggil CursorLoader ke kueri, yang kemudian mendapatkan 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.

Interaksi antara ContentProvider, class lain, dan penyimpanan.

Gambar 2. Interaksi antara ContentProvider, class lain, dan penyimpanan.

Catatan: Untuk mengakses penyedia, aplikasi Anda biasanya harus meminta izin tertentu dalam file manifes. Pola pengembangan ini dijelaskan secara rinci di bagian Izin Penyedia Konten.

Salah satu penyedia bawaan di platform Android adalah kamus pengguna, yang menyimpan ejaan kata-kata tidak standar yang ingin disimpan pengguna. Tabel 1 menunjukkan seperti apa data tersebut di tabel penyedia ini:

Tabel 1: Contoh tabel kamus pengguna.

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

Dalam tabel 1, setiap baris menggambarkan instance sebuah kata yang mungkin tidak terdapat di dalam kamus standar. Setiap kolom menggambarkan beberapa data untuk kata tersebut, seperti lokal tempat kata tersebut pertama kali ditemukan. Header kolom merupakan nama-nama kolom yang disimpan di dalam penyedia. Untuk melihat lokal baris, lihat kolom locale. Untuk penyedia ini, kolom _ID berfungsi sebagai kolom "kunci utama" yang otomatis disimpan oleh penyedia.

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 user dictionary 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 user dictionary 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 ke query(Uri,projection,selection,selectionArgs,sortOrder) cocok dengan pernyataan SQL SELECT:

Tabel 2: Query() dibandingkan dengan kueri SQL.

Argumen query() Kata kunci/parameter SELECT Catatan
Uri FROM table_name Uri memetakan ke tabel dalam penyedia yang bernama table_name.
projection col,col,col,... projection adalah array kolom yang harus disertakan untuk setiap baris yang 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 mencakup nama simbolis seluruh penyedia (otoritasnya) 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.

Di baris kode sebelumnya, konstanta CONTENT_URI berisi URI konten dari tabel "kata" kamus pengguna. Objek ContentResolver mengurai otoritas URI dan menggunakannya untuk "me-resolve" penyedia dengan membandingkan otoritas dengan tabel sistem dari penyedia yang dikenal. Kemudian, ContentResolver dapat mengirimkan argumen kueri ke penyedia yang tepat.

ContentProvider menggunakan bagian jalur URI konten untuk memilih tabel yang akan diakses. Penyedia biasanya memiliki jalur untuk setiap tabel yang ditampilkan.

Pada baris kode sebelumnya, URI lengkap untuk tabel "kata" adalah:

content://user_dictionary/words

dengan string user_dictionary adalah otoritas penyedia, dan string words adalah jalur tabel. String content:// (skema) selalu ada, dan mengidentifikasi ini sebagai URI konten.

Banyak penyedia yang memungkinkan Anda mengakses satu baris dalam sebuah tabel dengan menambahkan nilai ID ke bagian akhir URI. Misalnya, untuk mengambil baris yang _ID-nya adalah 4 dari 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 telah 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. Class ContentUris berisi metode sederhana untuk menambahkan nilai ID ke URI. Cuplikan sebelumnya menggunakan withAppendedId() untuk menambahkan ID ke URI konten UserDictionary.

Mengambil data dari penyedia

Bagian ini menjelaskan cara mengambil data dari penyedia, menggunakan Penyedia Kamus Pengguna sebagai contoh.

Agar jelas, cuplikan kode di bagian ini memanggil ContentResolver.query() pada "UI thread". Namun, dalam kode sesungguhnya, Anda harus melakukan kueri secara asinkron pada thread terpisah. Salah satu cara melakukannya adalah dengan menggunakan class CursorLoader, yang dijelaskan secara rinci dalam panduan Loader. Selain itu, baris kode tersebut hanyalah cuplikan dan tidak menunjukkan aplikasi lengkap.

Untuk mengambil data dari penyedia, ikuti langkah-langkah dasar berikut:

  1. Minta izin akses baca untuk penyedia.
  2. Tentukan kode yang mengirim kueri ke penyedia.

Meminta izin akses baca

Untuk mengambil data dari penyedia, aplikasi memerlukan "izin akses baca" untuk penyedia. Anda tidak dapat meminta izin ini saat dijalankan. Sebagai gantinya, Anda harus menentukan bahwa Anda memerlukan izin ini dalam manifes, menggunakan elemen <uses-permission> dan nama izin persis yang ditetapkan oleh penyedia. Saat menentukan elemen ini dalam manifes, Anda sebenarnya "meminta" izin 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 rinci di 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 kode pertama ini menentukan beberapa variabel untuk mengakses Penyedia Kamus Pengguna:

Kotlin

// A "projection" defines the columns that will be 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 will be 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 ditampilkan, satu set kriteria pemilihan, dan urutan penyusunan.

Set kolom yang harus ditampilkan kueri disebut dengan proyeksi (mProjection variabel).

Ekspresi yang menentukan baris yang harus diambil dibagi menjadi klausa pemilihan dan argumen pemilihan. Klausa pemilihan adalah kombinasi dari ekspresi logis dan Boolean, nama kolom, dan nilai (variabel mSelectionClause). Jika Anda menentukan parameter ? yang dapat diganti, bukannya nilai, metode kueri akan mengambil nilai dari array argumen pemilihan (variabel mSelectionArgs).

Dalam cuplikan berikutnya, jika pengguna tidak memasukkan sebuah 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 String array to contain the selection arguments.
 */
private lateinit var selectionArgs: Array<String>

// Gets a word from the UI
searchString = searchWord.text.toString()

// Remember to 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 may want to call android.util.Log.e() to log this error.
         *
         */
    }
    0 -> {
        /*
         * Insert code here to notify the user that the search was unsuccessful. This isn't
         * necessarily an error. You may 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 will return 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 may want to
     * 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 was unsuccessful. This isn't necessarily
     * an error. You may 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

}

Kueri ini merupakan serupa dengan Pernyataan SQL:

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, menyertakan data eksternal yang tidak tepercaya ke dalam Pernyataan SQL mentah dapat menyebabkan injeksi SQL.

Perhatikan klausa pemilihan ini:

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 melakukannya, Anda memungkinkan pengguna menyambungkan SQL yang berbahaya ke Pernyataan SQL Anda. Misalnya, pengguna dapat memasukkan "nothing; DROP TABLE *;" untuk mUserInput, yang akan menghasilkan klausa pemilihan var = nothing; DROP TABLE *;. Karena klausa pemilihan diperlakukan sebagai pernyataan SQL, hal ini dapat menyebabkan penyedia tersebut menghapus semua tabel dalam database SQLite yang mendasarinya (kecuali penyedia siap menangkap upaya injeksi SQL).

Untuk menghindari masalah ini, gunakan klausa pemilihan yang menggunakan ? sebagai parameter yang dapat diganti dan array argumen pemilihan terpisah. Saat Anda melakukannya, input pengguna akan dibatasi secara langsung pada kueri agar tidak 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 serta array argumen pemilihan merupakan cara yang lebih sering digunakan untuk menentukan pemilihan, meskipun penyedia tidak dibuat berdasarkan database 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 baris dalam hasil, menentukan jenis data dalam setiap kolom, mengambil data dari kolom, dan memeriksa properti lain dari hasil. Beberapa implementasi Cursor memperbarui objek secara otomatis saat data penyedia berubah, atau memicu metode dalam objek pengamat saat Cursor berubah, atau keduanya.

Catatan: Penyedia dapat membatasi akses ke kolom berdasarkan sifat objek yang membuat kueri. Misalnya, Penyedia Kontak membatasi akses ke adaptor sinkronisasi untuk beberapa kolom, sehingga kolom tersebut tidak akan ditampilkan pada aktivitas atau layanan.

Jika tidak ada baris yang cocok dengan kriteria pemilihan, penyedia akan menampilkan objek Cursor dengan Cursor.getCount() adalah 0 (kursor kosong).

Jika terjadi kesalahan internal, hasil kueri akan bergantung pada penyedia tertentu. Penyedia dapat memilih untuk menampilkan null, atau memunculkan Exception.

Karena Cursor adalah "daftar" baris, cara yang tepat untuk menampilkan konten Cursor adalah dengan menautkannya ke ListView melalui SimpleCursorAdapter.

Cuplikan berikut melanjutkan kode dari cuplikan sebelumnya. Cuplikan ini membuat objek SimpleCursorAdapter 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 will 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 will 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. Karena itu, kueri yang ditampilkan sebelumnya mengambil kolom _ID untuk tabel "kata", meskipun ListView tidak menampilkannya. Pembatasan ini juga menjelaskan alasan sebagian besar penyedia memuat kolom _ID pada masing-masing tabelnya.

Mendapatkan data dari hasil kueri

Selain menampilkannya, Anda juga dapat menggunakan hasil kueri untuk tugas-tugas lain. Misalnya, Anda dapat mengambil ejaan dari kamus pengguna, kemudian mencarinya dalam penyedia lain. Caranya, lakukan iterasi baris dalam Cursor:

Kotlin

/*
* Only executes if the cursor is valid. The User Dictionary Provider returns null if
* an internal error occurs. Other providers may 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 will 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 may 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 will 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.

Izin penyedia konten

Aplikasi penyedia dapat menentukan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia. Izin ini memastikan bahwa pengguna mengetahui data yang coba diakses oleh 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 menentukan izin apa pun, aplikasi lain tidak akan memiliki akses ke data penyedia. Namun, komponen dalam aplikasi penyedia selalu memiliki akses baca dan tulis penuh, terlepas dari izin apa pun yang ditentukan.

Seperti yang disebutkan sebelumnya, Penyedia Kamus Pengguna memerlukan izin android.permission.READ_USER_DICTIONARY untuk mengambil data darinya. Penyedia memiliki izin android.permission.WRITE_USER_DICTIONARY terpisah untuk menyisipkan, memperbarui, atau menghapus data.

Guna mendapatkan izin yang diperlukan untuk mengakses penyedia, aplikasi memintanya dengan elemen <uses-permission> dalam file manifesnya. Saat Pengelola Paket Android menginstal aplikasi, pengguna harus menyetujui semua izin yang diminta aplikasi. Jika pengguna menyetujui semua izin, Pengelola Paket melanjutkan penginstalan. Namun, jika pengguna tidak menyetujuinya, Pengelola Paket membatalkan penginstalan.

Elemen <uses-permission> berikut meminta akses baca ke Penyedia Kamus Pengguna:

<uses-permission android:name="android.permission.READ_USER_DICTIONARY">

Dampak dari izin pada akses penyedia dijelaskan secara terperinci dalam panduan Keamanan dan izin.

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 klien penyedia secara otomatis menangani keamanan dan komunikasi antarproses.

Menyisipkan 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 ini menampilkan cara menyisipkan sebuah 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 user dictionary 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 user dictionary 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 ini tidak menambahkan kolom _ID, karena kolom ini disimpan ecara 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 dalam newUri akan mengidentifikasi baris yang baru ditambahkan, dengan 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 sebuah baris, gunakan objek ContentValues dengan nilai yang diperbarui, persis seperti yang Anda lakukan pada penyisipan, dan kriteria pemilihan persis seperti yang Anda lakukan 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" ke lokal 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 user dictionary 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 user dictionary content URI
    updateValues,                      // the columns to update
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

Anda juga harus membersihkan input pengguna saat memanggil ContentResolver.update(). Untuk mengetahui selengkapnya tentang hal ini, baca bagian Melindungi dari input berbahaya.

Menghapus data

Menghapus baris serupa dengan mengambil data baris: Anda menentukan kriteria pemilihan untuk baris yang ingin Anda hapus dan metode klien akan menampilkan jumlah baris yang dihapus. Cuplikan berikut akan menghapus baris yang appid-nya 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.LOCALE} 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 user dictionary 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 user dictionary content URI
    selectionClause,                   // the column to select on
    selectionArgs                      // the value to compare to
);

Anda juga harus membersihkan input pengguna saat memanggil ContentResolver.delete(). Untuk mengetahui selengkapnya tentang hal 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)

Jenis data lain yang sering digunakan penyedia adalah Binary Large OBject (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 kontrak UserDictionary.Words-nya (kelas kontrak 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 menggunakan informasi jenis MIME untuk mengetahui apakah aplikasi Anda dapat menangani data yang disediakan penyedia, atau memilih jenis penanganan berdasarkan jenis MIME. Anda biasanya memerlukan jenis MIME saat menggunakan penyedia yang berisi struktur atau file data yang kompleks. 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 sintaks jenis MIME standar maupun khusus.

Bentuk-bentuk alternatif akses penyedia

Tiga bentuk alternatif akses penyedia adalah hal penting dalam development aplikasi:

  • Akses batch: Anda dapat membuat sebuah batch panggilan akses dengan metode dalam class ContentProviderOperation, kemudian menerapkannya dengan ContentResolver.applyBatch().
  • Kueri asinkron: Sebaiknya lakukan kueri dalam thread terpisah. Salah satu cara melakukannya adalah dengan menggunakan objek CursorLoader. Contoh dalam panduan Loader menunjukkan cara melakukannya.
  • Akses data melalui intent: Meskipun tidak dapat mengirim intent ke penyedia secara langsung, Anda dapat mengirim intent ke aplikasi penyedia, yang biasanya memiliki alat terbaik untuk memodifikasi data penyedia.

Akses batch dan modifikasi melalui intent dijelaskan dalam bagian-bagian berikut.

Akses batch

Akses batch ke penyedia berguna untuk menyisipkan baris dalam jumlah besar, atau menyisipkan baris ke dalam beberapa tabel dalam panggilan metode yang sama, atau biasanya melakukan satu set operasi di seluruh batas proses sebagai transaksi (operasi atomik).

Untuk mengakses penyedia dalam "mode batch", buat array objek ContentProviderOperation, lalu kirim ke penyedia konten dengan ContentResolver.applyBatch(). Anda meneruskan otoritas penyedia konten ke metode ini, bukan URI konten tertentu. Ini memungkinkan setiap objek ContentProviderOperation dalam array tersebut digunakan pada tabel yang berbeda. Panggilan ke ContentResolver.applyBatch() menampilkan array hasil.

Deskripsi kelas kontrak ContactsContract.RawContacts menyertakan cuplikan kode yang menunjukkan penyisipan batch. Aplikasi contoh Pengelola Kontak berisi contoh akses batch dalam file sumber ContactAdder.java-nya.

Akses data melalui intent

Intent dapat menyediakan akses tidak langsung ke penyedia konten. Anda mengizinkan pengguna mengakses data dalam penyedia meskipun aplikasi tidak memiliki izin akses, baik dengan mendapatkan intent yang dihasilkan aplikasi yang memiliki izin, atau dengan mengaktifkan aplikasi yang memiliki izin dan mengizinkan pengguna menggunakannya.

Mendapatkan akses dengan izin sementara

Anda dapat mengakses data di penyedia konten, meskipun tidak memiliki izin akses yang sesuai, dengan mengirimkan intent ke aplikasi yang memiliki izin tersebut 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:

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.

Penyedia menentukan izin URI untuk URI konten dalam manifesnya, menggunakan atribut android:grantUriPermission dari elemen <provider>, serta elemen turunan <grant-uri-permission> dari elemen <provider>. Mekanisme izin URI dijelaskan secara rinci dalam panduan Ringkasan izin.

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. Sebagai ganti meminta READ_CONTACTS, yang memberi Anda akses ke semua kontak pengguna beserta informasinya, sebaiknya izinkan pengguna mengontrol kontak yang akan digunakan oleh aplikasi Anda. Caranya, gunakan proses berikut:

  1. Aplikasi mengirimkan intent berisi tindakan ACTION_PICK dan jenis MIME "contacts" CONTENT_ITEM_TYPE, menggunakan metode startActivityForResult().
  2. Karena intent ini cocok dengan filter intent untuk aktivitas "selection" pada aplikasi Orang, aktivitas akan muncul di latar depan.
  3. 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 dari kontak yang dipilih pengguna dan tanda "extras" FLAG_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 memanggil finish() untuk menampilkan kontrol ke aplikasi.
  4. 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.
  5. 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. Kemudian, Anda bisa mendapatkan informasi hari ulang tahun alamat email kontak tersebut, dan mengirim kartu ucapan elektronik.

Menggunakan aplikasi lain

Cara yang mudah untuk memungkinkan pengguna memodifikasi data yang tidak Anda miliki izin aksesnya adalah dengan mengaktifkan aplikasi yang memiliki izin dan mengizinkan pengguna melakukannya sendiri.

Misalnya, aplikasi Kalender menerima intent ACTION_INSERT, yang memungkinkan Anda mengaktifkan UI penyisipan aplikasi tersebut. Anda dapat meneruskan data "extras" dalam intent ini, yang digunakan aplikasi untuk mengisi UI-nya terlebih dahulu. Karena acara rutin memiliki sintaks yang kompleks, cara yang lebih sering digunakan untuk menyisipkan acara ke dalam Penyedia Kalender adalah dengan mengaktifkan aplikasi Kalender menggunakan ACTION_INSERT, lalu mengizinkan pengguna menyisipkan acara di sana.

Menampilkan data dengan aplikasi penunjang

Jika aplikasi Anda memiliki izin akses, Anda mungkin perlu menggunakan intent untuk menampilkan data dalam aplikasi lain. Misalnya, aplikasi Kalender menerima intent ACTION_VIEW, yang menampilkan tanggal atau acara tertentu. Hal ini memungkinkan Anda menampilkan informasi kalender tanpa harus membuat UI sendiri. Untuk mengetahui selengkapnya tentang fitur ini, lihat panduan 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 bersama penyedia. Developer penyedia harus menentukannya, lalu menyediakannya bagi developer 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. URI konten untuk tabel "kata" ditentukan dalam konstanta UserDictionary.Words.CONTENT_URI. Class UserDictionary.Words juga berisi konstanta nama kolom, yang digunakan dalam cuplikan contoh pada panduan ini. Misalnya, sebuah proyeksi kueri dapat ditentukan sebagai:

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, atau string jenis MIME khusus, atau keduanya.

Jenis MIME memiliki format

type/subtype

Misalnya, jenis MIME text/html yang terkenal memiliki jenis text dan subjenis html. Jika penyedia menampilkan jenis ini untuk sebuah URI, ini berarti kueri dengan URI tersebut akan menampilkan teks berisi tag HTML.

String jenis MIME khusus, juga disebut jenis MIME "khusus vendor", memiliki nilai jenis dan subjenis yang lebih kompleks. Nilai jenis selalu berupa

vnd.android.cursor.dir

untuk beberapa baris, atau

vnd.android.cursor.item

untuk satu baris.

Subjenis 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

Perhatikan bahwa nilai subjenisnya adalah phone_v2.

Developer penyedia lain dapat membuat pola subjenis sendiri berdasarkan otoritas dan nama tabel penyedia. Misalnya, perhatikan penyedia yang berisi jadwal kereta api. Otoritas penyedianya adalah com.example.trains, dan berisi tabel Line1, Line2, dan Line3. Untuk merespons URI konten

content://com.example.trains/Line1

untuk tabel Line1, penyedia menampilkan jenis MIME

vnd.android.cursor.dir/vnd.example.line1

Untuk merespons URI konten

content://com.example.trains/Line2/5

untuk baris 5 di tabel Line2, penyedia menampilkan jenis MIME

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 di bagian URI Konten.