Membuat penyedia konten

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

Meskipun penyedia konten dimaksudkan untuk menyediakan data bagi aplikasi lain, Anda bisa memiliki aktivitas dalam aplikasi yang memungkinkan pengguna membuat kueri dan memodifikasi 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 perlu 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 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 berada 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 belum, 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, misalnya foto, audio, atau video. Simpan file di ruang pribadi aplikasi Anda. Sebagai respons terhadap 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 entitas, seperti nama orang atau harga item. Cara umum untuk menyimpan jenis data ini adalah dalam database SQLite, tetapi Anda dapat menggunakan jenis penyimpanan persisten apa pun. Untuk mempelajari lebih lanjut jenis penyimpanan yang tersedia di sistem Android, lihat bagian Mendesain penyimpanan data.
  2. Tentukan implementasi konkret class ContentProvider dan metode yang diperlukannya. Class ini adalah antarmuka antara data Anda dan bagian lain sistem Android. Untuk mengetahui informasi selengkapnya tentang class ini, lihat bagian Mengimplementasikan class ContentProvider.
  3. Definisikan string otoritas, URI konten, 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. Sebaiknya tentukan semua nilai ini sebagai konstanta di class kontrak terpisah. Nanti, Anda dapat mengekspos class ini kepada developer lain. Untuk informasi selengkapnya tentang URI konten, lihat bagian Mendesain URI konten. Untuk mengetahui informasi selengkapnya tentang intent, lihat bagian Intent dan akses data.
  4. Tambahkan bagian opsional lainnya, seperti data contoh atau implementasi AbstractThreadedSyncAdapter yang dapat menyinkronkan data antara penyedia dan data berbasis cloud.

Mendesain 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 inginkan, lalu mendesain antarmuka untuk membaca dan menulis data sesuai kebutuhan.

Berikut adalah beberapa teknologi penyimpanan data yang tersedia di Android:

  • Jika Anda menggunakan data terstruktur, pertimbangkan database relasional seperti SQLite atau datastore nilai kunci non-relasional seperti LevelDB. Jika Anda bekerja dengan data tidak terstruktur seperti audio, gambar, atau media video, maka pertimbangkan untuk menyimpan data sebagai file. Anda dapat mengombinasikan beberapa jenis penyimpanan dan mengeksposnya menggunakan satu penyedia konten jika perlu.
  • Sistem Android dapat berinteraksi dengan library persistensi Room, yang menyediakan akses ke SQLite database API 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 kumpulan 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 mendesain penyedia yang menawarkan data terkait media seperti musik atau video, Anda dapat menggunakan penyedia yang menggabungkan data tabel dan file.
  • Dalam kasus yang jarang terjadi, Anda mungkin akan mendapatkan manfaat dengan menerapkan lebih dari satu penyedia konten untuk satu aplikasi. Misalnya, Anda mungkin ingin berbagi beberapa data dengan widget menggunakan satu penyedia konten, dan mengekspos kumpulan data yang berbeda untuk dibagikan dengan aplikasi lain.
  • Untuk menggunakan 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 lama, Anda harus menandai repositori dengan nomor versi baru. Anda juga perlu meningkatkan nomor versi untuk aplikasi Anda 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 ini beberapa tips untuk mendesain struktur data penyedia Anda:

  • 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 terkait dalam tabel lain (dengan menggunakannya sebagai "kunci asing"). Meskipun Anda dapat menggunakan nama apa pun untuk kolom ini, penggunaan BaseColumns._ID adalah pilihan terbaik karena menautkan hasil kueri penyedia ke ListView mengharuskan salah satu kolom yang diambil diberi nama _ID.
  • Jika ingin menyediakan gambar bitmap atau potongan data berorientasi file lainnya yang sangat besar, simpan data tersebut dalam file, lalu sediakan secara tidak langsung, bukan menyimpannya secara langsung dalam tabel. Jika melakukannya, Anda perlu memberi tahu pengguna penyedia Anda bahwa mereka perlu menggunakan metode file ContentResolver untuk mengakses data.
  • Gunakan jenis data objek besar biner (BLOB) untuk menyimpan data yang ukurannya bervariasi 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 umum 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" Penyedia Kontak ContactsContract.Data adalah contoh tabel yang tidak bergantung pada skema.

