Membuat penyedia konten

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

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

Halaman ini berisi proses dasar untuk membuat 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 konten jika Anda 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 AbstractThreadedSyncAdapter, CursorAdapter, atau CursorLoader Google Cloud Platform.

    Anda tidak memerlukan penyedia untuk menggunakan database atau jenis penyimpanan persisten jika penggunaan sepenuhnya berada dalam aplikasi Anda sendiri dan Anda tidak memerlukan fitur sebelumnya yang tercantum. 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 dalam direktori pribadi aplikasi spasi. Sebagai respons terhadap permintaan file dari aplikasi lain, penyedia bisa menawarkan {i> handle<i} ke file tersebut.
    "Terstruktur" data
    Data yang biasanya masuk ke dalam {i>database<i}, {i>array<i}, 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 data untuk entitas, seperti nama orang atau harga barang. Cara umum untuk jenis data ini disimpan dalam database SQLite, namun Anda bisa menggunakan dan penyimpanan persisten. Untuk mempelajari lebih lanjut jenis penyimpanan yang tersedia di sistem Android, lihat Bagian penyimpanan data desain.
  2. Tentukan implementasi konkret dari class ContentProvider dan metode-metode yang diperlukannya. Class ini adalah antarmuka antara data Anda dan elemen Android. Untuk informasi selengkapnya tentang kelas ini, lihat Implementasikan class ContentProvider.
  3. Definisikan string otoritas, URI konten, dan nama kolom penyedia. Jika Anda ingin aplikasi penyedia untuk menangani intent, juga mendefinisikan tindakan intent, data tambahan, dan flag. Definisikan juga izin yang Anda butuhkan untuk aplikasi yang ingin untuk mengakses data Anda. Pertimbangkan untuk mendefinisikan semua nilai ini sebagai konstanta dalam kelas kontrak terpisah. Nanti, Anda dapat mengekspos class ini kepada developer lain. Untuk selengkapnya informasi tentang URI konten, lihat Bagian Mendesain URI konten. Untuk informasi selengkapnya tentang intent, lihat class Bagian Intent dan akses data.
  4. Tambahkan bagian opsional lainnya, seperti data sampel atau penerapan dari 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, memutuskan bagaimana menyimpan data. Anda dapat menyimpan data dalam bentuk apa pun seperti, lalu rancang antarmuka untuk membaca dan menulis data yang diperlukan.

Berikut adalah beberapa teknologi penyimpanan data yang tersedia di Android:

  • Jika Anda bekerja dengan data terstruktur, maka pertimbangkan salah satu {i>database<i} relasional seperti 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 data sebagai file. Anda dapat mengombinasikan beberapa jenis penyimpanan dan mengeksposnya menggunakan satu penyedia konten jika diperlukan.
  • Sistem Android dapat berinteraksi dengan library persistensi Room, yang memberikan akses ke SQLite database API yang disediakan oleh penyedia Android gunakan untuk menyimpan data berorientasi tabel. Untuk membuat {i>database <i}menggunakan membuat instance subclass dari RoomDatabase, sebagaimana dijelaskan di Menyimpan data di dalam database lokal menggunakan Room.

    Anda tidak harus menggunakan database untuk mengimplementasikan repositori. Penyedia muncul secara eksternal sebagai satu set tabel, mirip dengan {i>database<i} 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 merancang 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 mendapat manfaat dari menerapkan lebih dari satu penyedia konten untuk aplikasi tunggal. Misalnya, Anda mungkin ingin berbagi beberapa data dengan widget menggunakan satu penyedia konten, dan mengekspos set data yang berbeda untuk dibagikan menggunakan berbagai aplikasi obrolan.
  • Untuk menggunakan data berbasis jaringan, gunakan class di java.net dan android.net. Anda juga dapat menyinkronkan data berbasis jaringan ke data lokal menyimpan seperti {i>database<i}, dan kemudian menawarkan data itu sebagai tabel atau file.

