page.title=Dasar-Dasar Penyedia Konten @jd:body

Dalam dokumen ini

  1. Ikhtisar
    1. Mengakses penyedia
    2. URI Konten
  2. Mengambil Data dari Penyedia
    1. Meminta izin akses baca
    2. Membuat query
    3. Menampilkan hasil query
    4. Mendapatkan data dari hasil query
  3. Izin Penyedia Konten
  4. Menyisipkan, Memperbarui, dan Menghapus Data
    1. Menyisipkan data
    2. Memperbarui data
    3. Menghapus data
  5. Tipe Data Penyedia
  6. Bentuk-Bentuk Alternatif Akses Penyedia
    1. Akses batch
    2. Akses data melalui intent
  7. Kelas-kelas Kontrak
  8. Acuan Tipe MIME

Kelas-kelas utama

  1. {@link android.content.ContentProvider}
  2. {@link android.content.ContentResolver}
  3. {@link android.database.Cursor}
  4. {@link android.net.Uri}

Contoh-Contoh Terkait

  1. Kursor (Orang)
  2. Kursor (Telepon)

Lihat juga

  1. Membuat Penyedia Konten
  2. Penyedia Kalender

Penyedia konten mengelola akses ke repository data pusat. Penyedia adalah bagian dari aplikasi Android, yang sering menyediakan UI-nya sendiri untuk menggunakan data. Akan tetapi, penyedia konten terutama dimaksudkan untuk digunakan oleh aplikasi lain, yang mengakses penyedia itu melalui objek klien penyedia. Bersama-sama, penyedia dan klien penyedia menawarkan antarmuka standar yang konsisten ke data yang juga menangani komunikasi antar-proses dan akses data aman.

Topik ini menerangkan dasar-dasar dari hal-hal berikut:

Ikhtisar

Penyedia konten menyajikan data ke aplikasi eksternal sebagai satu atau beberapa tabel yang serupa dengan tabel-tabel yang ditemukan dalam database relasional. Sebuah baris mewakili instance beberapa tipe data yang dikumpulkan penyedia, dan tiap kolom dalam baris mewakili sepotong data yang dikumpulkan untuk sebuah instance.

Misalnya, salah satu penyedia bawaan di platform Android adalah kamus pengguna, yang menyimpan ejaan kata-kata tidak-standar yang ingin disimpan pengguna. Tabel 1 mengilustrasikan wujud data yang mungkin ada dalam 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, tiap baris mewakili instance sebuah kata yang mungkin tidak ditemukan dalam kamus standar. Tiap kolom mewakili beberapa data untuk kata itu, misalnya bahasa lokal tempat kata itu ditemukan kali pertama. Header kolom adalah nama kolom yang disimpan dalam penyedia. Untuk mengacu ke bahasa lokal suatu baris, Anda mengacu ke kolom locale-nya. Untuk penyedia ini, kolom _ID berfungsi sebagai "kunci utama" kolom yang dipelihara oleh penyedia secara otomatis.

Catatan: Penyedia tidak diharuskan memiliki kunci utama, dan tidak diharuskan menggunakan _ID sebagai nama kolom kunci utama jika kunci itu ada. Akan tetapi, jika Anda ingin mengikat data dari penyedia ke {@link android.widget.ListView}, salah satu nama kolom harus _ID. Ketentuan ini dijelaskan secara lebih detail di bagian Menampilkan hasil query.

Mengakses penyedia

Aplikasi mengakses data dari penyedia konten dengan sebuah objek klien {@link android.content.ContentResolver}. Objek ini memiliki metode yang memanggil metode dengan nama identik dalam objek penyedia, instance salah satu subkelas konkret dari {@link android.content.ContentProvider}. Metode-metode {@link android.content.ContentResolver} menyediakan fungsi-fungsi dasar "CRUD" (create, retrieve, update, dan delete) pada penyimpanan yang persisten.

Objek {@link android.content.ContentResolver} dalam proses aplikasi klien dan objek {@link android.content.ContentProvider} dalam aplikasi yang memiliki penyedia itu secara otomatis akan menangani komunikasi antar-proses. {@link android.content.ContentProvider} juga berfungsi sebagai lapisan abstraksi antara repository datanya dan penampilan eksternal data sebagai tabel.

Catatan: Untuk mengakses penyedia, aplikasi Anda biasanya harus meminta izin tertentu dalam file manifesnya. Hal ini dijelaskan lebih detail di bagian Izin Penyedia Konten