URI konten desain

URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI konten mencakup nama simbolis seluruh penyedia (otoritasnya) dan nama yang mengarah ke tabel atau file (jalur). Bagian ID opsional menunjuk ke satu baris dalam tabel. Setiap metode akses data ContentProvider memiliki URI konten sebagai argumen. Dengan begitu, Anda dapat menentukan tabel, baris, atau file yang akan diakses.

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

Merancang 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 standar, penyedia menawarkan akses ke satu baris dalam tabel dengan menerima URI konten beserta nilai ID untuk baris tersebut di akhir URI. Selain itu, berdasarkan standar, 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 dalam Cursor berupa _ID

Kemudian, pengguna 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 perubahan terhadap baris yang sama persis dengan 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 dengan karakter valid apa pun dengan panjang berapa pun.
  • # cocok dengan string karakter numerik dengan panjang berapa pun.

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

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

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

Pola URI konten berikut mungkin:

content://com.example.app.provider/*
Mencocokkan URI konten apa pun di penyedia.
content://com.example.app.provider/table2/*
Cocok dengan 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 baris tunggal.

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 atau 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 menangani bagian id dari URI konten. Class Uri dan Uri.Builder menyertakan metode praktis untuk menguraikan objek Uri yang ada dan membuat objek baru.

Mengimplementasikan class ContentProvider

Instance ContentProvider mengelola akses ke kumpulan data terstruktur dengan menangani permintaan dari aplikasi lain. Semua bentuk akses pada akhirnya akan 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()
Ambil data dari penyedia Anda. Gunakan argumen untuk memilih tabel yang akan dikueri, baris dan kolom yang akan ditampilkan, serta tata urutan 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. Gunakan 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. Gunakan 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 secara lebih mendetail di bagian Mengimplementasikan jenis MIME penyedia konten.
onCreate()
Lakukan inisialisasi penyedia. Sistem Android memanggil metode ini segera setelah membuat penyedia Anda. Penyedia Anda tidak dibuat sampai objek ContentResolver mencoba mengaksesnya.

Metode ini memiliki tanda tangan yang sama dengan metode ContentResolver yang bernama identik.

Penerapan metode ini harus memperhitungkan hal-hal berikut:

  • Semua metode ini kecuali onCreate() dapat dipanggil oleh beberapa thread sekaligus, jadi harus aman untuk thread. Untuk mempelajari beberapa thread lebih lanjut, lihat Ringkasan proses dan thread.
  • Hindari melakukan operasi yang lama di 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 perlu 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, tampilkan 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 di kode penyedia atau di skema database.

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 dengan penyedia Anda, pertimbangkan untuk menandai baris yang dihapus dengan flag "hapus", bukan menghapus baris sepenuhnya. Adaptor sinkronisasi dapat memeriksa baris yang dihapus dan menghapusnya dari server sebelum menghapusnya dari penyedia.

Mengimplementasikan metode update()

Metode update() menggunakan argumen ContentValues yang sama yang digunakan oleh insert() serta argumen selection dan selectionArgs yang sama yang digunakan oleh delete() dan ContentProvider.query(). Hal ini memungkinkan Anda menggunakan kembali kode di antara metode ini.

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 data. Jika Anda melakukan tugas yang memakan waktu di onCreate(), Anda akan memperlambat startup penyedia Anda. Pada akhirnya, hal ini akan 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 dibuat 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 mana pun.
getStreamTypes()
Metode yang diharapkan untuk Anda implementasikan 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 yang dikaitkan dengan URI konten yang cocok dengan pola.

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 beberapa baris data tabel, getType() 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 penyedia tersebut mengekspos tabel bernama table1, jenis MIME untuk beberapa baris di 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 penyedia Anda untuk URI konten tertentu. Filter jenis MIME yang ditawarkan 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() akan menampilkan:

{"image/jpeg"}

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

Mengimplementasikan kelas kontrak

Class kontrak adalah class public final yang berisi definisi konstan untuk URI, nama kolom, jenis MIME, dan metadata lain yang berkaitan dengan penyedia. Class menetapkan kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia dapat diakses dengan benar sekalipun ada perubahan pada nilai aktual URI, nama kolom, dan seterusnya.

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