Catatan: Jika Anda membuat perubahan pada repositori yang tidak agar kompatibel dengan versi sebelumnya, Anda perlu menandai repositori dengan versi baru angka Anda juga perlu meningkatkan nomor versi untuk aplikasi Anda yang menerapkan penyedia konten baru. Melakukan perubahan ini akan mencegah sistem menurunkan versi agar sistem macet saat mencoba menginstal ulang 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 "kunci utama" yang dikelola penyedia sebagai nilai numerik unik untuk setiap baris. Anda dapat menggunakan nilai ini untuk menautkan baris ke baris terkait baris di tabel lain (dengan menggunakannya sebagai "foreign key"). Meskipun Anda dapat menggunakan nama apa pun untuk kolom ini, menggunakan BaseColumns._ID adalah cara terbaik pilihan, karena menautkan hasil kueri penyedia ke ListView memerlukan salah satu kolom yang diambil memiliki nama _ID.
  • Jika Anda ingin menyediakan gambar bitmap atau potongan data berorientasi file lainnya yang sangat besar, simpan data dalam file dan kemudian menyediakannya secara tidak langsung alih-alih menyimpannya secara langsung di tabel sementara. Jika Anda 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. Di beberapa jenis tabel ini, Anda mendefinisikan kolom {i>primary key<i}, kolom jenis MIME, dan satu atau lebih banyak kolom umum sebagai BLOB. Arti data dalam kolom BLOB ditunjukkan dengan nilai dalam kolom jenis MIME. Ini memungkinkan Anda menyimpan berbagai tipe baris di tabel yang sama. "Data" Penyedia Kontak meja ContactsContract.Data adalah contoh project yang tidak bergantung pada skema tabel sementara.

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 baris individu dalam tabel. Setiap metode akses data dari ContentProvider memiliki URI konten sebagai argumen. Hal ini memungkinkan Anda 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. Kepada menghindari konflik dengan penyedia lain, menggunakan kepemilikan domain internet (secara terbalik) sebagai dasar otoritas penyedia Anda. Karena rekomendasi ini juga berlaku untuk Android nama paket, Anda dapat menentukan otoritas penyedia Anda sebagai perpanjangan dari nama dari paket yang berisi penyedia.

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

Mendesain struktur jalur

Developer biasanya membuat URI konten dari otoritas dengan menambahkan jalur yang menunjuk ke tabel individual. 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 terbatas pada satu segmen, dan tidak perlu memiliki 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 di akhir URI. Juga berdasarkan konvensi, penyedia mencocokkan ke kolom _ID tabel dan melakukan akses yang diminta terhadap baris yang sesuai.

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

Pengguna kemudian mengambil salah satu baris yang ditampilkan dari UI untuk melihat atau memodifikasi layanan otomatis dan data skalabel. Aplikasi mendapatkan baris yang sesuai dari Cursor yang mendukung ListView, dapatkan nilai _ID untuk baris ini, menambahkannya ke URI materi, dan mengirimkan permintaan akses ke penyedia. Penyedia kemudian dapat melakukan terhadap baris yang tepat yang dipilih pengguna.

Pola URI materi

Untuk membantu Anda memilih tindakan yang harus diambil untuk URI konten yang masuk, API penyedia menyertakan class praktis UriMatcher, 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 perancangan dan pengkodean penanganan URI konten, pertimbangkan penyedia dengan com.example.app.provider otoritas yang mengenali URI konten berikut 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 di penyedia.
content://com.example.app.provider/table2/*
Cocok dengan URI konten untuk tabel dataset1 dan dataset2, namun tidak cocok dengan URI konten untuk table1 atau table3.
content://com.example.app.provider/table3/#
Cocok dengan URI konten untuk baris tunggal dalam 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 dari URI untuk satu baris dengan 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 melakukan kueri seluruh tabel dan melakukan kueri untuk satu {i>record<i}.

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 lain, ContentUris, menyediakan metode praktis untuk bekerja dengan bagian id dari URI konten. Class Uri dan Uri.Builder menyertakan metode praktis untuk mengurai yang ada Uri objek dan membuat objek baru.

