Membuat penyedia materi

Penyedia materi mengelola akses ke repositori data pusat. Anda mengimplementasikan penyedia sebagai satu atau beberapa kelas dalam aplikasi Android, bersama elemen-elemen dalam file manifes. Salah satu kelas Anda mengimplementasikan subkelas ContentProvider, yang merupakan antarmuka antara penyedia Anda dan aplikasi lain. Walaupun penyedia materi dimaksudkan untuk menyediakan data bagi aplikasi lain, Anda tentu saja bisa memiliki aktivitas dalam aplikasi yang memungkinkan pengguna melakukan kueri dan memodifikasi data yang dikelola oleh penyedia Anda.

Bagian selebihnya dalam topik ini adalah daftar langkah-langkah dasar untuk membangun penyedia materi dan daftar API yang akan digunakan.

Sebelum Anda mulai membuat

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

  1. Putuskan apakah Anda memerlukan penyedia materi. Anda perlu membuat sebuah penyedia materi jika ingin menyediakan salah satu atau beberapa dari fitur berikut:
    • Anda ingin menawarkan data atau file yang kompleks ke aplikasi lain.
    • Anda ingin memungkinkan 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 kelas AbstractThreadedSyncAdapter, CursorAdapter, atau CursorLoader.

    Anda tidak perlu penyedia untuk menggunakan database atau jenis penyimpanan persisten lainnya jika penggunaannya sepenuhnya dalam aplikasi Anda sendiri dan Anda tidak memerlukan salah satu fitur yang tercantum di atas. Sebagai gantinya, Anda dapat menggunakan salah satu sistem penyimpanan yang dijelaskan pada halaman Menyimpan Data Aplikasi.

  2. Jika Anda belum siap melakukannya, bacalah topik Dasar-dasar penyedia materi untuk mempelajari lebih lanjut tentang penyedia dan cara kerjanya.

Berikutnya, ikuti langkah-langkah ini untuk membangun penyedia:

  1. Desain storage mentah untuk data Anda. Penyedia materi menawarkan data dengan dua cara:
    Data file
    Data yang biasanya masuk ke dalam file, misalnya foto, audio, atau video. Simpan file dalam ruang pribadi aplikasi Anda. Untuk merespons permintaan file dari aplikasi lain, penyedia Anda bisa menawarkan handle ke file tersebut.
    Data "terstruktur"
    Data yang biasanya masuk ke dalam database, larik, atau struktur serupa. Simpan data dalam bentuk yang kompatibel dengan tabel berisi baris dan kolom. Baris mewakili entitas, misalnya satu orang atau satu barang inventori. Kolom mewakili beberapa data untuk entitas itu, misalnya nama orang atau harga barang. Cara umum untuk menyimpan tipe data ini adalah dalam database SQLite, namun Anda bisa menggunakan tipe penyimpanan apa saja yang persisten. Untuk mempelajari lebih lanjut tentang tipe penyimpanan yang tersedia di sistem Android, lihat bagian Mendesain penyimpanan data.
  2. Tentukan implementasi konkret kelas ContentProvider dan metode yang diperlukannya. Kelas ini adalah antarmuka antara data Anda dan selebihnya adalah pada sistem Android. Untuk informasi selengkapnya tentang kelas ini, lihat bagian Mengimplementasikan kelas ContentProvider.
  3. Definisikan string otoritas, semua URI isinya, dan nama-nama kolom penyedia. Jika Anda ingin penyedia aplikasi menangani intent, tetapkan juga semua tindakan intent, data tambahan, dan flag. Tetapkan juga izin yang akan Anda syaratkan terhadap aplikasi yang ingin mengakses data Anda. Anda harus mempertimbangkan penetapan semua nilai ini sebagai konstanta di kelas kontrak terpisah; nantinya, Anda bisa mengekspos kelas ini kepada developer lain. Untuk informasi selengkapnya tentang URI materi, lihat bagian Mendesain URI Materi. Untuk informasi selengkapnya tentang intent, lihat bagian Intent dan Akses Data.
  4. Tambahkan bagian opsional lainnya, seperti data contoh atau implementasi AbstractThreadedSyncAdapter yang bisa 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, Anda harus memutuskan cara menyimpan data. Anda bisa menyimpan data dalam bentuk apa saja yang Anda sukai, kemudian mendesain antarmuka untuk membaca dan menulis data yang diperlukan.

