Membuat penyedia konten

Penyedia konten mengelola akses ke repositori data pusat. Anda mengimplementasikan penyedia sebagai satu atau beberapa class dalam aplikasi Android, bersama elemen dalam file manifes. Salah satu class Anda mengimplementasikan subclass ContentProvider, yang merupakan antarmuka antara penyedia Anda dan aplikasi lain.

Meskipun penyedia konten dimaksudkan untuk menyediakan data bagi aplikasi lain, Anda dapat memiliki aktivitas di aplikasi yang memungkinkan pengguna membuat kueri dan mengubah data yang dikelola oleh penyedia Anda.

Halaman ini berisi proses dasar untuk membangun penyedia konten dan daftar API yang akan digunakan.

Sebelum Anda mulai membuat

Sebelum Anda mulai membuat penyedia, perhatikan hal-hal berikut:

  • Tentukan apakah Anda memerlukan penyedia konten. Anda harus membangun penyedia konten jika ingin menyediakan satu atau beberapa fitur berikut:
    • Anda ingin menawarkan data atau file yang kompleks ke aplikasi lain.
    • Anda ingin mengizinkan pengguna menyalin data yang kompleks dari aplikasi Anda ke dalam aplikasi lain.
    • Anda ingin menyediakan saran penelusuran khusus dengan menggunakan kerangka kerja penelusuran.
    • Anda ingin mengekspos aplikasi Anda data ke widget.
    • Anda ingin mengimplementasikan class AbstractThreadedSyncAdapter, CursorAdapter, atau CursorLoader.

    Anda tidak memerlukan penyedia untuk menggunakan database atau jenis penyimpanan persisten lainnya jika penggunaannya sepenuhnya dalam aplikasi Anda sendiri dan Anda tidak memerlukan fitur yang tercantum sebelumnya. Sebagai gantinya, Anda dapat menggunakan salah satu sistem penyimpanan yang dijelaskan dalam Ringkasan penyimpanan data dan file.

  • Jika Anda belum melakukannya, baca Dasar-dasar penyedia konten untuk mempelajari lebih lanjut penyedia dan cara kerjanya.

Berikutnya, ikuti langkah-langkah ini untuk membangun penyedia:

  1. Desain storage mentah untuk data Anda. Penyedia konten menawarkan data dengan dua cara:
    Data file
    Data yang biasanya masuk ke dalam file, seperti foto, audio, atau video. Simpan file di ruang pribadi aplikasi Anda. Merespons permintaan file dari aplikasi lain, penyedia Anda dapat menawarkan handle ke file tersebut.
    Data "terstruktur"
    Data yang biasanya masuk ke dalam database, array, atau struktur serupa. Simpan data dalam bentuk yang kompatibel dengan tabel baris dan kolom. Baris mewakili entitas, seperti orang atau item dalam inventaris. Kolom mewakili beberapa data untuk entity, seperti nama orang atau harga item. Cara umum untuk menyimpan jenis data ini adalah dalam database SQLite, tetapi Anda dapat menggunakan semua jenis penyimpanan persisten. Untuk mempelajari jenis penyimpanan yang tersedia di sistem Android lebih lanjut, lihat bagian Mendesain penyimpanan data.
  2. Tentukan implementasi konkret class ContentProvider dan metode yang diperlukannya. Class ini adalah antarmuka antara data Anda dan bagian sistem Android lainnya. Untuk informasi selengkapnya tentang class ini, lihat bagian Mengimplementasikan class ContentProvider.
  3. Definisikan string otoritas, URI materi, dan nama kolom penyedia. Jika Anda ingin aplikasi penyedia menangani intent, tentukan juga tindakan intent, data tambahan, dan flag. Tentukan juga izin yang Anda perlukan untuk aplikasi yang ingin mengakses data Anda. Pertimbangkan untuk menentukan semua nilai ini sebagai konstanta dalam class kontrak terpisah. Nantinya, Anda dapat mengekspos class ini kepada developer lain. Untuk informasi selengkapnya tentang URI konten, lihat bagian Mendesain URI konten. Untuk informasi selengkapnya tentang intent, lihat bagian Intent dan akses data.
  4. Tambahkan bagian opsional lainnya, seperti data sampel atau implementasi AbstractThreadedSyncAdapter yang dapat menyinkronkan data antara penyedia dan data berbasis cloud.

Merancang penyimpanan data

Penyedia materi adalah antarmuka ke data yang disimpan dalam format terstruktur. Sebelum membuat antarmuka, tentukan cara menyimpan data. Anda dapat menyimpan data dalam bentuk apa pun yang Anda sukai, lalu mendesain antarmuka untuk membaca dan menulis data sesuai kebutuhan.