Mengimplementasikan class ContentProvider

Instance ContentProvider mengelola akses ke satu set data terstruktur dengan menangani permintaan dari aplikasi lain. Semua formulir akses akhirnya memanggil ContentResolver, yang kemudian memanggil dari ContentProvider untuk mendapatkan akses.

Metode yang diperlukan

Class abstrak ContentProvider menentukan enam metode abstrak yang 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 kueri, baris dan kolom yang akan dikembalikan, dan tata urutan hasilnya. Tampilkan data sebagai objek Cursor.
insert()
Masukkan baris baru ke penyedia Anda. Gunakan argumen untuk memilih tabel tujuan dan untuk mendapatkan nilai-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 untuk memperbarui 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 hapus. Mengembalikan jumlah baris yang dihapus.
getType()
Menampilkan jenis MIME yang sesuai dengan URI konten. Metode ini dijelaskan lebih lanjut di bagian Mengimplementasikan jenis MIME penyedia konten.
onCreate()
Lakukan inisialisasi pada penyedia. Sistem Android memanggil metode ini segera setelahnya membuat penyedia Anda. Penyedia Anda tidak dibuat sampai Objek ContentResolver mencoba mengaksesnya.

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

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 selengkapnya tentang beberapa thread, 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 rinci.
  • Walaupun harus mengimplementasikan metode-metode ini, kode Anda tidak perlu melakukan apa pun kecuali menampilkan jenis data yang diharapkan. Misalnya, Anda dapat mencegah aplikasi lain agar tidak memasukkan data ke dalam beberapa tabel dengan mengabaikan panggilan ke insert() dan kembali 0.

Mengimplementasikan metode query()

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

Jika kueri tidak cocok dengan baris mana pun, tampilkan Cursor instance 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 merupakan array instance Object. Dengan kelas ini, gunakan addRow() untuk menambahkan baris baru.

Sistem Android harus dapat menyampaikan 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 ContentValues argumen. Jika nama kolom tidak ada dalam argumen ContentValues, Anda mungkin ingin memberikan nilai default untuknya, baik di kode penyedia maupun di database Anda skema.

Metode ini menampilkan URI konten untuk baris baru. Untuk membangun ini, tambahkan kunci utama baris, 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 telah dihapus dengan kata "delete" bukan menghapus baris secara keseluruhan. Adaptor sinkronisasi dapat periksa baris yang telah dihapus dan membuangnya dari server sebelum menghapusnya dari penyedia.

Mengimplementasikan metode update()

Metode update() mengambil argumen ContentValues yang sama dengan yang digunakan oleh insert() dan 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() pada saat memulai penyedia. Hanya melakukan inisialisasi yang berjalan cepat tugas dalam metode ini dan menunda pembuatan database dan pemuatan data hingga penyedia benar-benar menerima permintaan data. Jika Anda melakukan tugas yang panjang di onCreate(), Anda memperlambat penyedia layanan startup. Pada akhirnya, hal ini akan memperlambat respons dari penyedia terhadap menggunakan berbagai aplikasi obrolan.

Dua cuplikan berikut menunjukkan interaksi antara ContentProvider.onCreate() dan Room.databaseBuilder(). Yang pertama cuplikan yang menunjukkan implementasi ContentProvider.onCreate() di mana 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() akan menampilkan String dalam format MIME yang menjelaskan jenis data yang ditampilkan oleh isi Argumen URI. 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 standar Jenis MIME untuk data tersebut. Daftar lengkap jenis standar ini tersedia di Jenis Media MIME IANA situs Anda.