Berikut ini adalah beberapa teknologi penyimpanan data yang tersedia di Android:

  • Jika Anda bekerja dengan data terstruktur maka pertimbangkan database relasional seperti SQLite, atau datastore nilai kunci non relasional seperti LevelDB. Jika Anda bekerja dengan data yang tidak terstruktur seperti audio, gambar, atau media video maka pertimbangkan untuk menyimpan data sebagai file. Anda dapat memadukan dan mencocokkan beberapa jenis penyimpanan yang berbeda, dan memaparkannya menggunakan penyedia konten tunggal 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, beri contoh subkelas dari RoomDatabase, seperti yang dijelaskan dalam panduan library persistensi Room.

    Ingatlah bahwa Anda tidak harus menggunakan database untuk mengimplementasikan repositori. Penyedia muncul secara eksternal sebagai satu set tabel, yang serupa dengan sebuah database relasional, namun ini bukan persyaratan untuk implementasi internal penyedia.

  • Untuk menyimpan file data, Android memiliki beragam API berorientasi file. Untuk mempelajari lebih lanjut tentang penyimpanan file, bacalah topik Penyimpanan Data. Jika Anda sedang mendesain penyedia yang menawarkan data yang terkait dengan media seperti musik atau video, Anda bisa memiliki penyedia yang mengombinasikan data tabel dan file.
  • Dalam kasus yang jarang terjadi, Anda mungkin mendapat manfaat dari penerapan lebih dari satu penyedia konten untuk satu aplikasi. Misalnya, Anda mungkin ingin berbagi beberapa data dengan widget menggunakan satu penyedia konten, dan membuka set data yang berbeda untuk dibagikan dengan aplikasi lain.
  • Untuk bekerja dengan data berbasis jaringan, gunakan kelas-kelas dalam java.net dan android.net. Anda juga bisa menyinkronkan data berbasis jaringan dengan penyimpanan data lokal seperti database, kemudian menawarkan data sebagai tabel atau file. Aplikasi contoh Adapter Sinkron Dasar memperagakan tipe sinkronisasi ini.

Catatan: Jika Anda membuat perubahan pada repositori Anda yang tidak backwards-compatible, Anda harus menandai repositori dengan nomor versi baru. Anda juga perlu menambah nomor versi untuk aplikasi Anda yang mengimplementasikan penyedia konten baru. Membuat perubahan ini mencegah downgrade sistem agar tidak menyebabkan sistem error ketika mencoba menginstal ulang aplikasi yang memiliki penyedia konten yang tidak kompatibel.

Pertimbangan desain data

Berikut ini adalah beberapa tip 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 bisa menggunakan nilai ini untuk menautkan baris ke baris yang terkait dalam tabel lain (dengan menggunakannya sebagai "kunci asing"). Walaupun Anda bisa menggunakan nama apa saja untuk kolom ini, menggunakan BaseColumns._ID adalah pilihan terbaik, karena menautkan hasil kueri penyedia dengan ListView mensyaratkan bahwa salah satu kolom yang diambil memiliki _ID nama.
  • Jika Anda ingin untuk menyediakan gambar bitmap atau potongan data berorientasi file lainnya yang berukuran sangat besar, simpanlah data dalam sebuah file kemudian sediakan secara tidak langsung sebagai ganti 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 tipe data Binary Large OBject (BLOB) untuk menyimpan data yang bervariasi ukurannya atau memiliki struktur yang beragam. Misalnya, Anda bisa menggunakan sebuah kolom BLOB untuk menyimpan buffer protokol atau struktur JSON.

    Anda juga bisa menggunakan BLOB untuk mengimplementasikan tabel yang tidak bergantung skema. Dalam tipe tabel ini, Anda menetapkan kolom kunci utama, kolom tipe MIME, dan satu atau beberapa kolom generik sebagai BLOB. Arti dari data dalam kolom-kolom BLOB ditunjukkan oleh nilai dalam kolom tipe MIME. Cara ini memungkinkan Anda menyimpan berbagai tipe baris dalam tabel yang sama. Tabel "data" ContactsContract.Data Penyedia Kontak adalah contoh tabel yang tidak bergantung skema tersebut.

