Content Provider — це компонент Android, який надає єдиний інтерфейс для доступу до даних між застосунками. Він абстрагує фізичне зберігання (SQLite, файли, мережеві джерела) та забезпечує безпечний обмін інформацією через ContentResolver. Згідно з Android Developer Guide, 2026, Content Provider є одним із чотирьох основних компонентів Android-застосунку разом із Activity, Service та BroadcastReceiver. Його завдання — зробити дані доступними іншим застосункам із контролем дозволів на читання та запис.
Головне
Content Provider — це компонент Android, який керує доступом до централізованого сховища даних і надає їх іншим застосункам через єдиний контрактний інтерфейс. Він приховує деталі реалізації сховища: дані можуть зберігатися в SQLite, файловій системі, хмарі або бути результатом мережевого запиту.
Android включає вбудовані Content Provider для системних даних — ContactsContract, MediaStore, CalendarContract, CallLog. Сторонні застосунки також можуть створювати власні провайдери для безпечного обміну даними. Кожен провайдер реєструється в AndroidManifest.xml із authority — унікальним рядком, який утворює першу частину URI.
Content Provider працює за моделлю клієнт-сервер. Провайдер виступає сервером, який реалізує шість обов'язкових методів: query, insert, update, delete, getType та onCreate. Клієнт (інший застосунок) звертається до провайдера через ContentResolver, який транслює виклики у відповідні методи провайдера через IPC-механізм Android.
Кожен Content Provider ідентифікується URI схеми content://. Наприклад, content://com.example.app.provider/items. Перша частина authority (com.example.app.provider) прив'язується до класу провайдера в маніфесті. Шлях /items вказує на таблицю, а /items/5 — на конкретний запис із ID=5.
Коли застосунок викликає ContentResolver.query(URI), Android перевіряє дозволи пакунка, що викликає, знаходить провайдера за authority та запускає його процес, якщо він ще не запущений. Провайдер виконує запит і повертає Cursor — об'єкт, який містить результат і дозволяє клієнту ітерувати записами.
Клас ContentProvider вимагає реалізації шести абстрактних методів. Кожен метод приймає URI та повертає результат, що відповідає типу операції. Система викликає ці методи з будь-якого процесу, тому вони мають бути потокобезпечними й не блокувати виконання надовго.
Метод query приймає URI, масив стовпців projection, рядок selection з аргументами та порядок сортування. Він повертає Cursor із даними. У реалізації потрібно розібрати URI за допомогою UriMatcher і виконати відповідний SQL-запит до бази даних.
Ці методи змінюють дані в сховищі. insert отримує ContentValues — пари ключ-значення — і повертає URI нового запису. update та delete приймають selection для фільтрації записів і повертають кількість порушених рядків. Після зміни даних провайдер зобов'язаний повідомити про це через ContentResolver.notifyChange.
| Метод | Призначення | Повернення |
|---|---|---|
| query | Отримання даних за URI | Cursor або null |
| insert | Додавання нового запису | URI нового запису |
| update | Оновлення існуючих записів | int (кількість рядків) |
| delete | Видалення записів | int (кількість рядків) |
| getType | MIME-тип для URI | String |
| onCreate | Ініціалізація провайдера | boolean |
ContentResolver — це єдина точка доступу для роботи з усіма Content Provider у системі. Клієнтський застосунок ніколи не викликає методи провайдера безпосередньо — тільки через ContentResolver, який Android отримує з контексту. CRUD-операції в ContentResolver мають ті самі назви, що й у провайдері, але приймають URI замість прямих посилань.
Для розбору вхідних URI всередині провайдера використовується UriMatcher. Він дозволяє зіставити URI з числовим кодом — наприклад, URI content://authority/items дає код 1, а content://authority/items/# — код 2. Це позбавляє від ручного парсингу рядка URI в кожному методі.
// Використання ContentResolver для доступу до контактів
val uri = ContactsContract.Contacts.CONTENT_URI
val cursor = contentResolver.query(
uri,
arrayOf(ContactsContract.Contacts.DISPLAY_NAME),
null, null, null
)
cursor?.use {
while (it.moveToNext()) {
val name = it.getString(it.getColumnIndexOrThrow(
ContactsContract.Contacts.DISPLAY_NAME
))
Log.d("Контакти", "Ім'я: $name")
}
}
Cursor після використання потрібно обов'язково закривати — у прикладі вище це робить use (функція-розширення Kotlin). Якщо Cursor не закрити, відбудеться витік пам'яті, оскільки він тримає посилання на дані в Binder-пулі. Для UI-сценаріїв використовуйте CursorLoader або Room із LiveData/Flow.
Створення власного Content Provider починається з успадкування від класу ContentProvider. Провайдер працює з SQLite базою через SQLiteOpenHelper та використовує UriMatcher для визначення типу запиту. Розглянемо мінімальну реалізацію для керування списком нотаток.
Провайдер реєструється в AndroidManifest.xml усередині тегу application. Атрибут authorities задає унікальний ідентифікатор, а exported визначає, чи можуть інші застосунки звертатися до провайдера. Без exported=true провайдер доступний лише всередині вашого застосунку.
// Приклад Content Provider для нотаток
class NotesProvider : ContentProvider() {
companion object {
const val AUTHORITY = "com.example.app.notes"
const val NOTES_PATH = "notes"
const val NOTES_URI = "content://$AUTHORITY/$NOTES_PATH"
const val NOTES_ID = "content://$AUTHORITY/$NOTES_PATH/#"
private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
addURI(AUTHORITY, NOTES_PATH, 1)
addURI(AUTHORITY, "$NOTES_PATH/#", 2)
}
}
override fun query(uri: Uri, projection: Array<String>?,
selection: String?, args: Array<String>?, sort: String?): Cursor? {
return when (uriMatcher.match(uri)) {
1 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, selection, args, null, null, sort)
2 -> dbHelper.readableDatabase.query(TABLE_NOTES,
projection, "_id=?", arrayOf(uri.lastPathSegment), null, null, null)
else -> throw IllegalArgumentException("Unknown URI: $uri")
}
}
override fun insert(uri: Uri, values: ContentValues?): Uri? {
val id = dbHelper.writableDatabase.insert(TABLE_NOTES, null, values)
context?.contentResolver?.notifyChange(uri, null)
return ContentUris.withAppendedId(uri, id)
}
override fun delete(uri: Uri, selection: String?, args: Array<String>?): Int {
val count = dbHelper.writableDatabase.delete(TABLE_NOTES, selection, args)
context?.contentResolver?.notifyChange(uri, null)
return count
}
// getType, update, onCreate опущені для стислості
}
Після створення класу провайдера його потрібно зареєструвати в маніфесті з атрибутами android:authorities та android:exported (true, якщо провайдер публічний). Система створює екземпляр провайдера при першому зверненні до нього — це відбувається в UI-потоці, тому onCreate має виконуватися швидко.
Content Provider дозволяє керувати доступом до даних на двох рівнях: дозволи на читання та дозволи на запис. Вони задаються в маніфесті атрибутами android:readPermission та android:writePermission. Якщо клієнтський застосунок не має відповідного дозволу, система відхиляє виклик із SecurityException.
Android підтримує тимчасові дозволи через прапорці FLAG_GRANT_READ_URI_PERMISSION та FLAG_GRANT_WRITE_URI_PERMISSION. Це корисно, коли застосунок передає URI файлу іншому застосунку через Intent — отримувач отримує доступ лише до конкретного URI на обмежений час. Система відкликає тимчасовий дозвіл після завершення роботи застосунку-отримувача.
Для системних провайдерів Android вимагає вказання конкретних дозволів у маніфесті застосунку. Наприклад, для доступу до контактів потрібен READ_CONTACTS, для календаря — READ_CALENDAR. Починаючи з Android 6, ці дозволи запитуються під час виконання, а не при встановленні.
Часті запитання
Content Provider — це компонент Android, який надає стандартний інтерфейс для обміну даними між застосунками через ContentResolver. Він абстрагує спосіб зберігання (SQLite, файли, мережа) та забезпечує безпечний доступ до даних із контролем дозволів на читання та запис.
Authority — це унікальний рядок-ідентифікатор провайдера, який вказується в AndroidManifest.xml. Вона утворює першу частину URI content://authority/path і використовується системою для маршрутизації викликів ContentResolver до потрібного провайдера. Authority має бути унікальною серед усіх застосунків на пристрої.
UriMatcher зіставляє URI з числовими кодами. Ви додаєте шаблони через addURI, а потім викликаєте match для отримання коду за вхідним URI. Це дозволяє в методах query, insert, update, delete визначити, яка таблиця або запис запитані, та виконати відповідну операцію з базою даних.
Так, Cursor обов'язково потрібно закривати після використання. Якщо Cursor не закрити, відбудеться витік пам'яті, оскільки він тримає Binder-посилання на дані провайдера. У Kotlin використовуйте функцію use для автоматичного закриття, а в Java — try-with-resources або виклик cursor.close() у finally.
Content Provider — це компонент для міжзастосункового доступу до даних, тоді як SQLiteDatabase — внутрішнє сховище одного застосунку. Content Provider надає URI-інтерфейс та контроль дозволів, а SQLiteDatabase працює безпосередньо з базою без механізмів безпеки на рівні ОС.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також