Untuk URI konten yang menunjuk ke satu baris atau beberapa baris data tabel, Pengembalian dengan biaya getType() 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 URI yang sesuai pola. Pilihan yang tepat untuk <name> adalah nama perusahaan Anda atau beberapa bagian dari nama paket Android aplikasi Anda. Pilihan yang bagus untuk <type> adalah string yang mengidentifikasi tabel yang terkait dengan URI.

Misalnya, jika otoritas penyedia melakukan com.example.app.provider, dan 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 penyedia Anda dapat ditampilkan untuk URI konten tertentu. Filter jenis MIME yang Anda tawarkan berdasarkan jenis MIME filter, sehingga Anda hanya menampilkan jenis MIME yang ingin ditangani klien.

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

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

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

{"image/jpeg"}

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

Mengimplementasikan kelas kontrak

Kelas kontrak adalah class public final yang berisi definisi konstan untuk URI, nama kolom, jenis MIME, dan metadata lain yang berhubungan dengan penyedia. Kelas membuat kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia dapat diakses dengan benar meskipun ada perubahan pada nilai aktual URI, nama kolom, dan sebagainya.

Kelas kontrak juga membantu pengembang karena biasanya memiliki nama-nama simbolik untuk konstantanya, sehingga developer cenderung tidak menggunakan nilai yang salah untuk nama kolom atau URI. Karena ini adalah , dapat berisi dokumentasi Javadoc. {i>Integrated Development Environment<i} seperti Android Studio bisa melengkapi nama konstanta dari kelas kontrak secara otomatis dan menampilkan Javadoc untuk konstanta tersebut.

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

Class ContactsContract dan class bertingkatnya adalah contoh kelas kontrak.

Mengimplementasikan izin penyedia konten

Izin dan akses untuk semua aspek sistem Android dijelaskan secara rinci 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 untuk Anda aplikasi dan penyedia layanan.
  • Database SQLiteDatabase yang Anda buat bersifat pribadi untuk aplikasi dan penyedia layanan.
  • 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 di penyimpanan eksternal, karena aplikasi lain bisa menggunakan panggilan API lain untuk membaca dan menulisnya.
  • Panggilan metode ini untuk membuka atau membuat file atau database SQLite pada server penyimpanan data dapat memberikan akses baca dan tulis ke semua aplikasi lain. Jika Anda menggunakan file atau database internal sebagai repositori penyedia Anda dan Anda memberikannya "dapat dibaca oleh seluruh dunia" atau "world-writeable" (dapat ditulis oleh publik) izin akses, izin akses yang Anda tetapkan untuk penyedia. manifesnya tidak melindungi data Anda. Akses {i>default<i} untuk file dan {i>database<i} di penyimpanan internal bersifat "pribadi"; jangan ubah ini untuk repositori penyedia Anda.

Jika Anda ingin menggunakan izin penyedia konten untuk mengontrol akses ke data Anda, maka menyimpan data Anda dalam file internal, database SQLite, atau cloud, seperti pada server jarak jauh, dan menjaga file dan database tetap pribadi untuk aplikasi Anda.

Mengimplementasikan izin

Secara default, semua aplikasi bisa membaca dari atau menulis ke penyedia Anda, bahkan jika data yang mendasarinya {i>private<i}, karena secara {i>default<i} penyedia Anda tidak mengatur izin akses. Untuk mengubahnya, mengatur izin bagi penyedia dalam file manifes, menggunakan atribut atau elemen dari elemen <provider>. Anda dapat mengatur izin akses yang berlaku untuk seluruh penyedia, ke tabel tertentu, ke {i>record <i}tertentu, atau ketiganya.

Anda menentukan izin untuk penyedia dengan satu atau beberapa <permission> dalam file manifes Anda. Untuk membuat izin unik untuk penyedia, gunakan pencakupan bergaya Java untuk Atribut android:name. Misalnya, beri nama izin akses baca com.example.app.provider.permission.READ_PROVIDER.