Berikut adalah beberapa teknologi penyimpanan data yang tersedia di Android:

  • Jika Anda bekerja dengan data terstruktur, pertimbangkan database relasional seperti SQLite atau datastore nilai kunci non-relasional seperti LevelDB. Jika Anda menangani data tidak terstruktur seperti audio, gambar, atau media video, maka pertimbangkan untuk menyimpan data tersebut sebagai file. Anda dapat memadupadankan beberapa jenis penyimpanan dan mengeksposnya menggunakan satu penyedia konten jika perlu.
  • Sistem Android dapat berinteraksi dengan library persistensi Room, yang menyediakan akses ke API database SQLite yang digunakan oleh penyedia Android sendiri untuk menyimpan data berorientasi tabel. Untuk membuat database menggunakan library ini, buat instance subclass RoomDatabase, seperti yang dijelaskan dalam Menyimpan data di database lokal menggunakan Room.

    Anda tidak harus menggunakan database untuk mengimplementasikan repositori. Penyedia muncul secara eksternal sebagai satu set tabel, yang mirip dengan database relasional, tetapi ini bukan persyaratan untuk implementasi internal penyedia.

  • Untuk menyimpan file data, Android memiliki beragam API berorientasi file. Untuk mempelajari penyimpanan file lebih lanjut, baca Ringkasan penyimpanan data dan file. Jika Anda sedang mendesain penyedia yang menawarkan data terkait media seperti musik atau video, Anda dapat memiliki penyedia yang menggabungkan data tabel dan file.
  • Dalam kasus yang jarang terjadi, Anda mungkin mendapatkan manfaat dari menerapkan lebih dari satu penyedia konten untuk satu aplikasi. Misalnya, Anda mungkin ingin berbagi beberapa data dengan widget menggunakan satu penyedia konten, dan mengekspos set data yang berbeda untuk dibagikan dengan aplikasi lain.
  • Untuk bekerja dengan data berbasis jaringan, gunakan class dalam java.net dan android.net. Anda juga dapat menyinkronkan data berbasis jaringan ke penyimpanan data lokal seperti database, lalu menawarkan data sebagai tabel atau file.

Catatan: Jika Anda membuat perubahan pada repositori yang tidak kompatibel dengan versi sebelumnya, Anda harus menandai repositori dengan nomor versi baru. Anda juga perlu menambah nomor versi untuk aplikasi yang mengimplementasikan penyedia konten baru. Melakukan perubahan ini akan mencegah downgrade sistem menyebabkan sistem error saat mencoba menginstal ulang aplikasi yang memiliki penyedia konten yang tidak kompatibel.

Pertimbangan desain data

Berikut adalah beberapa tips untuk mendesain struktur data penyedia:

  • Data tabel harus selalu memiliki kolom "kunci utama" yang dipelihara oleh penyedia sebagai nilai numerik unik untuk setiap baris. Anda dapat menggunakan nilai ini untuk menautkan baris ke baris yang terkait dalam tabel lain (dengan menggunakannya sebagai "kunci asing"). Meskipun Anda dapat menggunakan nama apa pun untuk kolom ini, menggunakan BaseColumns._ID adalah pilihan terbaik, karena menautkan hasil kueri penyedia ke ListView mengharuskan salah satu kolom yang diambil memiliki nama _ID.
  • Jika Anda ingin menyediakan gambar bitmap atau potongan data berorientasi file lainnya yang berukuran sangat besar, simpan data dalam file, lalu sediakan secara tidak langsung, bukan menyimpannya secara langsung dalam tabel. Jika melakukannya, Anda perlu memberi tahu pengguna penyedia bahwa mereka perlu menggunakan metode file ContentResolver untuk mengakses data.
  • Gunakan jenis data biner objek besar (BLOB) untuk menyimpan data yang bervariasi ukurannya atau memiliki struktur yang bervariasi. Misalnya, Anda dapat menggunakan kolom BLOB untuk menyimpan buffering protokol atau struktur JSON.

    Anda juga dapat menggunakan BLOB untuk menerapkan tabel yang tidak bergantung skema. Dalam jenis tabel ini, Anda menentukan kolom kunci utama, kolom jenis MIME, dan satu atau beberapa kolom generik sebagai BLOB. Arti data dalam kolom BLOB ditunjukkan oleh nilai dalam kolom jenis MIME. Hal ini memungkinkan Anda menyimpan berbagai jenis baris dalam tabel yang sama. Tabel "data" ContactsContract.Data Penyedia Kontak adalah contoh tabel yang tidak bergantung pada skema.

Mendesain URI konten

URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI konten menyertakan nama simbolis seluruh penyedia (otoritasnya) dan nama yang mengarah ke tabel atau file (jalur). Bagian ID opsional menunjuk ke baris individual dalam tabel. Setiap metode akses data ContentProvider memiliki URI konten sebagai argumen. Cara ini memungkinkan Anda menentukan tabel, baris, atau file yang akan diakses.

Untuk informasi tentang URI konten, lihat Dasar-dasar penyedia konten.

Mendesain otoritas