Mendesain URI materi

URI materi adalah URI yang mengidentifikasi data dalam penyedia. URI Materi berisi nama simbolis seluruh penyedia (otoritasnya) dan sebuah nama yang menunjuk ke tabel atau file (jalur). Bagian id opsional menunjuk ke satu baris dalam tabel. Setiap metode akses data ContentProvider memiliki sebuah URI materi sebagai argumen; hal ini memungkinkan Anda menentukan tabel, baris, atau file yang akan diakses.

Dasar-dasar URI materi dijelaskan dalam topik Dasar-dasar penyedia materi.

Mendesain otoritas

Penyedia biasanya memiliki otoritas tunggal, yang berfungsi sebagai nama internal Android-nya. Untuk menghindari konflik dengan penyedia lain, Anda harus menggunakan kepemilikan domain Internet (secara terbalik) sebagai basis otoritas penyedia Anda. Karena saran ini juga berlaku untuk nama-nama paket Android, Anda bisa menetapkan otoritas penyedia sebagai ekstensi dari nama paket yang berisi penyedia. Misalnya, jika nama paket Android Anda adalah com.example.<appname>, Anda harus memberikan penyedia Anda otoritas com.example.<appname>.provider.

Mendesain struktur jalur

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

Menangani ID URI materi

Berdasarkan standar, penyedia menawarkan akses ke satu baris dalam tabel dengan menerima URI materi dengan sebuah nilai ID untuk baris itu di akhir URI. Juga 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 dengan menggunakan CursorAdapter. Definisi CursorAdapter mengharuskan salah satu kolom dalam Cursor berupa _ID

Pengguna kemudian mengambil salah satu baris yang ditampilkan dari UI untuk menemukan atau memodifikasi data. Aplikasi mengambil baris yang sesuai dari Cursor yang mendukung ListView, mengambil nilai _ID untuk baris ini, menambahkannya ke URI materi, dan mengirim permintaan akses ke penyedia. Penyedia nanti bisa melakukan kueri atau modifikasi terhadap baris yang persis diambil pengguna.

Pola URI materi

Untuk membantu Anda memilih tindakan yang diambil bagi URI materi yang masuk, API penyedia menyertakan kelas praktis UriMatcher, yang memetakan "pola-pola" URI materi ke nilai-nilai integer. Anda bisa menggunakan nilai-nilai integer dalam pernyataan switch yang memilih tindakan yang diinginkan untuk URI materi atau URI yang cocok dengan pola tertentu.

Pola URI materi mencocokkan dengan URI materi menggunakan karakter pengganti:

  • *: Mencocokkan string yang memiliki karakter yang sah dengan panjang berapa saja.
  • #: Mencocokkan string karakter numerik dengan panjang berapa saja.

Sebagai contoh desain dan pengkodean penanganan URI materi, perhatikan penyedia dengan otoritas com.example.app.provider yang mengenali URI materi berikut yang menunjuk ke tabel-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 materi ini jika baris ID ditambahkan ke URI, misalnya content://com.example.app.provider/table3/1 untuk baris yang diidentifikasi oleh 1 dalam table3.

Pola-pola URI materi berikut akan menjadi mungkin:

content://com.example.app.provider/*
Mencocokkan URI materi di penyedia.
content://com.example.app.provider/table2/*:
Mencocokkan URI materi untuk tabel-tabel dataset1 dan dataset2, namun tidak mencocokkan URI materi untuk table1 atau table3.
content://com.example.app.provider/table3/#: Mencocokkan URI materi untuk satu baris di table3, misalnya 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 seluruh tabel secara berbeda dengan URI untuk satu baris, menggunakan pola URI materi content://<authority>/<path> untuk tabel, dan content://<authority>/<path>/<id> untuk satu baris.