Daftar berikut ini menjelaskan cakupan izin penyedia, dimulai dengan izin akses yang berlaku untuk seluruh penyedia dan kemudian menjadi lebih 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 <provider>.
Pisahkan izin baca dan tulis tingkat penyedia
Satu izin baca dan satu izin tulis untuk seluruh penyedia. Anda yang menentukannya dengan android:readPermission dan android:writePermission atribut <provider>. Izin akses akan didahulukan daripada izin yang disyaratkan oleh android:permission.
Izin tingkat path
Izin membaca, menulis, atau membaca/menulis untuk URI konten di penyedia Anda. Anda yang menentukan setiap URI yang ingin Anda kontrol dengan Elemen turunan <path-permission> dari elemen <provider>. Untuk setiap URI konten yang ditetapkan, Anda dapat menentukan izin akses baca/tulis, izin akses membaca, izin akses tulis, atau ketiganya. Fungsi baca dan izin akses tulis akan didahulukan daripada izin akses baca/tulis. Selain itu, tingkat jalur lebih diprioritaskan daripada izin tingkat penyedia.
Izin sementara
Tingkat izin yang memberikan akses sementara ke aplikasi, bahkan jika aplikasi tersebut tidak memiliki izin yang biasanya diperlukan. Atribut sementara fitur akses mengurangi jumlah izin yang harus diminta aplikasi manifesnya. Ketika Anda mengaktifkan izin akses sementara, satu-satunya aplikasi yang memerlukan izin akses permanen untuk penyedia Anda adalah izin akses yang terus-menerus memahami data Anda.

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

Rancang aplikasi email Anda agar sehingga saat pengguna ingin menampilkan foto, aplikasi akan mengirimkan intent yang berisi URI materi foto dan tanda izin ke penampil gambar. Penampil gambar dapat kemudian kueri penyedia email Anda untuk mengambil foto, meskipun pemirsa tidak memiliki izin baca normal untuk penyedia Anda.

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

Nilai atribut menentukan seberapa banyak penyedia Anda yang dapat diakses. Jika atribut ditetapkan ke "true", sistem akan memberikan izin sementara izin akses ke seluruh penyedia, menggantikan izin lain yang diperlukan berdasarkan izin tingkat penyedia atau tingkat jalur Anda.

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

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

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

<provider> elemen

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

Kewenangan (android:authorities)
Nama-nama simbolis yang mengidentifikasi seluruh penyedia dalam sistem. Ini dijelaskan secara lebih mendetail dalam Bagian Mendesain URI konten.
Nama class penyedia (android:name)
Class yang mengimplementasikan ContentProvider. Kelas ini yang dijelaskan secara lebih mendetail dalam Implementasikan class ContentProvider.
Izin
Atribut yang menentukan izin yang harus dimiliki aplikasi lain untuk mengakses data penyedia:

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

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

Atribut ini didokumentasikan secara lengkap dalam panduan untuk Elemen <provider>.

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

Atribut ini didokumentasikan secara lengkap 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. Sebagai gantinya, ia mengirim intent yang memulai aktivitas, yang sering kali merupakan bagian dari aplikasi penyedia itu sendiri. Aktivitas tujuan bertanggung jawab atas 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 dapat berisi "tambahan" data yang ditampilkan oleh aktivitas tujuan di UI. Pengguna kemudian 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 data disisipkan, diperbarui, dan dihapus sesuai dengan logika bisnis yang didefinisikan dengan ketat. Jika dalam hal ini, membiarkan aplikasi lain secara langsung mengubah data Anda dapat menyebabkan data tidak valid.

Jika Anda ingin developer menggunakan akses intent, pastikan untuk mendokumentasikannya secara saksama. Jelaskan mengapa akses intent yang menggunakan UI aplikasi Anda lebih baik daripada mencoba memodifikasi data dengan kode mereka.

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

Untuk informasi terkait lainnya, lihat Ringkasan penyedia kalender.