lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

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 Membangun

Sebelum Anda mulai membangun penyedia, lakukanlah hal-hal berikut:

  1. Putuskan apakah Anda memerlukan penyedia materi. Anda perlu membangun 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 tidak mengharuskan penyedia untuk menggunakan database SQLite jika hanya digunakan dalam aplikasi sendiri.

  2. Jika Anda belum siap melakukannya, bacalah topik Dasar-Dasar Penyedia Materi untuk mengetahui selengkapnya tentang penyedia.

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 privat 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 storage apa saja yang persisten. Untuk mengetahui selengkapnya tentang tipe storage yang tersedia di sistem Android, lihat bagian Mendesain Storage Data.
  2. Definisikan sebuah implementasi konkret kelas ContentProvider dan metode yang diperlukannya. Kelas ini adalah antarmuka antara data Anda dan bagian selebihnya 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 maksud, definisikan juga semua tindakan maksud, data tambahan, dan flag. Definisikan juga izin yang akan Anda syaratkan terhadap aplikasi yang ingin mengakses data Anda. Anda harus mempertimbangkan pendefinisian 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 maksud, lihat bagian Maksud dan Akses Data.
  4. Tambahkan bagian opsional lainnya, seperti data contoh atau implementasi AbstractThreadedSyncAdapter yang bisa menyinkronkan data antara penyedia dan data berbasis awan.

Mendesain Storage 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 storage data yang tersedia di Android:

  • Sistem Android menyertakan SQLite Database API yang digunakan penyedia Android sendiri untuk menyimpan data berorientasi tabel. Kelas SQLiteOpenHelper membantu Anda membuat database, dan kelas SQLiteDatabase adalah kelas dasar untuk mengakses database.

    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 mengetahui selengkapnya tentang storage file, bacalah topik Storage 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.
  • 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 Sample Sync Adapter memperagakan tipe sinkronisasi ini.

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 nama _ID.
  • 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 mendefinisikan 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 mendefinisikan 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 aksi 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 aksi 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() mengembalikan nilai integer URI. Pernyataan switch memilih antara melakukan kueri seluruh tabel dan melakukan kueri satu catatan:

public class ExampleProvider extends ContentProvider {
...
    // Creates a UriMatcher object.
    private static final UriMatcher sUriMatcher = 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
         */
        sUriMatcher.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.
         */
        sUriMatcher.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 (sUriMatcher.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 membangun 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 dikembalikan, dan urutan sortir hasilnya. Mengembalikan 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. Mengembalikan 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-metode ContentResolver yang sama namanya.

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 mengetahui selengkapnya 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 mengembalikan 0.

Mengimplementasikan metode query()

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

Jika Anda tidak menggunakan database SQLite sebagai storage data, gunakan salah satu subkelas konkret Cursor. Misalnya, kelas MatrixCursor mengimplementasikan kursor dengan masing-masing baris berupa larik 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 storage data Anda secara fisik. Jika menggunakan adaptor sinkronisasi bersama penyedia, Anda harus mempertimbangkan penandaan baris yang dihapus dengan flag "delete"; bukan menghilangkan baris itu sepenuhnya. Adaptor sinkronisasi bisa memeriksa baris yang dibuang dan menghilangkannya dari server sebelum membuangnya dari penyedia.

Mengimplementasikan metode update()

Metode update() mengambil argumen ContentValues yang sama dengan yang digunakan oleh insert(), dan argumen-argumen selection dan selectionArgs yang sama dengan yang 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. Pada gilirannya, hal ini akan memperlambat respons dari penyedia terhadap aplikasi lain.

Misalnya, jika menggunakan database SQLite, Anda bisa membuat sebuah objek SQLiteOpenHelper baru di ContentProvider.onCreate(), kemudian membuat tabel-tabel SQL saat pertama kali membuka database itu. Untuk memperlancar hal ini, saat pertama Anda memanggil getWritableDatabase(), metode ini memanggil metode SQLiteOpenHelper.onCreate() secara otomatis.

Dua cuplikan berikut memperagakan interaksi antara ContentProvider.onCreate() dan SQLiteOpenHelper.onCreate(). Cuplikan pertama adalah implementasi ContentProvider.onCreate():

public class ExampleProvider extends ContentProvider

    /*
     * Defines a handle to the database helper object. The MainDatabaseHelper class is defined
     * in a following snippet.
     */
    private MainDatabaseHelper mOpenHelper;

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

    // Holds the database object
    private SQLiteDatabase db;

    public boolean onCreate() {

        /*
         * Creates a new helper object. This method always returns quickly.
         * Notice that the database itself isn't created or opened
         * until SQLiteOpenHelper.getWritableDatabase is called
         */
        mOpenHelper = new MainDatabaseHelper(
            getContext(),        // the application context
            DBNAME,              // the name of the database)
            null,                // uses the default SQLite cursor
            1                    // the version number
        );

        return true;
    }

    ...

    // Implements the provider's insert method
    public Cursor insert(Uri uri, ContentValues values) {
        // Insert code here to determine which table to open, handle error-checking, and so forth

        ...

        /*
         * Gets a writeable database. This will trigger its creation if it doesn't already exist.
         *
         */
        db = mOpenHelper.getWritableDatabase();
    }
}

Cuplikan berikutnya adalah implementasi SQLiteOpenHelper.onCreate(), yang menyertakan kelas helper:

...
// A string that defines the SQL statement for creating a table
private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
    "main " +                       // Table's name
    "(" +                           // The columns in the table
    " _ID INTEGER PRIMARY KEY, " +
    " WORD TEXT"
    " FREQUENCY INTEGER " +
    " LOCALE TEXT )";
...
/**
 * Helper class that actually creates and manages the provider's underlying data repository.
 */
protected static final class MainDatabaseHelper extends SQLiteOpenHelper {

