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