Metode addURI() memetakan otoritas dan jalur ke nilai integer. Metode match() menampilkan nilai integer URI. Pernyataan switch memilih antara melakukan kueri seluruh tabel dan melakukan kueri satu catatan:

Kotlin

private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
    /*
     * The calls to addURI() go here, for all of the content URI patterns that the provider
     * should recognize. 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 is not recognized
                // You should 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 of the content URI patterns that the provider
         * should recognize. 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
         */
        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 is not recognized, you should do some error handling here.
        }
        // call the code to actually do the query
    }

Kelas lainnya, ContentUris, menyediakan metode praktis untuk menggunakan bagian id URI materi. Kelas-kelas 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 satu set 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-metode yang diperlukan

Kelas abstrak ContentProvider mendefinisikan enam metode abstrak yang harus Anda implementasikan sebagai bagian dari subkelas konkret Anda sendiri. Semua metode ini kecuali onCreate() dipanggil oleh aplikasi klien yang berupaya mengakses penyedia materi Anda:

query()
Mengambil data dari penyedia Anda. Menggunakan argumen untuk memilih tabel yang akan di-kueri, baris dan kolom yang akan ditampilkan, dan urutan sortir hasilnya. Menampilkan data berupa objek Cursor.
insert()
Menyisipkan baris baru ke dalam penyedia Anda. Menggunakan argumen untuk memilih tabel tujuan dan mendapatkan nilai-nilai kolom yang akan digunakan. Menampilkan URI materi untuk baris yang baru disisipkan.
update()
Memperbarui baris yang ada di penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan diperbarui dan mendapatkan nilai-nilai kolom yang diperbarui. Mengembalikan jumlah baris yang diperbarui.
delete()
Menghapus baris dari penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan dihapus. Mengembalikan jumlah baris yang dihapus.
getType()
Mengembalikan tipe MIME yang sesuai dengan URI materi. Metode ini dijelaskan lebih detail di bagian Mengimplementasikan tipe MIME penyedia materi.
onCreate()
Melakukan inisialiasi penyedia Anda. Sistem Android memanggil metode ini segera setelah membuat penyedia Anda. Perhatikan bahwa penyedia Anda tidak dibuat hingga objek ContentResolver mencoba mengaksesnya.

Perhatikan bahwa metode-metode ini memiliki signature yang sama dengan metode ContentResolver yang namanya sama.

Implementasi metode-metode ini harus memperhitungkan hal-hal berikut:

  • Semua metode ini kecuali onCreate() bisa dipanggil oleh beberapa thread sekaligus, jadi harus thread-safe (aman untuk thread). Untuk mempelajari lebih lanjut tentang multi-thread, lihat topik Proses dan Thread.
  • Hindari melakukan operasi yang lama dalam onCreate(). Tunda inisialisasi tugas hingga benar-benar diperlukan. Bagian Mengimplementasikan metode onCreate() membahas hal ini secara lebih detail.
  • Walaupun harus mengimplementasikan metode-metode ini, kode Anda tidak harus melakukan apa pun selain tipe data yang diharapkan. Misalnya, Anda mungkin ingin mencegah aplikasi lain menyisipkan data ke dalam beberapa tabel. Caranya, Anda bisa mengabaikan panggilan ke insert() dan menampilkan 0.

Mengimplementasikan metode query()

Metode ContentProvider.query() harus menampilkan objek Cursor, atau jika gagal, buat Exception. Jika menggunakan database SQLite sebagai penyimpanan data, Anda bisa menampilkan Cursor yang ditampilkan oleh salah satu metode query() dari kelas SQLiteDatabase. Jika kueri tidak cocok dengan baris apa pun, Anda harus menampilkan instance Cursor yang metode getCount()-nya menampilkan 0. Anda harus menampilkan null hanya jika terjadi kesalahan internal selama proses kueri.

