Penyedia konten mengelola akses ke repositori data pusat. Anda mengimplementasikan
penyedia sebagai satu atau beberapa class dalam aplikasi Android, bersama elemen dalam
file manifes. Salah satu class Anda mengimplementasikan subclass
ContentProvider
, yang merupakan antarmuka antara penyedia Anda dan
aplikasi lain.
Meskipun penyedia konten dimaksudkan untuk menyediakan data bagi aplikasi lain, Anda dapat memiliki aktivitas di aplikasi yang memungkinkan pengguna membuat kueri dan mengubah data yang dikelola oleh penyedia Anda.
Halaman ini berisi proses dasar untuk membangun penyedia konten dan daftar API yang akan digunakan.
Sebelum Anda mulai membuat
Sebelum Anda mulai membuat penyedia, perhatikan hal-hal berikut:
-
Tentukan apakah Anda memerlukan penyedia konten. Anda harus membangun penyedia
konten jika ingin menyediakan satu atau beberapa fitur berikut:
- Anda ingin menawarkan data atau file yang kompleks ke aplikasi lain.
- Anda ingin mengizinkan pengguna menyalin data yang kompleks dari aplikasi Anda ke dalam aplikasi lain.
- Anda ingin menyediakan saran penelusuran khusus dengan menggunakan kerangka kerja penelusuran.
- Anda ingin mengekspos aplikasi Anda data ke widget.
- Anda ingin mengimplementasikan class
AbstractThreadedSyncAdapter
,CursorAdapter
, atauCursorLoader
.
Anda tidak memerlukan penyedia untuk menggunakan database atau jenis penyimpanan persisten lainnya jika penggunaannya sepenuhnya dalam aplikasi Anda sendiri dan Anda tidak memerlukan fitur yang tercantum sebelumnya. Sebagai gantinya, Anda dapat menggunakan salah satu sistem penyimpanan yang dijelaskan dalam Ringkasan penyimpanan data dan file.
- Jika Anda belum melakukannya, baca Dasar-dasar penyedia konten untuk mempelajari lebih lanjut penyedia dan cara kerjanya.
Berikutnya, ikuti langkah-langkah ini untuk membangun penyedia:
-
Desain storage mentah untuk data Anda. Penyedia konten menawarkan data dengan dua cara:
- Data file
- Data yang biasanya masuk ke dalam file, seperti foto, audio, atau video. Simpan file di ruang pribadi aplikasi Anda. Merespons permintaan file dari aplikasi lain, penyedia Anda dapat menawarkan handle ke file tersebut.
- Data "terstruktur"
- Data yang biasanya masuk ke dalam database, array, atau struktur serupa. Simpan data dalam bentuk yang kompatibel dengan tabel baris dan kolom. Baris mewakili entitas, seperti orang atau item dalam inventaris. Kolom mewakili beberapa data untuk entity, seperti nama orang atau harga item. Cara umum untuk menyimpan jenis data ini adalah dalam database SQLite, tetapi Anda dapat menggunakan semua jenis penyimpanan persisten. Untuk mempelajari jenis penyimpanan yang tersedia di sistem Android lebih lanjut, lihat bagian Mendesain penyimpanan data.
-
Tentukan implementasi konkret class
ContentProvider
dan metode yang diperlukannya. Class ini adalah antarmuka antara data Anda dan bagian sistem Android lainnya. Untuk informasi selengkapnya tentang class ini, lihat bagian Mengimplementasikan class ContentProvider. - Definisikan string otoritas, URI materi, dan nama kolom penyedia. Jika Anda ingin aplikasi penyedia menangani intent, tentukan juga tindakan intent, data tambahan, dan flag. Tentukan juga izin yang Anda perlukan untuk aplikasi yang ingin mengakses data Anda. Pertimbangkan untuk menentukan semua nilai ini sebagai konstanta dalam class kontrak terpisah. Nantinya, Anda dapat mengekspos class ini kepada developer lain. Untuk informasi selengkapnya tentang URI konten, lihat bagian Mendesain URI konten. Untuk informasi selengkapnya tentang intent, lihat bagian Intent dan akses data.
-
Tambahkan bagian opsional lainnya, seperti data sampel atau implementasi
AbstractThreadedSyncAdapter
yang dapat menyinkronkan data antara penyedia dan data berbasis cloud.
Merancang penyimpanan data
Penyedia materi adalah antarmuka ke data yang disimpan dalam format terstruktur. Sebelum membuat antarmuka, tentukan cara menyimpan data. Anda dapat menyimpan data dalam bentuk apa pun yang Anda sukai, lalu mendesain antarmuka untuk membaca dan menulis data sesuai kebutuhan.
Berikut adalah beberapa teknologi penyimpanan data yang tersedia di Android:
- Jika Anda bekerja dengan data terstruktur, pertimbangkan database relasional seperti SQLite atau datastore nilai kunci non-relasional seperti LevelDB. Jika Anda menangani data tidak terstruktur seperti audio, gambar, atau media video, maka pertimbangkan untuk menyimpan data tersebut sebagai file. Anda dapat memadupadankan beberapa jenis penyimpanan dan mengeksposnya menggunakan satu penyedia konten jika perlu.
-
Sistem Android dapat berinteraksi dengan library persistensi Room, yang menyediakan akses ke API database SQLite yang digunakan oleh penyedia Android sendiri untuk menyimpan data berorientasi tabel. Untuk membuat database menggunakan library
ini, buat instance subclass
RoomDatabase
, seperti yang dijelaskan dalam Menyimpan data di database lokal menggunakan Room.Anda tidak harus menggunakan database untuk mengimplementasikan repositori. Penyedia muncul secara eksternal sebagai satu set tabel, yang mirip dengan database relasional, tetapi ini bukan persyaratan untuk implementasi internal penyedia.
- Untuk menyimpan file data, Android memiliki beragam API berorientasi file. Untuk mempelajari penyimpanan file lebih lanjut, baca Ringkasan penyimpanan data dan file. Jika Anda sedang mendesain penyedia yang menawarkan data terkait media seperti musik atau video, Anda dapat memiliki penyedia yang menggabungkan data tabel dan file.
- Dalam kasus yang jarang terjadi, Anda mungkin mendapatkan manfaat dari menerapkan lebih dari satu penyedia konten untuk satu aplikasi. Misalnya, Anda mungkin ingin berbagi beberapa data dengan widget menggunakan satu penyedia konten, dan mengekspos set data yang berbeda untuk dibagikan dengan aplikasi lain.
-
Untuk bekerja dengan data berbasis jaringan, gunakan class dalam
java.net
danandroid.net
. Anda juga dapat menyinkronkan data berbasis jaringan ke penyimpanan data lokal seperti database, lalu menawarkan data sebagai tabel atau file.
Catatan: Jika Anda membuat perubahan pada repositori yang tidak kompatibel dengan versi sebelumnya, Anda harus menandai repositori dengan nomor versi baru. Anda juga perlu menambah nomor versi untuk aplikasi yang mengimplementasikan penyedia konten baru. Melakukan perubahan ini akan mencegah downgrade sistem menyebabkan sistem error saat mencoba menginstal ulang aplikasi yang memiliki penyedia konten yang tidak kompatibel.
Pertimbangan desain data
Berikut adalah beberapa tips untuk mendesain struktur data penyedia:
-
Data tabel harus selalu memiliki kolom "kunci utama" yang dipelihara oleh penyedia
sebagai nilai numerik unik untuk setiap baris. Anda dapat menggunakan nilai ini untuk menautkan baris ke baris yang terkait dalam tabel lain (dengan menggunakannya sebagai "kunci asing"). Meskipun Anda dapat menggunakan nama apa pun untuk kolom ini, menggunakan
BaseColumns._ID
adalah pilihan terbaik, karena menautkan hasil kueri penyedia keListView
mengharuskan salah satu kolom yang diambil memiliki nama_ID
. -
Jika Anda ingin menyediakan gambar bitmap atau potongan data berorientasi file lainnya yang berukuran sangat besar, simpan
data dalam file, lalu sediakan secara tidak langsung, bukan menyimpannya secara langsung dalam
tabel. Jika melakukannya, Anda perlu memberi tahu pengguna penyedia bahwa mereka perlu menggunakan
metode file
ContentResolver
untuk mengakses data. -
Gunakan jenis data biner objek besar (BLOB) untuk menyimpan data yang bervariasi ukurannya atau memiliki
struktur yang bervariasi. Misalnya, Anda dapat menggunakan kolom BLOB untuk menyimpan
buffering protokol atau
struktur JSON.
Anda juga dapat menggunakan BLOB untuk menerapkan tabel yang tidak bergantung skema. Dalam jenis tabel ini, Anda menentukan kolom kunci utama, kolom jenis MIME, dan satu atau beberapa kolom generik sebagai BLOB. Arti data dalam kolom BLOB ditunjukkan oleh nilai dalam kolom jenis MIME. Hal ini memungkinkan Anda menyimpan berbagai jenis baris dalam tabel yang sama. Tabel "data"
ContactsContract.Data
Penyedia Kontak adalah contoh tabel yang tidak bergantung pada skema.
Mendesain URI konten
URI konten adalah URI yang mengidentifikasi data dalam penyedia. URI konten menyertakan
nama simbolis seluruh penyedia (otoritasnya) dan
nama yang mengarah ke tabel atau file (jalur). Bagian ID opsional menunjuk ke baris individual dalam tabel. Setiap metode akses data ContentProvider
memiliki URI konten sebagai argumen. Cara ini memungkinkan Anda menentukan tabel, baris, atau file yang akan diakses.
Untuk informasi tentang URI konten, lihat Dasar-dasar penyedia konten.
Mendesain otoritas
Penyedia biasanya memiliki otoritas tunggal, yang berfungsi sebagai nama internal Android-nya. Untuk menghindari konflik dengan penyedia lain, gunakan kepemilikan domain internet (secara terbalik) sebagai dasar otoritas penyedia Anda. Karena rekomendasi ini juga berlaku untuk nama paket Android, Anda dapat menentukan otoritas penyedia sebagai ekstensi dari nama paket yang berisi penyedia.
Misalnya, jika nama paket Android Anda adalah
com.example.<appname>
, berikan
otoritas com.example.<appname>.provider
kepada penyedia Anda.
Mendesain struktur jalur
Developer biasanya membuat URI konten dari otoritas dengan menambahkan jalur yang mengarah ke
masing-masing tabel. Misalnya, jika Anda memiliki dua tabel, table1 dan table2, Anda dapat menggabungkannya dengan otoritas dari contoh sebelumnya untuk menghasilkan URI konten com.example.<appname>.provider/table1
dan com.example.<appname>.provider/table2
. Jalur tidak dibatasi pada segmen tunggal, dan tidak harus berupa tabel untuk setiap tingkat jalur.
Menangani ID URI konten
Berdasarkan konvensi, penyedia menawarkan akses ke satu baris dalam tabel dengan menerima URI konten
dengan nilai ID untuk baris tersebut di akhir URI. Selain itu, berdasarkan konvensi, penyedia mencocokkan
nilai ID dengan kolom _ID
tabel dan melakukan akses yang diminta terhadap
baris yang cocok.
Standar ini memudahkan pola desain umum untuk aplikasi yang mengakses penyedia. Aplikasi
melakukan kueri terhadap penyedia dan menampilkan Cursor
yang dihasilkan dalam ListView
menggunakan CursorAdapter
.
Definisi CursorAdapter
mengharuskan salah satu kolom di Cursor
berupa _ID
Pengguna kemudian memilih salah satu baris yang ditampilkan dari UI untuk melihat atau mengubah
data. Aplikasi akan mendapatkan baris yang sesuai dari Cursor
yang mendukung
ListView
, mendapatkan nilai _ID
untuk baris ini, menambahkannya ke
URI konten, dan mengirim permintaan akses ke penyedia. Penyedia kemudian dapat melakukan
kueri atau modifikasi terhadap baris yang dipilih pengguna.
Pola URI materi
Untuk membantu Anda memilih tindakan yang akan diambil bagi URI konten yang masuk, API penyedia menyertakan
UriMatcher
class praktis, yang memetakan pola URI konten ke
nilai bilangan bulat. Anda dapat menggunakan nilai bilangan bulat dalam pernyataan switch
yang
memilih tindakan yang diinginkan untuk URI konten atau URI yang cocok dengan pola tertentu.
Pola URI materi mencocokkan dengan URI materi menggunakan karakter pengganti:
-
*
cocok dengan string yang memiliki karakter valid dengan panjang berapa saja. -
#
cocok dengan string karakter numerik dengan panjang berapa pun.
Sebagai contoh mendesain dan coding penanganan URI konten, pertimbangkan penyedia dengan
com.example.app.provider
otoritas yang mengenali URI konten berikut
yang mengarah ke tabel:
-
content://com.example.app.provider/table1
: tabel bernamatable1
. -
content://com.example.app.provider/table2/dataset1
: tabel bernamadataset1
. -
content://com.example.app.provider/table2/dataset2
: tabel bernamadataset2
. -
content://com.example.app.provider/table3
: tabel bernamatable3
.
Penyedia juga mengenali URI konten ini jika memiliki ID baris yang ditambahkan ke dalamnya, seperti content://com.example.app.provider/table3/1
untuk baris yang diidentifikasi oleh
1
dalam table3
.
Pola URI konten berikut mungkin diterapkan:
-
content://com.example.app.provider/*
- Mencocokkan URI konten apa pun di penyedia.
-
content://com.example.app.provider/table2/*
-
Mencocokkan URI konten untuk tabel
dataset1
dandataset2
, tetapi tidak cocok dengan URI konten untuktable1
atautable3
. -
content://com.example.app.provider/table3/#
-
Mencocokkan URI konten
untuk satu baris di
table3
, seperticontent://com.example.app.provider/table3/6
untuk baris yang diidentifikasi oleh6
.
Cuplikan kode berikut menunjukkan cara kerja metode di UriMatcher
.
Kode ini menangani URI untuk seluruh tabel secara berbeda dengan URI untuk
satu baris menggunakan pola URI konten
content://<authority>/<path>
untuk tabel dan
content://<authority>/<path>/<id>
untuk satu baris.
Metode addURI()
memetakan otoritas dan jalur ke nilai bilangan bulat. Metode match()
menampilkan nilai bilangan bulat untuk URI. Pernyataan switch
memilih antara membuat kueri seluruh tabel dan membuat kueri untuk satu data.
Kotlin
private val sUriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used * in the path. */ addURI("com.example.app.provider", "table3", 1) /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ addURI("com.example.app.provider", "table3/#", 2) } ... class ExampleProvider : ContentProvider() { ... // Implements ContentProvider.query() override fun query( uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String? ): Cursor? { var localSortOrder: String = sortOrder ?: "" var localSelection: String = selection ?: "" when (sUriMatcher.match(uri)) { 1 -> { // If the incoming URI was for all of table3 if (localSortOrder.isEmpty()) { localSortOrder = "_ID ASC" } } 2 -> { // If the incoming URI was for a single row /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ localSelection += "_ID ${uri?.lastPathSegment}" } else -> { // If the URI isn't recognized, // do some error handling here } } // Call the code to actually do the query } }
Java
public class ExampleProvider extends ContentProvider { ... // Creates a UriMatcher object. private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { /* * The calls to addURI() go here for all the content URI patterns that the provider * recognizes. For this snippet, only the calls for table 3 are shown. */ /* * Sets the integer value for multiple rows in table 3 to one. No wildcard is used * in the path. */ uriMatcher.addURI("com.example.app.provider", "table3", 1); /* * Sets the code for a single row to 2. In this case, the # wildcard is * used. content://com.example.app.provider/table3/3 matches, but * content://com.example.app.provider/table3 doesn't. */ uriMatcher.addURI("com.example.app.provider", "table3/#", 2); } ... // Implements ContentProvider.query() public Cursor query( Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... /* * Choose the table to query and a sort order based on the code returned for the incoming * URI. Here, too, only the statements for table 3 are shown. */ switch (uriMatcher.match(uri)) { // If the incoming URI was for all of table3 case 1: if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; break; // If the incoming URI was for a single row case 2: /* * Because this URI was for a single row, the _ID value part is * present. Get the last path segment from the URI; this is the _ID value. * Then, append the value to the WHERE clause for the query. */ selection = selection + "_ID = " + uri.getLastPathSegment(); break; default: ... // If the URI isn't recognized, do some error handling here } // Call the code to actually do the query }
Class lainnya, ContentUris
, menyediakan metode praktis untuk menggunakan bagian id
dari URI konten. Class Uri
dan Uri.Builder
menyertakan metode praktis untuk mengurai objek Uri
yang ada dan membuat objek baru.
Mengimplementasikan kelas ContentProvider
Instance ContentProvider
mengelola akses
ke serangkaian data terstruktur dengan menangani permintaan dari aplikasi lain. Semua bentuk
akses pada akhirnya memanggil ContentResolver
, yang kemudian memanggil metode
konkret ContentProvider
untuk mendapatkan akses.
Metode yang diperlukan
Class abstrak ContentProvider
menentukan enam metode abstrak yang
Anda terapkan sebagai bagian dari subclass konkret Anda. Semua metode ini kecuali
onCreate()
dipanggil oleh aplikasi klien
yang mencoba mengakses penyedia konten Anda.
-
query()
-
Mengambil data dari penyedia Anda. Menggunakan argumen untuk memilih tabel yang akan dikueri, baris dan kolom yang akan ditampilkan, dan urutan sortir hasilnya.
Tampilkan data sebagai objek
Cursor
. -
insert()
- Sisipkan baris baru ke penyedia Anda. Menggunakan argumen untuk memilih tabel tujuan dan mendapatkan nilai kolom yang akan digunakan. Menampilkan URI konten untuk baris yang baru disisipkan.
-
update()
- Perbarui baris yang ada di penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan diperbarui dan mendapatkan nilai kolom yang diperbarui. Mengembalikan jumlah baris yang diperbarui.
-
delete()
- Hapus baris dari penyedia Anda. Menggunakan argumen untuk memilih tabel dan baris yang akan dihapus. Mengembalikan jumlah baris yang dihapus.
-
getType()
- Menampilkan jenis MIME yang sesuai dengan URI konten. Metode ini dijelaskan lebih detail di bagian Menerapkan jenis MIME penyedia konten.
-
onCreate()
-
Inisialisasi penyedia Anda. Sistem Android memanggil metode ini segera setelah
membuat penyedia Anda. Penyedia Anda tidak dibuat hingga
objek
ContentResolver
mencoba mengaksesnya.
Metode ini memiliki signature yang sama dengan metode
ContentResolver
yang diberi nama identik.
Penerapan metode ini harus mempertimbangkan hal berikut:
-
Semua metode ini kecuali
onCreate()
dapat dipanggil oleh beberapa thread sekaligus, sehingga harus aman untuk thread. Untuk mempelajari beberapa thread lebih lanjut, lihat Ringkasan proses dan thread. -
Hindari melakukan operasi yang lama dalam
onCreate()
. Tunda inisialisasi tugas hingga benar-benar diperlukan. Bagian tentang mengimplementasikan metode onCreate() membahas hal ini secara lebih mendetail. -
Meskipun harus mengimplementasikan metode ini, kode Anda tidak harus melakukan apa pun kecuali
menampilkan jenis data yang diharapkan. Misalnya, Anda dapat mencegah aplikasi lain
menyisipkan data ke dalam beberapa tabel dengan mengabaikan panggilan ke
insert()
dan menampilkan 0.
Mengimplementasikan metode query()
Metode
ContentProvider.query()
harus menampilkan objek Cursor
atau, jika
gagal, menampilkan Exception
. Jika menggunakan database SQLite sebagai penyimpanan
data, Anda dapat menampilkan Cursor
yang ditampilkan oleh salah satu
metode query()
dari class SQLiteDatabase
.
Jika kueri tidak cocok dengan baris apa pun, tampilkan instance Cursor
yang metode getCount()
-nya menampilkan 0.
Tampilkan null
hanya jika terjadi error internal selama proses kueri.
Jika Anda tidak menggunakan database SQLite sebagai penyimpanan data, gunakan salah satu subclass konkret dari Cursor
. Misalnya, class MatrixCursor
menerapkan kursor dengan setiap baris berupa array instance Object
. Dengan class ini,
gunakan addRow()
untuk menambahkan baris baru.
Sistem Android harus dapat mengomunikasikan Exception
lintas batas proses. Android dapat melakukannya untuk pengecualian berikut yang berguna
dalam menangani error kueri:
-
IllegalArgumentException
. Anda dapat memilih untuk menampilkannya jika penyedia Anda menerima URI konten yang tidak valid. -
NullPointerException
Mengimplementasikan metode insert()
Metode insert()
menambahkan
baris baru ke tabel yang sesuai, menggunakan nilai dalam argumen
ContentValues
. Jika nama kolom tidak ada dalam argumen ContentValues
, Anda
mungkin perlu memberikan nilai default untuknya, baik dalam kode penyedia atau skema
database Anda.
Metode ini menampilkan URI konten untuk baris baru. Untuk membuatnya, tambahkan kunci utama
baris baru, biasanya nilai _ID
, ke URI konten tabel, menggunakan
withAppendedId()
.
Mengimplementasikan metode delete()
Metode delete()
tidak perlu menghapus baris dari penyimpanan data Anda. Jika Anda menggunakan adaptor sinkronisasi
bersama penyedia Anda, sebaiknya tandai baris yang dihapus
dengan flag "hapus", bukan menghapus baris tersebut sepenuhnya. Adaptor sinkronisasi dapat memeriksa baris yang dihapus dan mengeluarkannya dari server sebelum menghapusnya dari penyedia.
Mengimplementasikan metode update()
Metode update()
menggunakan argumen ContentValues
yang sama dengan yang digunakan oleh
insert()
serta
argumen selection
dan selectionArgs
yang sama yang digunakan oleh
delete()
dan
ContentProvider.query()
.
Tindakan ini mungkin memungkinkan Anda menggunakan kembali kode antarmetode tersebut.
Mengimplementasikan metode onCreate()
Sistem Android memanggil onCreate()
saat memulai penyedia. Hanya lakukan tugas inisialisasi yang berjalan cepat dalam metode ini serta tunda pembuatan database dan pemuatan data hingga penyedia benar-benar menerima permintaan untuk data tersebut. Jika Anda melakukan tugas yang panjang dalam onCreate()
, startup penyedia akan diperlambat. Pada akhirnya, hal tersebut memperlambat respons dari penyedia terhadap aplikasi
lain.
Dua cuplikan berikut menunjukkan interaksi antara
ContentProvider.onCreate()
dan
Room.databaseBuilder()
. Cuplikan
pertama menunjukkan implementasi
ContentProvider.onCreate()
tempat
objek database di-build dan menangani objek akses data dibuat:
Kotlin
// Defines the database name private const val DBNAME = "mydb" ... class ExampleProvider : ContentProvider() { // Defines a handle to the Room database private lateinit var appDatabase: AppDatabase // Defines a Data Access Object to perform the database operations private var userDao: UserDao? = null override fun onCreate(): Boolean { // Creates a new database object appDatabase = Room.databaseBuilder(context, AppDatabase::class.java, DBNAME).build() // Gets a Data Access Object to perform the database operations userDao = appDatabase.userDao return true } ... // Implements the provider's insert method override fun insert(uri: Uri, values: ContentValues?): Uri? { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
Java
public class ExampleProvider extends ContentProvider // Defines a handle to the Room database private AppDatabase appDatabase; // Defines a Data Access Object to perform the database operations private UserDao userDao; // Defines the database name private static final String DBNAME = "mydb"; public boolean onCreate() { // Creates a new database object appDatabase = Room.databaseBuilder(getContext(), AppDatabase.class, DBNAME).build(); // Gets a Data Access Object to perform the database operations userDao = appDatabase.getUserDao(); return true; } ... // Implements the provider's insert method public Cursor insert(Uri uri, ContentValues values) { // Insert code here to determine which DAO to use when inserting data, handle error conditions, etc. } }
Mengimplementasikan jenis MIME ContentProvider
Class ContentProvider
memiliki dua metode untuk menampilkan jenis MIME:
-
getType()
- Salah satu metode wajib yang Anda terapkan untuk penyedia apa pun.
-
getStreamTypes()
- Metode yang diharapkan untuk diimplementasikan jika penyedia Anda menawarkan file.
Tipe MIME untuk tabel
Metode getType()
menampilkan
String
dalam format MIME yang menjelaskan jenis data yang ditampilkan oleh argumen
URI konten. Argumen Uri
dapat berupa pola, bukan URI tertentu.
Dalam hal ini, tampilkan jenis data terkait URI konten yang cocok dengan
polanya.
Untuk jenis data umum seperti teks, HTML, atau JPEG,
getType()
menampilkan jenis MIME
standar untuk data tersebut. Daftar lengkap jenis standar ini tersedia di situs IANA MIME Media Types.
Untuk URI konten yang mengarah ke baris atau baris data tabel,
getType()
akan menampilkan
jenis MIME dalam format MIME khusus vendor Android:
-
Bagian jenis:
vnd
-
Bagian subjenis:
-
Jika pola URI adalah untuk satu baris:
android.cursor.item/
-
Jika pola URI adalah untuk lebih dari satu baris:
android.cursor.dir/
-
Jika pola URI adalah untuk satu baris:
-
Bagian khusus penyedia:
vnd.<name>
.<type>
Anda menyediakan
<name>
dan<type>
. Nilai<name>
bersifat unik secara global, dan nilai<type>
bersifat unik untuk pola URI yang sesuai. Pilihan yang tepat untuk<name>
adalah nama perusahaan Anda atau beberapa bagian dari nama paket Android aplikasi Anda. Pilihan tepat untuk<type>
adalah string yang mengidentifikasi tabel yang terkait dengan URI.
Misalnya, jika otoritas penyedia adalah
com.example.app.provider
, dan otoritas ini mengekspos tabel bernama
table1
, jenis MIME untuk beberapa baris dalam table1
adalah:
vnd.android.cursor.dir/vnd.com.example.provider.table1
Untuk satu baris table1
, jenis MIME adalah:
vnd.android.cursor.item/vnd.com.example.provider.table1
Tipe MIME untuk file
Jika penyedia Anda menawarkan file, implementasikan
getStreamTypes()
.
Metode ini menampilkan array String
jenis MIME untuk file yang dapat ditampilkan oleh penyedia Anda untuk URI konten tertentu. Filter jenis MIME yang Anda tawarkan dengan argumen filter jenis MIME, sehingga Anda hanya menampilkan jenis MIME yang ingin ditangani klien.
Misalnya, pertimbangkan penyedia yang menawarkan gambar foto sebagai file dalam format JPG,
PNG, dan GIF.
Jika aplikasi memanggil ContentResolver.getStreamTypes()
dengan string filter image/*
, untuk sesuatu yang
merupakan "gambar",
metode ContentProvider.getStreamTypes()
akan menampilkan array:
{ "image/jpeg", "image/png", "image/gif"}
Jika hanya tertarik dengan file JPG, aplikasi dapat memanggil
ContentResolver.getStreamTypes()
dengan string filter *\/jpeg
, dan
getStreamTypes()
menampilkan:
{"image/jpeg"}
Jika penyedia Anda tidak menawarkan jenis MIME yang diminta dalam string filter,
getStreamTypes()
akan menampilkan null
.
Mengimplementasikan kelas kontrak
Class kontrak adalah class public final
yang berisi definisi konstanta untuk
URI, nama kolom, jenis MIME, dan metadata lainnya yang berkaitan dengan penyedia. Class ini
menetapkan kontrak antara penyedia dan aplikasi lain dengan memastikan bahwa penyedia
dapat diakses dengan benar meskipun ada perubahan pada nilai URI sebenarnya, nama kolom,
dan sebagainya.
Class kontrak juga membantu developer karena class ini biasanya memiliki nama simbolik untuk konstantanya, sehingga memperkecil kemungkinan developer menggunakan nilai yang salah untuk nama kolom atau URI. Karena berupa class, class dapat berisi dokumentasi Javadoc. Lingkungan pengembangan terintegrasi seperti Android Studio dapat melengkapi otomatis nama konstanta dari class kontrak dan menampilkan Javadoc untuk konstanta tersebut.
Developer tidak dapat mengakses file class class kontrak dari aplikasi Anda, tetapi dapat mengompilasinya secara statis ke dalam aplikasi dari file JAR yang Anda berikan.
Class ContactsContract
dan class bertingkatnya adalah contoh
class kontrak.
Mengimplementasikan izin penyedia konten
Izin dan akses untuk semua aspek sistem Android dijelaskan secara mendetail dalam Tips keamanan. Ringkasan penyimpanan data dan file juga menjelaskan keamanan dan izin yang berlaku untuk berbagai jenis penyimpanan. Secara singkat, poin-poin pentingnya adalah sebagai berikut:
- Secara default, file data yang disimpan di penyimpanan internal perangkat bersifat pribadi bagi aplikasi dan penyedia Anda.
-
Database
SQLiteDatabase
yang Anda buat bersifat pribadi bagi aplikasi dan penyedia Anda. - Secara default, file data yang Anda simpan ke penyimpanan eksternal bersifat publik dan dapat dibaca secara global. Anda tidak dapat menggunakan penyedia konten untuk membatasi akses ke file dalam penyimpanan eksternal, karena aplikasi lain dapat menggunakan panggilan API lain untuk membaca dan menulisnya.
- Panggilan metode untuk membuka atau membuat file atau database SQLite pada penyimpanan internal perangkat Anda berpotensi memberikan akses baca dan tulis ke semua aplikasi lain. Jika Anda menggunakan file atau database internal sebagai repositori penyedia dan Anda memberinya akses "world-readable" (bisa dibaca secara global) atau "world-writeable" (dapat ditulis secara global), izin yang Anda tetapkan untuk penyedia Anda dalam manifesnya tidak akan melindungi data Anda. Akses default untuk file dan database di penyimpanan internal adalah "pribadi"; jangan mengubah ini untuk repositori penyedia Anda.
Jika Anda ingin menggunakan izin penyedia konten untuk mengontrol akses ke data Anda, maka simpan data Anda dalam file internal, database SQLite, atau cloud, misalnya di server jarak jauh, dan pertahankan file dan database hanya untuk aplikasi Anda.
Menerapkan izin
Secara default, semua aplikasi dapat membaca dari atau menulis ke penyedia Anda, meskipun data yang mendasarinya bersifat
pribadi, karena secara default penyedia Anda tidak menetapkan izin. Untuk mengubahnya,
tetapkan izin untuk penyedia dalam file manifes, menggunakan atribut atau elemen
turunan dari elemen
<provider>
. Anda dapat menetapkan izin yang berlaku untuk seluruh penyedia,
pada tabel tertentu, catatan tertentu, atau ketiganya.
Anda menetapkan izin untuk penyedia dengan satu atau beberapa
elemen
<permission>
dalam file manifes. Agar izin
unik bagi penyedia Anda, gunakan cakupan bergaya Java untuk
atribut
android:name
. Misalnya, beri nama izin baca com.example.app.provider.permission.READ_PROVIDER
.
Daftar berikut ini menjelaskan cakupan izin penyedia, dimulai dengan izin yang berlaku untuk seluruh penyedia, lalu menjadi lebih mendetail. Izin yang lebih terperinci akan lebih diutamakan daripada izin dengan cakupan yang lebih besar.
- Izin baca-tulis tunggal tingkat penyedia
-
Satu izin yang mengontrol akses baca dan tulis ke seluruh penyedia, yang ditetapkan
dengan atribut
android:permission
elemen<provider>
. - Pisahkan izin baca dan tulis tingkat penyedia
-
Izin baca dan izin tulis untuk seluruh penyedia. Anda menentukannya
dengan atribut
android:readPermission
danandroid:writePermission
dari elemen<provider>
. Izin ini akan lebih diutamakan daripada izin yang diwajibkan olehandroid:permission
. - Izin tingkat path
-
Izin baca, tulis, atau baca/tulis untuk URI konten di penyedia Anda. Anda menentukan setiap URI yang ingin dikontrol dengan elemen turunan
<path-permission>
dari elemen<provider>
. Untuk setiap URI konten yang ditentukan, Anda dapat menentukan satu izin baca/tulis, satu izin baca, satu izin tulis, atau ketiganya. Izin baca dan tulis lebih diutamakan daripada izin baca/tulis. Selain itu, izin tingkat jalur akan lebih diprioritaskan daripada izin tingkat penyedia. - Izin sementara
-
Tingkat izin yang memberikan akses sementara ke aplikasi, meskipun aplikasi
tidak memiliki izin yang biasanya diminta. Fitur akses sementara mengurangi jumlah izin yang harus diminta aplikasi dalam manifesnya. Jika Anda mengaktifkan izin sementara, satu-satunya aplikasi yang memerlukan
izin permanen untuk penyedia Anda adalah aplikasi yang terus mengakses semua
data Anda.
Misalnya, pertimbangkan izin yang diperlukan jika Anda mengimplementasikan penyedia dan aplikasi email dan ingin mengizinkan aplikasi penampil gambar dari luar menampilkan lampiran foto dari penyedia Anda. Untuk memberikan akses yang diperlukan kepada penampil gambar tanpa memerlukan izin, Anda dapat menyiapkan izin sementara untuk URI konten bagi foto.
Desain aplikasi email Anda agar saat pengguna ingin menampilkan foto, aplikasi tersebut akan mengirimkan intent yang berisi URI konten foto dan tanda izin ke penampil gambar. Selanjutnya, penampil gambar dapat mengkueri penyedia email untuk mengambil foto, meskipun penampil tersebut tidak memiliki izin baca normal untuk penyedia Anda.
Untuk mengaktifkan izin sementara, tetapkan atribut
android:grantUriPermissions
dari elemen<provider>
atau tambahkan satu atau beberapa elemen turunan<grant-uri-permission>
ke elemen<provider>
Anda. PanggilContext.revokeUriPermission()
setiap kali Anda menghapus dukungan untuk URI konten yang terkait dengan izin sementara dari penyedia.Nilai atribut menentukan seberapa banyak penyedia Anda yang dijadikan dapat diakses. Jika atribut ditetapkan ke
"true"
, sistem akan memberikan izin sementara kepada seluruh penyedia, dengan mengabaikan izin lain yang diperlukan oleh izin tingkat penyedia atau tingkat jalur.Jika tanda ini disetel ke
"false"
, tambahkan elemen turunan<grant-uri-permission>
ke elemen<provider>
Anda. Setiap elemen turunan menetapkan URI konten atau URI yang telah diberi akses sementara.Untuk mendelegasikan akses sementara ke aplikasi, intent harus berisi flag
FLAG_GRANT_READ_URI_PERMISSION
, flagFLAG_GRANT_WRITE_URI_PERMISSION
, atau keduanya. Keduanya ditetapkan dengan metodesetFlags()
.Jika atribut
android:grantUriPermissions
tidak ada, atribut tersebut diasumsikan sebagai"false"
.
Elemen <provider>
Seperti komponen Activity
dan Service
,
subclass ContentProvider
ditentukan dalam file manifes untuk aplikasinya, menggunakan
elemen
<provider>
. Sistem Android mendapatkan informasi berikut dari
elemen:
-
Otoritas
(
android:authorities
) - Nama simbolis yang mengidentifikasi seluruh penyedia dalam sistem. Atribut ini dijelaskan secara lebih detail di bagian Mendesain URI konten.
-
Nama class penyedia
(
android:name
) -
Class yang mengimplementasikan
ContentProvider
. Class ini dijelaskan secara lebih detail di bagian Mengimplementasikan class ContentProvider. - Izin
-
Atribut yang menentukan izin yang harus dimiliki aplikasi lain untuk mengakses
data penyedia:
-
android:grantUriPermissions
: tanda izin sementara -
android:permission
: izin baca/tulis tunggal untuk seluruh penyedia -
android:readPermission
: izin baca seluruh penyedia -
android:writePermission
: izin tulis tingkat penyedia
Izin dan atribut yang sesuai dijelaskan secara lebih mendetail di bagian Menerapkan izin penyedia konten.
-
- Atribut-atribut startup dan kontrol
-
Atribut ini menentukan cara dan waktu sistem Android memulai penyedia, karakteristik proses penyedia, dan setelan runtime lainnya:
-
android:enabled
: flag yang memungkinkan sistem memulai penyedia -
android:exported
: flag yang mengizinkan aplikasi lain menggunakan penyedia ini -
android:initOrder
: urutan yang digunakan untuk memulai penyedia ini, relatif terhadap penyedia lain dalam proses yang sama -
android:multiProcess
: flag yang memungkinkan sistem memulai penyedia dalam proses yang sama seperti proses klien pemanggil -
android:process
: nama proses yang digunakan oleh penyedia -
android:syncable
: flag yang menunjukkan bahwa data penyedia akan disinkronkan dengan data di server
Atribut ini didokumentasikan sepenuhnya dalam panduan untuk elemen
<provider>
. -
- Atribut-atribut informatif
-
Ikon dan label opsional untuk penyedia:
-
android:icon
: resource drawable yang berisi ikon untuk penyedia. Ikon ini muncul di samping label penyedia dalam daftar aplikasi di Setelan > Aplikasi > Semua. -
android:label
: label informasi yang mendeskripsikan penyedia, datanya, atau keduanya. Label ini muncul dalam daftar aplikasi di Setelan > Aplikasi > Semua.
Atribut ini didokumentasikan sepenuhnya dalam panduan untuk elemen
<provider>
. -
Intent dan akses data
Aplikasi dapat mengakses penyedia konten secara tidak langsung dengan Intent
.
Aplikasi tidak memanggil metode ContentResolver
atau
ContentProvider
. Sebagai gantinya, aplikasi akan mengirim intent yang memulai aktivitas,
yang sering kali merupakan bagian dari aplikasi milik penyedia itu sendiri. Aktivitas tujuan bertugas mengambil dan menampilkan data dalam UI-nya.
Bergantung pada tindakan dalam intent, aktivitas tujuan juga dapat meminta pengguna untuk melakukan modifikasi pada data penyedia. Intent juga mungkin berisi data "tambahan" yang ditampilkan aktivitas tujuan di UI. Kemudian, pengguna memiliki opsi untuk mengubah data ini sebelum menggunakannya untuk mengubah data dalam penyedia.
Anda dapat menggunakan akses intent untuk membantu integritas data. Penyedia Anda mungkin bergantung pada data yang disisipkan, diperbarui, dan dihapus sesuai dengan logika bisnis yang didefinisikan dengan ketat. Jika demikian, membiarkan aplikasi lain mengubah data Anda secara langsung dapat menyebabkan data yang tidak valid.
Jika Anda ingin developer menggunakan akses intent, pastikan untuk mendokumentasikannya secara saksama. Jelaskan alasan akses intent yang menggunakan UI aplikasi Anda lebih baik daripada mencoba mengubah data dengan kodenya.
Menangani intent masuk yang ingin mengubah data penyedia Anda tidak berbeda dengan menangani intent lainnya. Anda dapat mempelajari penggunaan intent lebih lanjut dengan membaca Intent dan Filter Intent.
Untuk informasi terkait lainnya, lihat Ringkasan penyedia kalender.