Penyedia biasanya memiliki otoritas tunggal, yang berfungsi sebagai nama internal Android-nya. Untuk menghindari konflik dengan penyedia lain, gunakan kepemilikan domain internet (secara terbalik) sebagai dasar otoritas penyedia Anda. Karena rekomendasi ini juga berlaku untuk nama paket Android, Anda dapat menentukan otoritas penyedia sebagai ekstensi dari nama paket yang berisi penyedia.

Misalnya, jika nama paket Android Anda adalah com.example.<appname>, berikan otoritas com.example.<appname>.provider kepada penyedia Anda.

Mendesain struktur jalur

Developer biasanya membuat URI konten dari otoritas dengan menambahkan jalur yang mengarah ke masing-masing tabel. Misalnya, jika Anda memiliki dua tabel, table1 dan table2, Anda dapat menggabungkannya dengan otoritas dari contoh sebelumnya untuk menghasilkan URI konten com.example.<appname>.provider/table1 dan com.example.<appname>.provider/table2. Jalur tidak dibatasi pada segmen tunggal, dan tidak harus berupa tabel untuk setiap tingkat jalur.

Menangani ID URI konten

Berdasarkan konvensi, penyedia menawarkan akses ke satu baris dalam tabel dengan menerima URI konten dengan nilai ID untuk baris tersebut di akhir URI. Selain itu, berdasarkan konvensi, penyedia mencocokkan nilai ID dengan kolom _ID tabel dan melakukan akses yang diminta terhadap baris yang cocok.

Standar ini memudahkan pola desain umum untuk aplikasi yang mengakses penyedia. Aplikasi melakukan kueri terhadap penyedia dan menampilkan Cursor yang dihasilkan dalam ListView menggunakan CursorAdapter. Definisi CursorAdapter mengharuskan salah satu kolom di Cursor berupa _ID

Pengguna kemudian memilih salah satu baris yang ditampilkan dari UI untuk melihat atau mengubah data. Aplikasi akan mendapatkan baris yang sesuai dari Cursor yang mendukung ListView, mendapatkan nilai _ID untuk baris ini, menambahkannya ke URI konten, dan mengirim permintaan akses ke penyedia. Penyedia kemudian dapat melakukan kueri atau modifikasi terhadap baris yang dipilih pengguna.

Pola URI materi

Untuk membantu Anda memilih tindakan yang akan diambil bagi URI konten yang masuk, API penyedia menyertakan UriMatcher class praktis, yang memetakan pola URI konten ke nilai bilangan bulat. Anda dapat menggunakan nilai bilangan bulat dalam pernyataan switch yang memilih tindakan yang diinginkan untuk URI konten atau URI yang cocok dengan pola tertentu.

Pola URI materi mencocokkan dengan URI materi menggunakan karakter pengganti:

  • * cocok dengan string yang memiliki karakter valid dengan panjang berapa saja.
  • # cocok dengan string karakter numerik dengan panjang berapa pun.

Sebagai contoh mendesain dan coding penanganan URI konten, pertimbangkan penyedia dengan com.example.app.provider otoritas yang mengenali URI konten berikut yang mengarah ke tabel:

  • content://com.example.app.provider/table1: tabel bernama table1.
  • content://com.example.app.provider/table2/dataset1: tabel bernama dataset1.
  • content://com.example.app.provider/table2/dataset2: tabel bernama dataset2.
  • content://com.example.app.provider/table3: tabel bernama table3.

Penyedia juga mengenali URI konten ini jika memiliki ID baris yang ditambahkan ke dalamnya, seperti content://com.example.app.provider/table3/1 untuk baris yang diidentifikasi oleh 1 dalam table3.

Pola URI konten berikut mungkin diterapkan:

content://com.example.app.provider/*
Mencocokkan URI konten apa pun di penyedia.
content://com.example.app.provider/table2/*
Mencocokkan URI konten untuk tabel dataset1 dan dataset2, tetapi tidak cocok dengan URI konten untuk table1 atau table3.
content://com.example.app.provider/table3/#
Mencocokkan URI konten untuk satu baris di table3, seperti content://com.example.app.provider/table3/6 untuk baris yang diidentifikasi oleh 6.

Cuplikan kode berikut menunjukkan cara kerja metode di UriMatcher. Kode ini menangani URI untuk seluruh tabel secara berbeda dengan URI untuk satu baris menggunakan pola URI konten content://<authority>/<path> untuk tabel dan content://<authority>/<path>/<id> untuk satu baris.