Jika Anda tidak menggunakan database SQLite sebagai penyimpanan data, gunakan salah satu subkelas konkret Cursor. Misalnya, kelas MatrixCursor mengimplementasikan kursor dengan masing-masing baris berupa array Object. Dengan kelas ini, gunakan addRow() untuk menambahkan baris baru.

Ingatlah bahwa sistem Android harus mampu mengomunikasikan Exception lintas batas proses. Android bisa melakukannya untuk pengecualian berikut yang mungkin berguna dalam menangani kesalahan kueri:

Mengimplementasikan metode insert()

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

Metode ini harus mengembalikan URI materi untuk baris baru. Untuk membuatnya, tambahkan nilai _ID baris baru (atau kunci utama lainnya) ke tabel URI materi, dengan menggunakan withAppendedId().

Mengimplementasikan metode delete()

Metode delete() tidak harus menghapus baris-baris dari penyimpanan data Anda secara fisik. Jika menggunakan adaptor sinkronisasi bersama penyedia, Anda harus mempertimbangkan penandaan baris yang dihapus dengan flag "hapus"; bukan menghilangkan baris itu sepenuhnya. Adaptor sinkronisasi bisa memeriksa baris yang dibuang dan menghilangkannya dari server sebelum menghapusnya dari penyedia.

Mengimplementasikan metode update()

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

Mengimplementasikan metode onCreate()

Sistem Android memanggil onCreate() saat memulai penyedia. Anda harus melakukan tugas-tugas inisialisasi yang berjalan cepat saja dalam metode ini, serta menunda pembuatan database dan pemuatan data hingga penyedia benar-benar menerima permintaan terhadap data. Jika Anda melakukan tugas yang memakan waktu dalam onCreate(), Anda akan memperlambat startup penyedia. Sebagai gantinya, tugas akan memperlambat respons dari penyedia terhadap aplikasi lain.

Dua cuplikan berikut memperagakan interaksi antara ContentProvider.onCreate() dan Room.databaseBuilder(). Cuplikan ini menunjukkan implementasi ContentProvider.onCreate() tempat objek basis data dibangun 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 Tipe MIME Penyedia Materi

Kelas ContentProvider memiliki dua metode untuk menampilkan tipe-tipe MIME:

getType()
Salah satu metode wajib yang harus Anda implementasikan untuk setiap penyedia.
getStreamTypes()
Sebuah metode yang diharapkan untuk Anda implementasikan jika penyedia Anda menawarkan file.

Tipe MIME untuk tabel

Metode getType() menampilkan String dengan format MIME yang menjelaskan tipe data yang ditampilkan oleh argumen URI materi. Argumen Uri bisa berupa pola, bukannya URI tertentu; dalam hal ini, Anda harus menampilkan tipe data terkait URI materi yang cocok dengan polanya.

Untuk tipe data umum misalnya teks, HTML, atau JPEG, getType() harus menampilkan tipe MIME standar untuk data itu. Daftar lengkap tipe standar ini tersedia di situs Tipe Media IANA MIME.

Untuk URI materi yang menunjuk ke baris atau baris-baris data tabel, getType() harus menampilkan tipe MIME dalam format MIME khusus-vendor Android:

  • Bagian tipe: vnd
  • Bagian subtipe:
    • 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> harus unik secara global, dan nilai <type> harus unik bagi pola URI yang sesuai. Pilihan tepat untuk <name> adalah nama perusahaan Anda atau beberapa bagian dari nama paket Android aplikasi. Pilihan tepat untuk <type> adalah string yang mengidentifikasi tabel yang terkait dengan URI.

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

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

Untuk satu baris table1, tipe 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 tipe MIME untuk file yang bisa ditampilkan penyedia Anda untuk URI materi bersangkutan. Anda harus memfilter tipe MIME yang ditawarkan dengan argumen filter tipe MIME, sehingga Anda hanya menampilkan tipe MIME yang ingin ditangani klien.