Developer tidak dapat mengakses file class kelas kontrak dari aplikasi Anda, tetapi dapat mengompilasinya secara statis ke dalam aplikasi mereka 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 pada penyimpanan internal perangkat bersifat pribadi untuk 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 menulis file tersebut.
  • Panggilan metode untuk membuka atau membuat file atau database SQLite pada penyimpanan internal perangkat 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" (dapat 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 dalam penyimpanan internal adalah "pribadi"; jangan ubah akses 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, seperti pada server jarak jauh, dan jaga kerahasiaan file dan database tersebut bagi aplikasi Anda.

Mengimplementasikan izin

Secara default, semua aplikasi bisa 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 bagi penyedia Anda dalam file manifes, menggunakan atribut atau elemen turunan dari elemen <provider>. Anda dapat menetapkan izin yang berlaku untuk seluruh penyedia, ke tabel tertentu, ke kumpulan data tertentu, atau ketiganya.

Anda menetapkan izin untuk penyedia dengan satu atau beberapa elemen <permission> dalam file manifes. Untuk membuat 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, hingga makin terperinci. Izin yang lebih terperinci akan didahulukan daripada izin dengan cakupan yang lebih luas.

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 diprioritaskan 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 menetapkan satu izin baca/tulis, satu izin baca, satu izin tulis, atau ketiganya. Izin baca dan tulis akan lebih diprioritaskan daripada izin baca/tulis. Selain itu, izin tingkat jalur lebih diprioritaskan daripada izin tingkat penyedia.
Izin sementara
Tingkat izin yang memberikan akses sementara ke aplikasi, meskipun aplikasi tersebut tidak memiliki izin yang biasanya diperlukan. Fitur akses sementara mengurangi jumlah izin yang harus diminta aplikasi dalam manifesnya. Saat Anda mengaktifkan izin sementara, satu-satunya aplikasi yang memerlukan izin permanen untuk penyedia adalah aplikasi yang mengakses semua data Anda secara terus-menerus.

Misalnya, pertimbangkan izin yang diperlukan jika Anda mengimplementasikan penyedia email dan aplikasi, serta 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.

Rancang aplikasi email Anda agar saat pengguna ingin menampilkan foto, aplikasi akan mengirimkan intent yang berisi URI konten foto dan flag izin ke penampil gambar. Kemudian, penampil gambar dapat mengkueri penyedia email Anda untuk mengambil foto, meskipun penampil gambar 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 Anda.

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

Jika flag 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. Atribut ini ditetapkan dengan metode setFlags().

Jika atribut android:grantUriPermissions tidak ada, atribut ini 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 mendetail di bagian URI konten desain.
Nama class penyedia (android:name)
Class yang mengimplementasikan ContentProvider. Class ini dijelaskan secara lebih mendetail di bagian Mengimplementasikan class ContentProvider.
Izin
Atribut yang menentukan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia:

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

Atribut-atribut startup dan kontrol
Atribut ini menentukan bagaimana dan kapan sistem Android memulai penyedia, karakteristik proses penyedia, dan setelan runtime lainnya:

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 muncul dalam daftar aplikasi di Setelan > Aplikasi > Semua.

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

Catatan: Jika Anda menargetkan Android 11 atau yang lebih tinggi, lihat dokumentasi visibilitas paket untuk kebutuhan konfigurasi lebih lanjut.

Intent dan akses data

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

Bergantung pada tindakan dalam intent, aktivitas tujuan juga dapat meminta pengguna untuk membuat modifikasi pada data penyedia. Intent juga dapat berisi data "tambahan" yang ditampilkan aktivitas tujuan di UI. Kemudian, pengguna memiliki opsi untuk mengubah data ini sebelum menggunakannya untuk mengubah data di 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 ditentukan dengan ketat. Jika hal ini terjadi, mengizinkan 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. Menjelaskan alasan akses intent menggunakan UI aplikasi Anda lebih baik daripada mencoba memodifikasi data dengan kodenya.

Menangani intent masuk yang ingin memodifikasi 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.