Metode addURI() memetakan otoritas dan jalur ke nilai bilangan bulat. Metode match() menampilkan nilai bilangan bulat untuk URI. Pernyataan switch memilih antara membuat kueri seluruh tabel dan membuat kueri untuk satu data.

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here for all the content URI patterns that the provider
     * recognizes. For this snippet, only the calls for table 3 are shown.
     */

    /*
     * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used
     * in the path.
     */
    addURI("com.example.app.provider", "table3", 1)

    /*
     * Sets the code for a single row to 2. In this case, the # wildcard is
     * used. content://com.example.app.provider/table3/3 matches, but
     * content://com.example.app.provider/table3 doesn't.
     */
    addURI("com.example.app.provider", "table3/#", 2)
}
...
class ExampleProvider : ContentProvider() {
    ...
    // Implements ContentProvider.query()
    override fun query(
            uri: Uri?,
            projection: Array<out String>?,
            selection: String?,
            selectionArgs: Array<out String>?,
            sortOrder: String?
    ): Cursor? {
        var localSortOrder: String = sortOrder ?: ""
        var localSelection: String = selection ?: ""
        when (sUriMatcher.match(uri)) {
            1 -> { // If the incoming URI was for all of table3
                if (localSortOrder.isEmpty()) {
                    localSortOrder = "_ID ASC"
                }
            }
            2 -> {  // If the incoming URI was for a single row
                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                localSelection += "_ID ${uri?.lastPathSegment}"
            }
            else -> { // If the URI isn't recognized,
                // do some error handling here
            }
        }

        // Call the code to actually do the query
    }
}

Java

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

    static {
        /*
         * The calls to addURI() go here for all the content URI patterns that the provider
         * recognizes. For this snippet, only the calls for table 3 are shown.
         */

        /*
         * Sets the integer value for multiple rows in table 3 to one. No wildcard is used
         * in the path.
         */
        uriMatcher.addURI("com.example.app.provider", "table3", 1);

        /*
         * Sets the code for a single row to 2. In this case, the # wildcard is
         * used. content://com.example.app.provider/table3/3 matches, but
         * content://com.example.app.provider/table3 doesn't.
         */
        uriMatcher.addURI("com.example.app.provider", "table3/#", 2);
    }
...
    // Implements ContentProvider.query()
    public Cursor query(
        Uri uri,
        String[] projection,
        String selection,
        String[] selectionArgs,
        String sortOrder) {
...
        /*
         * Choose the table to query and a sort order based on the code returned for the incoming
         * URI. Here, too, only the statements for table 3 are shown.
         */
        switch (uriMatcher.match(uri)) {


            // If the incoming URI was for all of table3
            case 1:

                if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
                break;

            // If the incoming URI was for a single row
            case 2:

                /*
                 * Because this URI was for a single row, the _ID value part is
                 * present. Get the last path segment from the URI; this is the _ID value.
                 * Then, append the value to the WHERE clause for the query.
                 */
                selection = selection + "_ID = " + uri.getLastPathSegment();
                break;

            default:
            ...
                // If the URI isn't recognized, do some error handling here
        }
        // Call the code to actually do the query
    }

Class lainnya, ContentUris, menyediakan metode praktis untuk menggunakan bagian id dari URI konten. Class Uri dan Uri.Builder menyertakan metode praktis untuk mengurai objek Uri yang ada dan membuat objek baru.

Mengimplementasikan kelas ContentProvider

Instance ContentProvider mengelola akses ke serangkaian data terstruktur dengan menangani permintaan dari aplikasi lain. Semua bentuk akses pada akhirnya memanggil ContentResolver, yang kemudian memanggil metode konkret ContentProvider untuk mendapatkan akses.

Metode yang diperlukan

Class abstrak ContentProvider menentukan enam metode abstrak yang Anda terapkan sebagai bagian dari subclass konkret Anda. Semua metode ini kecuali onCreate() dipanggil oleh aplikasi klien yang mencoba mengakses penyedia konten Anda.

query()
Mengambil data dari penyedia Anda. Menggunakan argumen untuk memilih tabel yang akan dikueri, baris dan kolom yang akan ditampilkan, dan urutan sortir hasilnya. Tampilkan data sebagai objek Cursor.
insert()
Sisipkan baris baru ke penyedia Anda. Menggunakan argumen untuk memilih tabel tujuan dan mendapatkan nilai kolom yang akan digunakan. Menampilkan URI konten untuk baris yang baru disisipkan.
update()
Perbarui baris yang ada di penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan diperbarui dan mendapatkan nilai kolom yang diperbarui. Mengembalikan jumlah baris yang diperbarui.
delete()
Hapus baris dari penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan dihapus. Mengembalikan jumlah baris yang dihapus.
getType()
Menampilkan jenis MIME yang sesuai dengan URI konten. Metode ini dijelaskan lebih detail di bagian Menerapkan jenis MIME penyedia konten.
onCreate()
Inisialisasi penyedia Anda. Sistem Android memanggil metode ini segera setelah membuat penyedia Anda. Penyedia Anda tidak dibuat hingga objek ContentResolver mencoba mengaksesnya.

Metode ini memiliki signature yang sama dengan metode ContentResolver yang diberi nama identik.