Misalnya, perhatikan penyedia yang menawarkan gambar foto sebagai file dalam format .jpg, .png, dan .gif Jika aplikasi memanggil ContentResolver.getStreamTypes() dengan string filter image/* (sesuatu yang merupakan "gambar"), maka metode {ContentProvider.getStreamTypes() harus menampilkan array:

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

Jika aplikasi hanya tertarik pada file .jpg, maka aplikasi bisa memanggil ContentResolver.getStreamTypes() dengan string filter *\/jpeg, dan ContentProvider.getStreamTypes() harus menampilkan:

{"image/jpeg"}

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

Mengimplementasikan kelas kontrak

Kelas kontrak adalah kelas public final yang berisi definisi konstanta untuk URI, nama kolom, tipe MIME, dan metadata lain yang melekat ke penyedia. Kelas membentuk sebuah kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia bisa diakses dengan benar sekalipun ada perubahan pada nilai URI sesungguhnya, nama kolom, dan seterusnya.

Kelas kontrak juga membantu developer karena kelas ini biasanya memiliki nama-nama simbolik untuk konstantanya, sehingga memperkecil kemungkinan developer menggunakan nilai yang salah untuk nama kolom atau URI. Karena berupa kelas, kelas ini bisa berisi dokumentasi Javadoc. Lingkungan pengembangan terpadu seperti Android Studio secara otomatis bisa melengkapi nama-nama konstanta dari kelas kontrak dan menampilkan Javadoc untuk konstanta.

Developer tidak bisa mengakses file kelas milik kelas kontrak dari aplikasi Anda, namun bisa mengompilasinya secara statis ke dalam aplikasi mereka dari file .jar yang Anda sediakan.

Kelas ContactsContract dan kelas-kelas yang ada adalah contoh kelas kontrak.

Mengimplementasikan izin penyedia materi

Izin dan akses untuk semua aspek sistem Android dijelaskan secara detail dalam topik Keamanan dan izin. Topik Penyimpanan Data juga menjelaskan keamanan dan izin terkait dengan berbagai tipe penyimpanan. Singkatnya, poin-poin pentingnya adalah:

  • Secara default, file data yang disimpan pada 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 bisa dibaca secara global. Anda tidak bisa menggunakan penyedia materi untuk membatasi akses ke file dalam penyimpanan eksternal, karena aplikasi lain bisa menggunakan panggilan API lain untuk membaca dan menulis ke file tersebut.
  • Panggilan metode untuk membuka atau membuat file atau database SQLite pada penyimpanan internal perangkat Anda berpotensi memberikan akses baca maupun 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" (bisa ditulis secara global), izin yang Anda atur untuk penyedia dalam manifesnya tidak akan melindungi data Anda. Akses default untuk file dan database dalam penyimpanan internal bersifat "pribadi", dan untuk repositori penyedia, tidak boleh Anda ubah.

Jika Anda ingin menggunakan izin penyedia materi untuk mengontrol akses ke data Anda, maka Anda harus menyimpan data Anda dalam file internal, database SQLite, atau "cloud" (misalnya, di server jauh), dan Anda harus membuat file dan database tetap bersifat pribadi bagi aplikasi Anda.

Mengimplementasikan izin

Semua aplikasi bisa membaca dari atau menulis ke penyedia Anda, sekalipun data yang mendasari bersifat pribadi, karena secara default penyedia Anda tidak mengatur izin. Untuk mengubahnya, atur izin untuk penyedia dalam file manifes, dengan menggunakan atribut atau elemen turunan dari elemen <provider>. Anda bisa mengatur izin yang berlaku pada seluruh penyedia, atau pada tabel tertentu, atau bahkan pada catatan tertentu, atau ketiganya.

Anda mendefinisikan izin untuk penyedia dengan satu atau beberapa elemen <permission> dalam file manifes. Untuk membuat izin unik bagi penyedia, gunakan scoping (pengaturan lingkup) bergaya Java untuk atribut android:name. Misalnya, beri nama izin membaca dengan com.example.app.provider.permission.READ_PROVIDER.

Daftar berikut menjelaskan cakupan penyedia izin, mulai dengan izin yang berlaku pada seluruh penyedia kemudian menjadi semakin sempit. Izin yang lebih sempit akan didahulukan daripada izin dengan cakupan lebih luas:

Izin baca-tulis tunggal tingkat penyedia
Suatu izin yang mengontrol akses baca-tulis bagi seluruh penyedia, ditetapkan dengan atribut android:permission dari elemen <provider>.
Izin baca-tulis terpisah tingkat penyedia
Satu izin baca dan satu izin tulis untuk seluruh penyedia. Anda menetapkan keduanya dengan atribut android:readPermission dan android:writePermission dari elemen <provider>. Kedua izin akan didahulukan daripada izin yang diharuskan oleh android:permission.
Izin tingkat jalur
Izin baca, tulis, atau baca/tulis untuk URI materi dalam penyedia Anda. Anda menetapkan tiap URI yang ingin dikontrol dengan elemen turunan <path-permission> dari elemen <provider>. Untuk setiap URI materi yang ditetapkan, Anda bisa menetapkan satu izin baca/tulis, satu izin baca, atau satu izin tulis, atau ketiganya. Izin baca dan tulis akan didahulukan daripada izin baca/tulis. Serta, izin tingkat jalur akan didahulukan daripada izin tingkat penyedia.
Izin sementara
Tingkat izin yang memberikan akses sementara ke aplikasi, sekalipun aplikasi itu tidak memiliki izin yang biasanya diminta. Fitur akses sementara mengurangi jumlah izin yang harus diminta aplikasi dalam manifesnya. Bila Anda mengaktifkan izin sementara, satu-satunya aplikasi yang memerlukan izin "permanen" untuk penyedia adalah aplikasi yang mengakses terus-menerus semua data Anda.

Perhatikan izin yang Anda perlukan untuk mengimplementasikan penyedia dan aplikasi email, bila Anda ingin memperbolehkan aplikasi penampil gambar dari luar menampilkan lampiran foto dari penyedia Anda. Untuk memberikan akses yang diperlukan kepada penampil gambar tanpa mengharuskan izin, siapkan izin sementara untuk URI materi bagi foto. Desainlah aplikasi email Anda agar bila pengguna ingin menampilkan foto, aplikasi itu akan mengirim intent berisi URI materi foto dan flag izin ke penampil gambar. Penampil gambar nanti bisa melakukan kueri penyedia email untuk mengambil foto, sekalipun penampil itu tidak memiliki izin baca normal untuk penyedia Anda.

Untuk mengaktifkan izin sementara, atur atribut android:grantUriPermissions dari elemen <provider>, atau tambahkan satu atau beberapa elemen turunan <grant-uri-permission> ke elemen <provider> Anda. Jika menggunakan izin sementara, Anda harus memanggil Context.revokeUriPermission() kapan saja Anda membuang dukungan untuk URI materi dari penyedia, dan URI materi dikaitkan dengan izin sementara.

Nilai atribut menentukan seberapa banyak penyedia Anda yang dijadikan bisa diakses. Jika atribut diatur ke true, maka sistem akan memberikan izin sementara kepada seluruh penyedia, dengan mengesampingkan izin lain yang diharuskan oleh izin tingkat penyedia atau tingkat jalur.

Jika flag ini diatur ke false, maka Anda harus menambahkan elemen-elemen turunan <grant-uri-permission> ke elemen <provider> Anda. Tiap elemen turunan menetapkan URI materi atau URI yang telah diberi akses sementara.

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

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

Elemen <provider>

Seperti halnya komponen Activity dan Service, subkelas ContentProvider harus didefinisikan dalam file manifes untuk aplikasinya, dengan menggunakan elemen <provider>. Sistem Android mendapatkan informasi berikut dari elemen:

Otoritas (android:authorities)
Nama-nama simbolik yang mengidentifikasi seluruh penyedia dalam sistem. Atribut ini dijelaskan lebih detail di bagian Mendesain URI Materi.
Nama kelas penyedia ( android:name )
Kelas yang mengimplementasikan ContentProvider. Kelas ini dijelaskan lebih detail di bagian Mengimplementasikan kelas ContentProvider.
Izin
Atribut-atribut yang menetapkan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia:

Izin dan atribut-atribut yang sesuai dijelaskan lebih detail di bagian Mengimplementasikan Izin penyedia materi.

Atribut-atribut startup dan kontrol
Atribut-atribut ini menentukan cara dan waktu sistem Android memulai penyedia, karakteristik proses penyedia, dan setelan waktu proses lainnya:
  • android:enabled: Flag yang memperbolehkan sistem untuk memulai penyedia.
  • android:exported: Flag yang memperbolehkan aplikasi lain untuk menggunakan penyedia ini.
  • android:initOrder: Urutan yang digunakan untuk memulai penyedia ini, relatif terhadap penyedia lain dalam proses yang sama.
  • android:multiProcess: Flag yang memperbolehkan sistem untuk memulai penyedia dalam proses yang sama dengan proses klien pemanggil.
  • android:process: Nama proses tempat penyedia harus berjalan.
  • android:syncable: Flag yang menunjukkan bahwa data penyedia harus disinkronkan dengan data di server.

Atribut-atribut ini didokumentasikan dengan lengkap dalam topik panduan developer untuk elemen <provider>.

Atribut-atribut informatif
Ikon dan label opsional untuk penyedia:
  • android:icon: Sumber daya dapat digambar, berisi ikon untuk penyedia. Ikon ini muncul di sebelah label penyedia dalam daftar aplikasi dalam menu Setelan > Aplikasi > Semua.
  • android:label: Label informatif yang menjelaskan penyedia atau datanya, atau keduanya. Label ini muncul dalam daftar aplikasi di Setelan > Aplikasi > Semua.

Atribut-atribut ini didokumentasikan dengan lengkap dalam topik panduan developer untuk elemen <provider>.

Intent dan akses data

Aplikasi bisa mengakses penyedia materi secara tidak langsung dengan sebuah Intent. Aplikasi tidak memanggil satu pun dari metode-metode ContentResolver atau ContentProvider. Sebagai gantinya, aplikasi mengirim intent yang memulai aktivitas, yang sering kali merupakan bagian dari aplikasi penyedia sendiri. Aktivitas tujuan bertugas mengambil dan menampilkan data dalam UI-nya. Bergantung pada tindakan dalam intent, aktivitas tujuan juga bisa meminta pengguna untuk membuat modifikasi pada data penyedia. Intent juga bisa berisi data "tambahan" yang ditampilkan aktivitas tujuan dalam UI; pengguna nanti memiliki opsi untuk mengubah data ini sebelum menggunakannya untuk memodifikasi data di penyedia.

Anda mungkin perlu menggunakan akses intent guna membantu memastikan integritas data. Penyedia Anda mungkin bergantung pada data yang disisipkan, diperbarui, dan dihapusnya sesuai dengan logika bisnis yang didefinisikan dengan ketat. Jika demikian halnya, memperbolehkan aplikasi lain mengubah data Anda secara langsung bisa menyebabkan data yang tidak valid. Jika Anda ingin developer menggunakan akses intent, pastikan untuk mendokumentasikannya secara saksama. Jelaskan kepada mereka alasan akses intent yang menggunakan UI aplikasi Anda sendiri adalah lebih baik daripada mencoba memodifikasi data dengan kode mereka.

Menangani sebuah intent masuk yang ingin memodifikasi data penyedia Anda tidak berbeda dengan menangani intent lainnya. Anda bisa mengetahui selengkapnya tentang penggunaan intent dengan membaca topik Intent dan Filter Intent.

Untuk kode sampel yang terkait dengan halaman ini, lihat aplikasi contoh Note Pad.

Untuk informasi terkait tambahan, lihat Penyedia Kalender.