Misalnya, untuk mendapatkan daftar kata dan lokalnya dari Penyedia Kamus Pengguna, Anda memanggil {@link android.content.ContentResolver#query ContentResolver.query()}. Metode {@link android.content.ContentResolver#query query()} memanggil metode {@link android.content.ContentProvider#query ContentProvider.query()} yang didefinisikan oleh Penyedia Kamus Pengguna. Baris-baris kode berikut menunjukkan sebuah panggilan {@link android.content.ContentResolver#query ContentResolver.query()}:

// Queries the user dictionary and returns results
mCursor = getContentResolver().query(
    UserDictionary.Words.CONTENT_URI,   // The content URI of the words table
    mProjection,                        // The columns to return for each row
    mSelectionClause                    // Selection criteria
    mSelectionArgs,                     // Selection criteria
    mSortOrder);                        // The sort order for the returned rows

Tabel 2 menampilkan cara argumen untuk {@link android.content.ContentResolver#query query(Uri,projection,selection,selectionArgs,sortOrder)} cocok dengan sebuah pernyataan SELECT di SQL:

Tabel 2: Query() dibandingkan dengan query 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 satu larik kolom yang harus disertakan untuk tiap baris yang diambil.
selection WHERE col = value selection menetapkan kriteria untuk memilih baris.
selectionArgs (Tidak ada padanan persis. Argumen pemilihan mengganti ? placeholder dalam klausa pemilihan.)
sortOrder ORDER BY col,col,... sortOrder menetapkan urutan munculnya baris dalam {@link android.database.Cursor} yang dihasilkan.

URI Konten

URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI Konten menyertakan nama simbolis seluruh penyedia (otoritasnya) dan sebuah nama yang menunjuk ke tabel (path). Bila Anda memanggil metode klien untuk mengakses tabel dalam penyedia, URI konten untuk tabel itu adalah salah satu argumennya.

Dalam baris kode sebelumnya, konstanta {@link android.provider.UserDictionary.Words#CONTENT_URI} mengandung URI konten dari tabel "words" kamus pengguna. Objek {@link android.content.ContentResolver} akan mengurai otoritas URI, dan menggunakannya untuk "mengetahui" penyedia dengan membandingkan otoritas tersebut dengan sebuah tabel sistem berisi penyedia yang dikenal. {@link android.content.ContentResolver} kemudian bisa mengirim argumen query ke penyedia yang benar.

{@link android.content.ContentProvider} menggunakan bagian path dari URI konten untuk memilih tabel yang akan diakses. Penyedia biasanya memiliki path untuk tiap tabel yang dieksposnya.

Dalam baris kode sebelumnya, URI lengkap untuk tabel "words" adalah:

content://user_dictionary/words

dalam hal ini string user_dictionary adalah otoritas penyedia, dan string words adalah path tabel. String content:// (skema) selalu ada, dan mengidentifikasinya sebagai URI konten.

Banyak penyedia yang memperbolehkan Anda mengakses satu baris dalam tabel dengan menambahkan sebuah ID nilai ke akhir URI. Misalnya, untuk mengambil sebuah baris yang _ID-nya adalah 4 dari kamus pengguna, Anda bisa menggunakan URI konten ini:

Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4);

Anda akan sering menggunakan nilai-nilai ID bila telah mengambil satu set baris kemudian ingin memperbarui atau menghapus salah satunya.

Catatan: Kelas-kelas {@link android.net.Uri} dan {@link android.net.Uri.Builder} berisi metode praktis untuk membangun objek dari string URI yang tersusun dengan baik. {@link android.content.ContentUris} berisi metode praktis untuk menambahkan nilai ID ke URI. Cuplikan kode sebelumnya menggunakan {@link android.content.ContentUris#withAppendedId withAppendedId()} untuk menambahkan id ke URI konten User Dictionary.

Mengambil Data dari Penyedia

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

Demi kejelasan, cuplikan kode di bagian ini memanggil {@link android.content.ContentResolver#query ContentResolver.query()} pada "UI thread"". Akan tetapi, dalam kode sesungguhnya, Anda harus melakukan query secara asinkron pada sebuah thread terpisah. Satu cara melakukannya adalah menggunakan kelas {@link android.content.CursorLoader}, yang dijelaskan lebih detail dalam panduan Loader. Juga, baris-baris kode tersebut hanyalah cuplikan; tidak menunjukkan sebuah aplikasi lengkap.

Untuk mengambil data dari penyedia, ikutilah langkah-langkah dasar ini:

  1. Minta izin akses baca untuk penyedia itu.
  2. Definisikan kode yang mengirim query ke penyedia.

Meminta izin akses baca

Untuk mengambil data dari penyedia, aplikasi Anda memerlukan "izin akses baca" untuk penyedia itu. Anda tidak bisa meminta izin ini saat runtime; sebagai gantinya, Anda harus menetapkan bahwa Anda memerlukan izin ini dalam manifes, dengan menggunakan elemen <uses-permission> dan nama persis izin yang didefinisikan oleh penyedia itu. Bila menetapkan elemen ini dalam manifes, Anda secara efektif "meminta" izin ini untuk aplikasi Anda. Bila pengguna menginstal aplikasi Anda, mereka secara implisit akan memberikan permintaan ini.

Untuk menemukan nama persis dari izin akses baca untuk penyedia yang sedang Anda gunakan, serta nama-nama izin akses lain yang digunakan oleh penyedia, lihatlah dalam dokumentasi penyedia.

Peran izin dalam yang mengakses penyedia dijelaskan lebih detail di bagian Izin Penyedia Konten.

Penyedia Kamus Pengguna mendefinisikan izin android.permission.READ_USER_DICTIONARY dalam file manifesnya, sehingga aplikasi yang ingin membaca dari penyedia itu harus meminta izin ini.

Membuat query

Langkah berikutnya dalam mengambil data penyedia adalah membuat query. Cuplikan kode pertama ini mendefinisikan beberapa variabel untuk mengakses Penyedia Kamus Pengguna:


// 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 mSelectionClause = null;

// Initializes an array to contain selection arguments
String[] mSelectionArgs = {""};

Cuplikan berikutnya menampilkan cara menggunakan {@link android.content.ContentResolver#query ContentResolver.query()}, dengan menggunakan Penyedia Kamus Pengguna sebagai contoh. Query klien penyedia serupa dengan query SQL, dan berisi satu set kolom yang akan dihasilkan, satu set kriteria pemilihan, dan urutan sortir.

Set kolom yang harus dikembalikan query disebut dengan proyeksi (variabel mProjection).

Ekspresi yang menetapkan baris yang harus diambil dipecah menjadi klausa pemilihan dan argumen pemilihan. Klausa pemilihan adalah kombinasi ekspresi logis dan boolean, nama kolom, dan nilai (variabel mSelectionClause). Jika Anda menetapkan parameter ? yang bisa diganti, sebagai ganti nilai, metode query akan mengambil nilai dari larik argumen pemilihan (variabel mSelectionArgs).

Dalam cuplikan berikutnya, jika pengguna tidak memasukkan sebuah kata, klausa pemilihan akan diatur ke null, dan query menghasilkan semua kata dalam penyedia. Jika pengguna memasukkan sebuah kata, klausa pemilihan akan diatur ke UserDictionary.Words.WORD + " = ?" dan elemen pertama larik argumen pemilihan diatur ke kata yang dimasukkan pengguna.

/*
 * This defines a one-element String array to contain the selection argument.
 */
String[] mSelectionArgs = {""};

// Gets a word from the UI
mSearchString = mSearchWord.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(mSearchString)) {
    // Setting the selection clause to null will return all words
    mSelectionClause = null;
    mSelectionArgs[0] = "";

} else {
    // Constructs a selection clause that matches the word that the user entered.
    mSelectionClause = UserDictionary.Words.WORD + " = ?";

    // Moves the user's input string to the selection arguments.
    mSelectionArgs[0] = mSearchString;

}

// 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
    mProjection,                       // The columns to return for each row
    mSelectionClause                   // Either null, or the word the user entered
    mSelectionArgs,                    // Either empty, or the string the user entered
    mSortOrder);                       // 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

}

Query ini analog 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 merusak

Jika data dikelola oleh penyedia konten berada dalam database SQL, memasukkan data tak dipercaya eksternal ke dalam pernyataan SQL mentah bisa menyebabkan injeksi SQL.

Perhatikan klausa pemilihan ini:

// Constructs a selection clause by concatenating the user's input to the column name
String mSelectionClause =  "var = " + mUserInput;

Jika melakukannya, Anda akan membuat pengguna menyambungkan SQL merusak ke pernyataan SQL Anda. Misalnya, pengguna bisa memasukkan "nothing; DROP TABLE *;" untuk mUserInput, yang akan menghasilkan klausa pemilihan var = nothing; DROP TABLE *;. Karena klausa pemilihan diperlakukan sebagai pernyataan SQL, hal ini bisa menyebabkan penyedia itu menghapus semua tabel dalam database SQLite yang mendasarinya (kecuali penyedia disiapkan untuk menangkap upaya injeksi SQL).

Untuk menghindari masalah ini, gunakan klausa pemilihan yang menggunakan ? sebagai parameter yang bisa diganti dan larik argumen pemilihan yang terpisah. Bila Anda melakukannya, input pengguna akan dibatasi secara langsung pada query agar tidak ditafsirkan sebagai bagian dari pernyataan SQL. Karena tidak diperlakukan sebagai SQL, input pengguna tidak bisa menyuntikkan SQL merusak. Sebagai ganti menggunakan penyambungan untuk menyertakan input pengguna, gunakan klausa pemilihan ini:

// Constructs a selection clause with a replaceable parameter
String mSelectionClause =  "var = ?";

Buat larik argumen pemilihan seperti ini:

// Defines an array to contain the selection arguments
String[] selectionArgs = {""};

Masukkan nilai dalam larik argumen pemilihan seperti ini:

// Sets the selection argument to the user's input
selectionArgs[0] = mUserInput;

Sebuah klausa pemilihan yang menggunakan ? sebagai parameter yang bisa diganti dan sebuah larik argumen pemilihan adalah cara yang lebih disukai untuk menyebutkan pemilihan, sekalipun penyedia tidak dibuat berdasarkan database SQL.

Menampilkan hasil query

Metode klien {@link android.content.ContentResolver#query ContentResolver.query()} selalu menghasilkan {@link android.database.Cursor} berisi kolom-kolom yang ditetapkan oleh proyeksi query untuk baris yang cocok dengan kriteria pemilihan query. Objek {@link android.database.Cursor} menyediakan akses baca acak ke baris dan kolom yang dimuatnya. Dengan metode {@link android.database.Cursor}, Anda bisa mengulang baris-baris dalam hasil, menentukan tipe data tiap kolom, mengambil data dari kolom, dan memeriksa properti lain dari hasil. Beberapa implementasi {@link android.database.Cursor} akan memperbarui objek secara otomatis bila data penyedia berubah, atau memicu metode dalam objek pengamat bila {@link android.database.Cursor} berubah, atau keduanya.

Catatan: Penyedia bisa membatasi akses ke kolom berdasarkan sifat objek yang membuat query. Misalnya, Penyedia Kontak membatasi akses untuk beberapa kolom pada adaptor sinkronisasi, sehingga tidak akan mengembalikannya ke aktivitas atau layanan.

Jika tidak ada baris yang cocok dengan kriteria pemilihan, penyedia akan mengembalikan objek {@link android.database.Cursor} dengan {@link android.database.Cursor#getCount Cursor.getCount()} adalah 0 (kursor kosong).

Jika terjadi kesalahan internal, hasil query akan bergantung pada penyedia tertentu. Penyedia bisa memilih untuk menghasilkan null, atau melontarkan {@link java.lang.Exception}.

Karena {@link android.database.Cursor} adalah "daftar" baris, cara yang cocok untuk menampilkan konten {@link android.database.Cursor} adalah mengaitkannya dengan {@link android.widget.ListView} melalui {@link android.widget.SimpleCursorAdapter}.

Cuplikan berikut melanjutkan kode dari cuplikan sebelumnya. Cuplikan ini membuat objek {@link android.widget.SimpleCursorAdapter} berisi {@link android.database.Cursor} yang diambil oleh query, dan mengatur objek ini menjadi adaptor bagi {@link android.widget.ListView}:

// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mWordListColumns =
{
    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[] mWordListItems = { R.id.dictWord, R.id.locale};

// Creates a new SimpleCursorAdapter
mCursorAdapter = 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
    mWordListColumns,                      // A string array of column names in the cursor
    mWordListItems,                        // An integer array of view IDs in the row layout
    0);                                    // Flags (usually none are needed)

// Sets the adapter for the ListView
mWordList.setAdapter(mCursorAdapter);

Catatan: Untuk mendukung {@link android.widget.ListView} dengan {@link android.database.Cursor}, kursor harus berisi kolom bernama _ID. Karena itu, query yang ditampilkan sebelumnya mengambil kolom _ID untuk tabel "words", walaupun {@link android.widget.ListView} tidak menampilkannya. Pembatasan ini juga menjelaskan mengapa sebagian besar penyedia memiliki kolom _ID untuk masing-masing tabelnya.

Mendapatkan data dari hasil query

Daripada sekadar menampilkan hasil query, Anda bisa menggunakannya untuk tugas-tugas lain. Misalnya, Anda bisa mengambil ejaan dari kamus pengguna kemudian mencarinya dalam penyedia lain. Caranya, ulangi baris-baris dalam {@link android.database.Cursor}:


// 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 {@link android.database.Cursor} berisi beberapa metode "get" untuk mengambil berbagai tipe data dari objek. Misalnya, cuplikan sebelumnya menggunakan {@link android.database.Cursor#getString getString()}. Implementasi juga memiliki metode {@link android.database.Cursor#getType getType()} yang menghasilkan nilai yang menunjukkan tipe data kolom.

Izin Penyedia Konten

Aplikasi penyedia bisa menetapkan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia. Izin ini akan memastikan bahwa pengguna mengetahui data yang coba diakses oleh aplikasi. Berdasarkan ketentuan 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. Akan tetapi, komponen-komponen dalam aplikasi penyedia selalu memiliki akses penuh untuk baca dan tulis, izin apa pun yang ditetapkan.

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

Untuk mendapatkan izin yang diperlukan untuk mengakses penyedia, aplikasi memintanya dengan elemen <uses-permission> dalam file manifesnya. Bila Android Package Manager memasang aplikasi, pengguna harus menyetujui semua izin yang diminta aplikasi. Jika pengguna menyetujui semuanya, Package Manager akan melanjutkan instalasi; jika pengguna tidak menyetujui, Package Manager akan membatalkan instalasi.

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

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

Dampak izin pada akses penyedia dijelaskan secara lebih detail dalam panduan Keamanan dan Izin.

Menyisipkan, Memperbarui, dan Menghapus Data

Lewat cara yang sama dengan cara mengambil data dari penyedia, Anda juga menggunakan interaksi antara klien penyedia dan {@link android.content.ContentProvider} penyedia untuk memodifikasi data. Anda memanggil metode {@link android.content.ContentResolver} dengan argumen yang diteruskan ke metode {@link android.content.ContentProvider} yang sesuai. Penyedia dan klien penyedia menangani secara otomatis keamanan dan komunikasi antar-proses.

Menyisipkan data

Untuk menyisipkan data ke penyedia, Anda memanggil metode {@link android.content.ContentResolver#insert ContentResolver.insert()}. Metode ini menyisipkan sebuah baris baru ke penyedia itu dan menghasilkan URI konten untuk baris itu. Cuplikan ini menampilkan cara menyisipkan sebuah kata baru ke Penyedia Kamus Pengguna:

// Defines a new Uri object that receives the result of the insertion
Uri mNewUri;

...

// Defines an object to contain the new values to insert
ContentValues mNewValues = new ContentValues();

/*
 * Sets the values of each column and inserts the word. The arguments to the "put"
 * method are "column name" and "value"
 */
mNewValues.put(UserDictionary.Words.APP_ID, "example.user");
mNewValues.put(UserDictionary.Words.LOCALE, "en_US");
mNewValues.put(UserDictionary.Words.WORD, "insert");
mNewValues.put(UserDictionary.Words.FREQUENCY, "100");

mNewUri = getContentResolver().insert(
    UserDictionary.Word.CONTENT_URI,   // the user dictionary content URI
    mNewValues                          // the values to insert
);

Data untuk baris baru masuk ke dalam satu objek {@link android.content.ContentValues}, yang serupa bentuknya dengan kursor satu-baris. Kolom dalam objek ini tidak perlu memiliki tipe data yang sama, dan jika Anda tidak ingin menetapkan nilai sama sekali, Anda bisa mengatur kolom ke null dengan menggunakan {@link android.content.ContentValues#putNull ContentValues.putNull()}.

Cuplikan ini tidak menambahkan kolom _ID, karena kolom ini dipelihara secara otomatis. Penyedia menetapkan sebuah nilai unik _ID ke setiap baris yang ditambahkan. Penyedia biasanya menggunakan nilai ini sebagai kunci utama tabel.

URI konten yang dihasilkan 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. Kebanyakan penyedia bisa mendeteksi bentuk URI konten ini secara otomatis kemudian melakukan operasi yang diminta pada baris tersebut.

Untuk mendapatkan nilai _ID dari {@link android.net.Uri} yang dihasilkan, panggil {@link android.content.ContentUris#parseId ContentUris.parseId()}.

Memperbarui data

Untuk memperbarui sebuah baris, gunakan objek {@link android.content.ContentValues} dengan nilai-nilai yang diperbarui, persis seperti yang Anda lakukan pada penyisipan, dan kriteria pemilihan persis seperti yang Anda lakukan pada query. Metode klien yang Anda gunakan adalah {@link android.content.ContentResolver#update ContentResolver.update()}. Anda hanya perlu menambahkan nilai-nilai ke objek {@link android.content.ContentValues} untuk kolom yang sedang Anda perbarui. Jika Anda ingin membersihkan konten kolom, aturlah nilai ke null.

Cuplikan berikut mengubah semua baris yang kolom lokalnya memiliki bahasa "en" ke lokal null. Nilai hasil adalah jumlah baris yang diperbarui:

// Defines an object to contain the updated values
ContentValues mUpdateValues = new ContentValues();

// Defines selection criteria for the rows you want to update
String mSelectionClause = UserDictionary.Words.LOCALE +  "LIKE ?";
String[] mSelectionArgs = {"en_%"};

// Defines a variable to contain the number of updated rows
int mRowsUpdated = 0;

...

/*
 * Sets the updated value and updates the selected words.
 */
mUpdateValues.putNull(UserDictionary.Words.LOCALE);

mRowsUpdated = getContentResolver().update(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mUpdateValues                       // the columns to update
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Anda juga harus membersihkan input pengguna bila memanggil {@link android.content.ContentResolver#update ContentResolver.update()}. Untuk mengetahui selengkapnya tentang hal ini, bacalah bagian Melindungi dari input merusak.

Menghapus data

Menghapus baris serupa dengan mengambil baris data: Anda menetapkan kriteria pemilihan untuk baris yang ingin Anda hapus dan metode klien akan menghasilkan jumlah baris yang dihapus. Cuplikan berikut menghapus baris yang appid-nya sama dengan "user". Metode menghasilkan jumlah baris yang dihapus.


// Defines selection criteria for the rows you want to delete
String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?";
String[] mSelectionArgs = {"user"};

// Defines a variable to contain the number of rows deleted
int mRowsDeleted = 0;

...

// Deletes the words that match the selection criteria
mRowsDeleted = getContentResolver().delete(
    UserDictionary.Words.CONTENT_URI,   // the user dictionary content URI
    mSelectionClause                    // the column to select on
    mSelectionArgs                      // the value to compare to
);

Anda juga harus membersihkan input pengguna bila memanggil {@link android.content.ContentResolver#delete ContentResolver.delete()}. Untuk mengetahui selengkapnya tentang hal ini, bacalah bagian Melindungi dari input merusak.

Tipe Data Penyedia

Penyedia konten bisa menawarkan berbagai tipe data. Penyedia Kamus Pengguna hanya menawarkan teks, namun penyedia juga bisa menawarkan format berikut:

Tipe data lain yang sering digunakan penyedia adalah Binary Large OBject (BLOB) yang diimplementasikan sebagai larik byte 64 KB. Anda bisa melihat tipe data yang tersedia dengan memperhatikan metode "get" kelas {@link android.database.Cursor}.

Tipe data tiap kolom dalam penyedia biasanya tercantum dalam dokumentasinya. Tipe data untuk Penyedia Kamus Pengguna tercantum dalam dokumentasi acuan untuk kelas kontraknya {@link android.provider.UserDictionary.Words} (kelas kontrak dijelaskan di bagian Kelas-kelas Kontrak). Anda juga bisa menentukan tipe data dengan memanggil {@link android.database.Cursor#getType Cursor.getType()}.

Penyedia juga memelihara informasi tipe data MIME untuk tiap URI konten yang didefinisikannya. Anda bisa menggunakan informasi tipe MIME untuk mengetahui apakah aplikasi Anda bisa menangani data yang disediakan penyedia, atau memilih tipe penanganan berdasarkan tipe MIME. Anda biasanya memerlukan tipe MIME saat menggunakan penyedia yang berisi struktur atau file data yang kompleks. Misalnya, tabel {@link android.provider.ContactsContract.Data} dalam Penyedia Kontak menggunakan tipe MIME untuk memberi label tipe data kontak yang disimpan di tiap baris. Untuk mendapatkan tipe MIME yang sesuai dengan URI konten, panggil {@link android.content.ContentResolver#getType ContentResolver.getType()}.

Bagian Acuan Tipe MIME menerangkan sintaks tipe MIME baik yang standar maupun custom.

Bentuk-Bentuk Alternatif Akses Penyedia

Tiga bentuk alternatif akses penyedia adalah penting dalam pengembangan aplikasi:

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 lintas batas proses sebagai transaksi (operasi atomik).

Untuk mengakses penyedia dalam "mode batch", buat satu larik objek {@link android.content.ContentProviderOperation}, kemudian kirim larik itu ke penyedia konten dengan {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. Anda meneruskan otoritas penyedia konten ke metode ini, daripada URI konten tertentu. Ini memungkinkan tiap objek {@link android.content.ContentProviderOperation} dalam larik untuk bekerja terhadap tabel yang berbeda. Panggilan ke {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} menghasilkan satu larik hasil.

Keterangan kelas kontrak {@link android.provider.ContactsContract.RawContacts} menyertakan cuplikan kode yang memperagakan penyisipan batch. Contoh aplikasi Contacts Manager berisi contoh akses batch dalam file sumber ContactAdder.java-nya .

Akses data melalui intent

Intent bisa menyediakan akses tidak langsung ke penyedia konten. Anda memperbolehkan pengguna mengakses data dalam penyedia sekalipun aplikasi Anda tidak memiliki izin akses, baik dengan mendapatkan intent yang dihasilkan aplikasi yang memiliki izin, atau dengan mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaan di dalamnya.

Mendapatkan akses dengan izin sementara

Anda bisa mengakses data dalam penyedia konten, sekalipun tidak memiliki izin akses yang sesuai, dengan mengirimkan intent ke aplikasi yang memang memiliki izin dan menerima hasil berupa intent berisi izin "URI". Inilah izin untuk URI konten tertentu yang berlaku hingga aktivitas yang menerima izin selesai. Aplikasi yang memiliki izin tetap akan memberikan izin sementara dengan mengatur flag dalam intent yang dihasilkan:

Catatan: Flag ini tidak memberikan akses baca atau tulis umum ke penyedia yang otoritasnya dimuat dalam URI konten. Aksesnya hanya untuk URI itu sendiri.

Penyedia mendefinisikan izin URI untuk URI konten dalam manifesnya, dengan menggunakan atribut android:grantUriPermission dari elemen <provider> , serta elemen anak <grant-uri-permission> dari elemen <provider>. Mekanisme izin URI dijelaskan secara lebih detail dalam panduan Keamanan dan Izin, di bagian "Izin URI".

Misalnya, Anda bisa mengambil data untuk satu kontak di Penyedia Kontak, sekalipun tidak memiliki izin {@link android.Manifest.permission#READ_CONTACTS}. Anda mungkin ingin melakukan ini dalam aplikasi yang mengirim kartu ucapan elektronik ke seorang kenalan pada hari ulang tahunnya. Sebagai ganti meminta {@link android.Manifest.permission#READ_CONTACTS}, yang memberi Anda akses ke semua kontak pengguna dan semua informasinya, Anda lebih baik membiarkan pengguna mengontrol kontak-kontak yang akan digunakan oleh aplikasi Anda. Caranya, gunakan proses berikut:

  1. Aplikasi Anda akan mengirim intent berisi tindakan {@link android.content.Intent#ACTION_PICK} dan tipe MIME "contacts" {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}, dengan menggunakan metode {@link android.app.Activity#startActivityForResult startActivityForResult()}.
  2. Karena intent ini cocok dengan filter intent untuk aktivitas "pemilihan" aplikasi People, aktivitas akan muncul ke latar depan.
  3. Dalam aktivitas pemilihan, pengguna memilih sebuah kontak untuk diperbarui. Bila ini terjadi, aktivitas pemilihan akan memanggil {@link android.app.Activity#setResult setResult(resultcode, intent)} untuk membuat intent yang akan diberikan kembali ke aplikasi Anda. Intent itu berisi URI konten kontak yang dipilih pengguna, dan flag "extras" {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}. Semua flag ini memberikan izin URI ke aplikasi Anda untuk membaca data kontak yang ditunjuk oleh URI konten. Aktivitas pemilihan kemudian memanggil {@link android.app.Activity#finish()} untuk mengembalikan kontrol ke aplikasi Anda.
  4. Aktivitas Anda akan kembali ke latar depan, dan sistem memanggil metode {@link android.app.Activity#onActivityResult onActivityResult()} aktivitas Anda. Metode ini menerima intent yang dihasilkan oleh aktivitas pemilihan dalam aplikasi People.
  5. Dengan URI konten dari intent yang dihasilkan, Anda bisa membaca data kontak dari Penyedia Kontak, sekalipun Anda tidak meminta izin akses baca tetap ke penyedia dalam manifes Anda. Anda kemudian bisa mendapatkan informasi hari ulang tahun si kontak atau alamat emailnya, kemudian mengirim kartu ucapan elektronik.

Menggunakan aplikasi lain

Satu cara mudah agar pengguna bisa memodifikasi data yang izin aksesnya tidak Anda miliki adalah mengaktifkan aplikasi yang memiliki izin dan membiarkan pengguna melakukan pekerjaannya di sana.

Misalnya, aplikasi Kalender menerima intent {@link android.content.Intent#ACTION_INSERT}, yang memungkinkan Anda mengaktifkan UI penyisipan aplikasi itu. Anda bisa meneruskan data "extras" dalam intent ini, yang digunakan aplikasi untuk mengisi dahulu UI-nya. Karena kejadian berulang memiliki sintaks yang rumit, cara yang lebih disukai untuk menyisipkan kejadian ke dalam Penyedia Kalender adalah mengaktifkan aplikasi Kalender dengan {@link android.content.Intent#ACTION_INSERT}, kemudian membiarkan pengguna menyisipkan kejadian di sana.

Kelas-kelas Kontrak

Kelas kontrak mendefinisikan 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; pengembang penyedia harus mendefinisikannya kemudian membuatnya tersedia bagi pengembang lain. Banyak penyedia yang disertakan pada platform Android memiliki kelas kontrak yang sesuai dalam {@link android.provider} paketnya.

Misalnya, Penyedia Kamus Pengguna memiliki kelas kontrak {@link android.provider.UserDictionary} yang berisi URI konten dan konstanta nama kolom. URI konten untuk tabel "words" didefinisikan dalam konstanta {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. Kelas {@link android.provider.UserDictionary.Words} juga berisi konstanta nama kolom, yang digunakan dalam cuplikan contoh pada panduan ini. Misalnya, sebuah proyeksi query bisa didefinisikan sebagai:

String[] mProjection =
{
    UserDictionary.Words._ID,
    UserDictionary.Words.WORD,
    UserDictionary.Words.LOCALE
};

Kelas kontrak lain adalah {@link android.provider.ContactsContract} untuk Penyedia Kontak. Dokumentasi acuan untuk kelas ini menyertakan contoh cuplikan kode. Salah satu subkelasnya, {@link android.provider.ContactsContract.Intents.Insert}, adalah kelas kontrak yang berisi konstanta untuk intent dan data intent.

Acuan Tipe MIME

Penyedia konten bisa menghasilkan tipe media MIME standar, atau string tipe MIME custom, atau keduanya.

Tipe MIME memiliki format

type/subtype

Misalnya, tipe MIME text/html yang dikenal luas memiliki tipe text dan subtipe html. Jika penyedia menghasilkan tipe ini untuk sebuah URI, artinya query dengan URI itu akan menghasilkan teks berisi tag HTML.

String tipe MIME custom, yang juga disebut dengan tipe MIME "khusus vendor", memiliki nilai-nilai tipe dan subtipe yang lebih kompleks. Nilai tipe selalu

vnd.android.cursor.dir

untuk beberapa baris, atau

vnd.android.cursor.item

untuk satu baris.

Subtipe adalah khusus penyedia. Penyedia bawaan Android biasanya memiliki subtipe sederhana. Misalnya, bila aplikasi Contacts membuat satu baris untuk nomor telepon, aplikasi akan mengatur tipe MIME berikut di baris itu:

vnd.android.cursor.item/phone_v2

Perhatikan bahwa nilai subtipe adalah sekadar phone_v2.

Pengembang penyedia lain bisa membuat pola subtipe sendiri berdasarkan otoritas dan nama-nama tabel penyedia. Misalnya, perhatikan penyedia yang berisi jadwal kereta api. Otoritas penyedia adalah com.example.trains, dan berisi tabel-tabel Line1, Line2, dan Line3. Untuk merespons URI konten

content://com.example.trains/Line1

untuk tabel Line1, penyedia menghasilkan tipe 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 menghasilkan tipe MIME

vnd.android.cursor.item/vnd.example.line2

Kebanyakan penyedia konten mendefinisikan konstanta kelas kontrak untuk tipe MIME yang digunakannya. Kelas kontrak {@link android.provider.ContactsContract.RawContacts} pada Penyedia Kontak misalnya, mendefinisikan konstanta {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} untuk tipe MIME baris kontak mentah tunggal.

URI konten untuk baris-baris tunggal dijelaskan di bagian URI Konten.