Penerapan metode ini harus mempertimbangkan hal berikut:

  • Semua metode ini kecuali onCreate() dapat dipanggil oleh beberapa thread sekaligus, sehingga harus aman untuk thread. Untuk mempelajari beberapa thread lebih lanjut, lihat Ringkasan proses dan thread.
  • Hindari melakukan operasi yang lama dalam onCreate(). Tunda inisialisasi tugas hingga benar-benar diperlukan. Bagian tentang mengimplementasikan metode onCreate() membahas hal ini secara lebih mendetail.
  • Meskipun harus mengimplementasikan metode ini, kode Anda tidak harus melakukan apa pun kecuali menampilkan jenis data yang diharapkan. Misalnya, Anda dapat mencegah aplikasi lain menyisipkan data ke dalam beberapa tabel dengan mengabaikan panggilan ke insert() dan menampilkan 0.

Mengimplementasikan metode query()

Metode ContentProvider.query() harus menampilkan objek Cursor atau, jika gagal, menampilkan Exception. Jika menggunakan database SQLite sebagai penyimpanan data, Anda dapat menampilkan Cursor yang ditampilkan oleh salah satu metode query() dari class SQLiteDatabase.

Jika kueri tidak cocok dengan baris apa pun, tampilkan instance Cursor yang metode getCount()-nya menampilkan 0. Tampilkan null hanya jika terjadi error internal selama proses kueri.

Jika Anda tidak menggunakan database SQLite sebagai penyimpanan data, gunakan salah satu subclass konkret dari Cursor. Misalnya, class MatrixCursor menerapkan kursor dengan setiap baris berupa array instance Object. Dengan class ini, gunakan addRow() untuk menambahkan baris baru.

Sistem Android harus dapat mengomunikasikan Exception lintas batas proses. Android dapat melakukannya untuk pengecualian berikut yang berguna dalam menangani error kueri:

Mengimplementasikan metode insert()

Metode insert() menambahkan baris baru ke tabel yang sesuai, menggunakan nilai dalam argumen ContentValues. Jika nama kolom tidak ada dalam argumen ContentValues, Anda mungkin perlu memberikan nilai default untuknya, baik dalam kode penyedia atau skema database Anda.

Metode ini menampilkan URI konten untuk baris baru. Untuk membuatnya, tambahkan kunci utama baris baru, biasanya nilai _ID, ke URI konten tabel, menggunakan withAppendedId().

Mengimplementasikan metode delete()

Metode delete() tidak perlu menghapus baris dari penyimpanan data Anda. Jika Anda menggunakan adaptor sinkronisasi bersama penyedia Anda, sebaiknya tandai baris yang dihapus dengan flag "hapus", bukan menghapus baris tersebut sepenuhnya. Adaptor sinkronisasi dapat memeriksa baris yang dihapus dan mengeluarkannya dari server sebelum menghapusnya dari penyedia.

Mengimplementasikan metode update()

Metode update() menggunakan argumen ContentValues yang sama dengan yang digunakan oleh insert() serta argumen selection dan selectionArgs yang sama yang digunakan oleh delete() dan ContentProvider.query(). Tindakan ini mungkin memungkinkan Anda menggunakan kembali kode antarmetode tersebut.

Mengimplementasikan metode onCreate()

Sistem Android memanggil onCreate() saat memulai penyedia. Hanya lakukan tugas inisialisasi yang berjalan cepat dalam metode ini serta tunda pembuatan database dan pemuatan data hingga penyedia benar-benar menerima permintaan untuk data tersebut. Jika Anda melakukan tugas yang panjang dalam onCreate(), startup penyedia akan diperlambat. Pada akhirnya, hal tersebut memperlambat respons dari penyedia terhadap aplikasi lain.

Dua cuplikan berikut menunjukkan interaksi antara ContentProvider.onCreate() dan Room.databaseBuilder(). Cuplikan pertama menunjukkan implementasi ContentProvider.onCreate() tempat objek database di-build dan menangani objek akses data dibuat:

Kotlin

// Defines the database name
private const val DBNAME = "mydb"
...
class ExampleProvider : ContentProvider() {

    // Defines a handle to the Room database
    private lateinit var appDatabase: AppDatabase

    // Defines a Data Access Object to perform the database operations
    private var userDao: UserDao? = null

    override fun onCreate(): Boolean {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build()

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.userDao

        return true
    }
    ...
    // Implements the provider's insert method
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Java

public class ExampleProvider extends ContentProvider

    // Defines a handle to the Room database
    private AppDatabase appDatabase;

    // Defines a Data Access Object to perform the database operations
    private UserDao userDao;

    // Defines the database name
    private static final String DBNAME = "mydb";

    public boolean onCreate() {

        // Creates a new database object
        appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build();

        // Gets a Data Access Object to perform the database operations
        userDao = appDatabase.getUserDao();

        return true;
    }
    ...
    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc.
    }
}

Mengimplementasikan jenis MIME ContentProvider

Class ContentProvider memiliki dua metode untuk menampilkan jenis MIME:

getType()
Salah satu metode wajib yang Anda terapkan untuk penyedia apa pun.
getStreamTypes()
Metode yang diharapkan untuk diimplementasikan jika penyedia Anda menawarkan file.

Tipe MIME untuk tabel

Metode getType() menampilkan String dalam format MIME yang menjelaskan jenis data yang ditampilkan oleh argumen URI konten. Argumen Uri dapat berupa pola, bukan URI tertentu. Dalam hal ini, tampilkan jenis data terkait URI konten yang cocok dengan polanya.

Untuk jenis data umum seperti teks, HTML, atau JPEG, getType() menampilkan jenis MIME standar untuk data tersebut. Daftar lengkap jenis standar ini tersedia di situs IANA MIME Media Types.

Untuk URI konten yang mengarah ke baris atau baris data tabel, getType() akan menampilkan jenis MIME dalam format MIME khusus vendor Android:

  • Bagian jenis: vnd
  • Bagian subjenis:
    • Jika pola URI adalah untuk satu baris: android.cursor.item/
    • Jika pola URI adalah untuk lebih dari satu baris: android.cursor.dir/
  • Bagian khusus penyedia: vnd.<name>.<type>

    Anda menyediakan <name> dan <type>. Nilai <name> bersifat unik secara global, dan nilai <type> bersifat unik untuk pola URI yang sesuai. Pilihan yang tepat untuk <name> adalah nama perusahaan Anda atau beberapa bagian dari nama paket Android aplikasi Anda. Pilihan tepat untuk <type> adalah string yang mengidentifikasi tabel yang terkait dengan URI.

Misalnya, jika otoritas penyedia adalah com.example.app.provider, dan otoritas ini mengekspos tabel bernama table1, jenis MIME untuk beberapa baris dalam table1 adalah:

vnd.android.cursor.dir/vnd.com.example.provider.table1

Untuk satu baris table1, jenis MIME adalah:

vnd.android.cursor.item/vnd.com.example.provider.table1

Tipe MIME untuk file

Jika penyedia Anda menawarkan file, implementasikan getStreamTypes(). Metode ini menampilkan array String jenis MIME untuk file yang dapat ditampilkan oleh penyedia Anda untuk URI konten tertentu. Filter jenis MIME yang Anda tawarkan dengan argumen filter jenis MIME, sehingga Anda hanya menampilkan jenis MIME yang ingin ditangani klien.