    /*
     * Instantiates an open helper for the provider's SQLite data repository
     * Do not do database creation and upgrade here.
     */
    MainDatabaseHelper(Context context) {
        super(context, DBNAME, null, 1);
    }

    /*
     * Creates the data repository. This is called when the provider attempts to open the
     * repository and SQLite reports that it doesn't exist.
     */
    public void onCreate(SQLiteDatabase db) {

        // Creates the main table
        db.execSQL(SQL_CREATE_MAIN);
    }
}

Mengimplementasikan Tipe MIME Penyedia Materi

Kelas ContentProvider memiliki dua metode untuk mengembalikan 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() mengembalikan String dengan format MIME yang menjelaskan tipe data yang dikembalikan oleh argumen URI materi. Argumen Uri bisa berupa pola, bukannya URI tertentu; dalam hal ini, Anda harus mengembalikan tipe data terkait URI materi yang cocok dengan polanya.

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

Untuk URI materi yang menunjuk ke baris atau baris-baris data tabel, getType() harus mengembalikan 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 sebagian 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 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 mengembalikan larik String tipe MIME untuk file yang bisa dikembalikan penyedia Anda untuk URI materi bersangkutan. Anda harus memfilter tipe MIME yang ditawarkan dengan argumen filter tipe MIME, sehingga Anda hanya mengembalikan 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 mengembalikan larik:

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

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

{"image/jpeg"}

Jika penyedia Anda tidak menawarkan tipe MIME apa pun yang diminta dalam string filter, getStreamTypes() harus mengembalikan 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 development terpadu (IDE) 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 tersarangnya 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 Storage Data juga menjelaskan keamanan dan izin terkait dengan berbagai tipe storage. Singkatnya, poin-poin pentingnya adalah:

  • Secara default, file data yang disimpan pada penyimpanan internal perangkat bersifat privat bagi aplikasi dan penyedia Anda.
  • Database SQLiteDatabase yang Anda buat bersifat privat 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 adalah "privat", 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 "awan" (misalnya, di server jauh), dan Anda harus membuat file dan database tetap privat bagi aplikasi Anda.

Mengimplementasikan izin

Semua aplikasi bisa membaca dari atau menulis ke penyedia Anda, sekalipun data yang mendasari adalah privat, karena secara default penyedia Anda tidak mengatur izin. Untuk mengubahnya, atur izin untuk penyedia dalam file manifes, dengan menggunakan atribut atau elemen anak 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 anak <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. Juga, 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 maksud 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 anak <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 anak <grant-uri-permission> ke elemen <provider> Anda. Tiap elemen anak menetapkan URI materi atau URI yang telah diberi akses sementara.

Untuk mendelegasikan akses sementara ke sebuah aplikasi, maksud 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 pengembang 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 Settings > Apps > All.
  • android:label: Label informatif yang menjelaskan penyedia atau datanya, atau keduanya. Label ini muncul dalam daftar aplikasi di Settings > Apps > All.

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

Maksud 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 maksud yang memulai aktivitas, yang sering kali merupakan bagian dari aplikasi penyedia sendiri. Aktivitas tujuan bertugas mengambil dan menampilkan data dalam UI-nya. Bergantung pada aksi dalam maksud, aktivitas tujuan juga bisa meminta pengguna untuk membuat modifikasi pada data penyedia. Maksud 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 maksud 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 maksud, pastikan untuk mendokumentasikannya secara saksama. Jelaskan kepada mereka alasan akses maksud yang menggunakan UI aplikasi Anda sendiri adalah lebih baik daripada mencoba memodifikasi data dengan kode mereka.

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