Misalnya, pertimbangkan penyedia yang menawarkan gambar foto sebagai file dalam format JPG, PNG, dan GIF. Jika aplikasi memanggil ContentResolver.getStreamTypes() dengan string filter image/*, untuk sesuatu yang merupakan "gambar", metode ContentProvider.getStreamTypes() akan menampilkan array:

{ "image/jpeg", "image/png", "image/gif"}

Jika hanya tertarik dengan file JPG, aplikasi dapat memanggil ContentResolver.getStreamTypes() dengan string filter *\/jpeg, dan getStreamTypes() menampilkan:

{"image/jpeg"}

Jika penyedia Anda tidak menawarkan jenis MIME yang diminta dalam string filter, getStreamTypes() akan menampilkan null.

Mengimplementasikan kelas kontrak

Class kontrak adalah class public final yang berisi definisi konstanta untuk URI, nama kolom, jenis MIME, dan metadata lainnya yang berkaitan dengan penyedia. Class ini menetapkan kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia dapat diakses dengan benar meskipun ada perubahan pada nilai URI sebenarnya, nama kolom, dan sebagainya.

Class kontrak juga membantu developer karena class ini biasanya memiliki nama simbolik untuk konstantanya, sehingga memperkecil kemungkinan developer menggunakan nilai yang salah untuk nama kolom atau URI. Karena berupa class, class dapat berisi dokumentasi Javadoc. Lingkungan pengembangan terintegrasi seperti Android Studio dapat melengkapi otomatis nama konstanta dari class kontrak dan menampilkan Javadoc untuk konstanta tersebut.

Developer tidak dapat mengakses file class class kontrak dari aplikasi Anda, tetapi dapat mengompilasinya secara statis ke dalam aplikasi dari file JAR yang Anda berikan.

Class ContactsContract dan class bertingkatnya adalah contoh class kontrak.

Mengimplementasikan izin penyedia konten

Izin dan akses untuk semua aspek sistem Android dijelaskan secara mendetail dalam Tips keamanan. Ringkasan penyimpanan data dan file juga menjelaskan keamanan dan izin yang berlaku untuk berbagai jenis penyimpanan. Secara singkat, poin-poin pentingnya adalah sebagai berikut:

  • Secara default, file data yang disimpan di penyimpanan internal perangkat bersifat pribadi bagi aplikasi dan penyedia Anda.
  • Database SQLiteDatabase yang Anda buat bersifat pribadi bagi aplikasi dan penyedia Anda.
  • Secara default, file data yang Anda simpan ke penyimpanan eksternal bersifat publik dan dapat dibaca secara global. Anda tidak dapat menggunakan penyedia konten untuk membatasi akses ke file dalam penyimpanan eksternal, karena aplikasi lain dapat menggunakan panggilan API lain untuk membaca dan menulisnya.
  • Panggilan metode untuk membuka atau membuat file atau database SQLite pada penyimpanan internal perangkat Anda berpotensi memberikan akses baca dan tulis ke semua aplikasi lain. Jika Anda menggunakan file atau database internal sebagai repositori penyedia dan Anda memberinya akses "world-readable" (bisa dibaca secara global) atau "world-writeable" (dapat ditulis secara global), izin yang Anda tetapkan untuk penyedia Anda dalam manifesnya tidak akan melindungi data Anda. Akses default untuk file dan database di penyimpanan internal adalah "pribadi"; jangan mengubah ini untuk repositori penyedia Anda.

Jika Anda ingin menggunakan izin penyedia konten untuk mengontrol akses ke data Anda, maka simpan data Anda dalam file internal, database SQLite, atau cloud, misalnya di server jarak jauh, dan pertahankan file dan database hanya untuk aplikasi Anda.

Menerapkan izin

Secara default, semua aplikasi dapat membaca dari atau menulis ke penyedia Anda, meskipun data yang mendasarinya bersifat pribadi, karena secara default penyedia Anda tidak menetapkan izin. Untuk mengubahnya, tetapkan izin untuk penyedia dalam file manifes, menggunakan atribut atau elemen turunan dari elemen <provider>. Anda dapat menetapkan izin yang berlaku untuk seluruh penyedia, pada tabel tertentu, catatan tertentu, atau ketiganya.

Anda menetapkan izin untuk penyedia dengan satu atau beberapa elemen <permission> dalam file manifes. Agar izin unik bagi penyedia Anda, gunakan cakupan bergaya Java untuk atribut android:name. Misalnya, beri nama izin baca com.example.app.provider.permission.READ_PROVIDER.

Daftar berikut ini menjelaskan cakupan izin penyedia, dimulai dengan izin yang berlaku untuk seluruh penyedia, lalu menjadi lebih mendetail. Izin yang lebih terperinci akan lebih diutamakan daripada izin dengan cakupan yang lebih besar.

Izin baca-tulis tunggal tingkat penyedia
Satu izin yang mengontrol akses baca dan tulis ke seluruh penyedia, yang ditetapkan dengan atribut android:permission elemen <provider>.
Pisahkan izin baca dan tulis tingkat penyedia
Izin baca dan izin tulis untuk seluruh penyedia. Anda menentukannya dengan atribut android:readPermission dan android:writePermission dari elemen <provider>. Izin ini akan lebih diutamakan daripada izin yang diwajibkan oleh android:permission.
Izin tingkat path
Izin baca, tulis, atau baca/tulis untuk URI konten di penyedia Anda. Anda menentukan setiap URI yang ingin dikontrol dengan elemen turunan <path-permission> dari elemen <provider>. Untuk setiap URI konten yang ditentukan, Anda dapat menentukan satu izin baca/tulis, satu izin baca, satu izin tulis, atau ketiganya. Izin baca dan tulis lebih diutamakan daripada izin baca/tulis. Selain itu, izin tingkat jalur akan lebih diprioritaskan daripada izin tingkat penyedia.
Izin sementara
Tingkat izin yang memberikan akses sementara ke aplikasi, meskipun aplikasi tidak memiliki izin yang biasanya diminta. Fitur akses sementara mengurangi jumlah izin yang harus diminta aplikasi dalam manifesnya. Jika Anda mengaktifkan izin sementara, satu-satunya aplikasi yang memerlukan izin permanen untuk penyedia Anda adalah aplikasi yang terus mengakses semua data Anda.

Misalnya, pertimbangkan izin yang diperlukan jika Anda mengimplementasikan penyedia dan aplikasi email dan ingin mengizinkan aplikasi penampil gambar dari luar menampilkan lampiran foto dari penyedia Anda. Untuk memberikan akses yang diperlukan kepada penampil gambar tanpa memerlukan izin, Anda dapat menyiapkan izin sementara untuk URI konten bagi foto.

Desain aplikasi email Anda agar saat pengguna ingin menampilkan foto, aplikasi tersebut akan mengirimkan intent yang berisi URI konten foto dan tanda izin ke penampil gambar. Selanjutnya, penampil gambar dapat mengkueri penyedia email untuk mengambil foto, meskipun penampil tersebut tidak memiliki izin baca normal untuk penyedia Anda.

Untuk mengaktifkan izin sementara, tetapkan atribut android:grantUriPermissions dari elemen <provider> atau tambahkan satu atau beberapa elemen turunan <grant-uri-permission> ke elemen <provider> Anda. Panggil Context.revokeUriPermission() setiap kali Anda menghapus dukungan untuk URI konten yang terkait dengan izin sementara dari penyedia.

Nilai atribut menentukan seberapa banyak penyedia Anda yang dijadikan dapat diakses. Jika atribut ditetapkan ke "true", sistem akan memberikan izin sementara kepada seluruh penyedia, dengan mengabaikan izin lain yang diperlukan oleh izin tingkat penyedia atau tingkat jalur.

Jika tanda ini disetel ke "false", tambahkan elemen turunan <grant-uri-permission> ke elemen <provider> Anda. Setiap elemen turunan menetapkan URI konten atau URI yang telah diberi akses sementara.

Untuk mendelegasikan akses sementara ke aplikasi, intent harus berisi flag FLAG_GRANT_READ_URI_PERMISSION, flag FLAG_GRANT_WRITE_URI_PERMISSION, atau keduanya. Keduanya ditetapkan dengan metode setFlags().

Jika atribut android:grantUriPermissions tidak ada, atribut tersebut diasumsikan sebagai "false".

Elemen <provider>

Seperti komponen Activity dan Service, subclass ContentProvider ditentukan dalam file manifes untuk aplikasinya, menggunakan elemen <provider>. Sistem Android mendapatkan informasi berikut dari elemen:

Otoritas (android:authorities)
Nama simbolis yang mengidentifikasi seluruh penyedia dalam sistem. Atribut ini dijelaskan secara lebih detail di bagian Mendesain URI konten.
Nama class penyedia (android:name)
Class yang mengimplementasikan ContentProvider. Class ini dijelaskan secara lebih detail di bagian Mengimplementasikan class ContentProvider.
Izin
Atribut yang menentukan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia:

Izin dan atribut yang sesuai dijelaskan secara lebih mendetail di bagian Menerapkan izin penyedia konten.

Atribut-atribut startup dan kontrol
Atribut ini menentukan cara dan waktu sistem Android memulai penyedia, karakteristik proses penyedia, dan setelan runtime lainnya:
  • android:enabled: flag yang memungkinkan sistem memulai penyedia
  • android:exported: flag yang mengizinkan aplikasi lain menggunakan penyedia ini
  • android:initOrder: urutan yang digunakan untuk memulai penyedia ini, relatif terhadap penyedia lain dalam proses yang sama
  • android:multiProcess: flag yang memungkinkan sistem memulai penyedia dalam proses yang sama seperti proses klien pemanggil
  • android:process: nama proses yang digunakan oleh penyedia
  • android:syncable: flag yang menunjukkan bahwa data penyedia akan disinkronkan dengan data di server

Atribut ini didokumentasikan sepenuhnya dalam panduan untuk elemen <provider>.

Atribut-atribut informatif
Ikon dan label opsional untuk penyedia:
  • android:icon: resource drawable yang berisi ikon untuk penyedia. Ikon ini muncul di samping label penyedia dalam daftar aplikasi di Setelan > Aplikasi > Semua.
  • android:label: label informasi yang mendeskripsikan penyedia, datanya, atau keduanya. Label ini muncul dalam daftar aplikasi di Setelan > Aplikasi > Semua.

Atribut ini didokumentasikan sepenuhnya dalam panduan untuk elemen <provider>.

Intent dan akses data

Aplikasi dapat mengakses penyedia konten secara tidak langsung dengan Intent. Aplikasi tidak memanggil metode ContentResolver atau ContentProvider. Sebagai gantinya, aplikasi akan mengirim intent yang memulai aktivitas, yang sering kali merupakan bagian dari aplikasi milik penyedia itu sendiri. Aktivitas tujuan bertugas mengambil dan menampilkan data dalam UI-nya.

Bergantung pada tindakan dalam intent, aktivitas tujuan juga dapat meminta pengguna untuk melakukan modifikasi pada data penyedia. Intent juga mungkin berisi data "tambahan" yang ditampilkan aktivitas tujuan di UI. Kemudian, pengguna memiliki opsi untuk mengubah data ini sebelum menggunakannya untuk mengubah data dalam penyedia.

Anda dapat menggunakan akses intent untuk membantu integritas data. Penyedia Anda mungkin bergantung pada data yang disisipkan, diperbarui, dan dihapus sesuai dengan logika bisnis yang didefinisikan dengan ketat. Jika demikian, membiarkan aplikasi lain mengubah data Anda secara langsung dapat menyebabkan data yang tidak valid.

Jika Anda ingin developer menggunakan akses intent, pastikan untuk mendokumentasikannya secara saksama. Jelaskan alasan akses intent yang menggunakan UI aplikasi Anda lebih baik daripada mencoba mengubah data dengan kodenya.

Menangani intent masuk yang ingin mengubah data penyedia Anda tidak berbeda dengan menangani intent lainnya. Anda dapat mempelajari penggunaan intent lebih lanjut dengan membaca Intent dan Filter Intent.

Untuk informasi terkait lainnya, lihat Ringkasan penyedia kalender.