Content Provider: архітектура компонента та принцип роботи

Автор: IT Sectr Опубліковано: 2026-06-17 Час читання: 8 хв

Content Provider — це компонент Android, який надає єдиний інтерфейс для доступу до даних між застосунками. Він абстрагує фізичне зберігання (SQLite, файли, мережеві джерела) та забезпечує безпечний обмін інформацією через ContentResolver. Згідно з Android Developer Guide, 2026, Content Provider є одним із чотирьох основних компонентів Android-застосунку разом із Activity, Service та BroadcastReceiver. Його завдання — зробити дані доступними іншим застосункам із контролем дозволів на читання та запис.

Головне

  • Content Provider — стандартний компонент Android для міжзастосункового обміну даними через інтерфейс ContentResolver.
  • URI (content://) ідентифікує таблицю або запис і використовується для всіх CRUD-операцій із провайдером.
  • UriMatcher — службовий клас, який розбирає вхідні URI та визначає, яка таблиця або рядок запитані.
  • Дозволи на читання та запис задаються в маніфесті й запитуються в користувача під час встановлення.
  • CursorLoader або Room із Content Provider забезпечують асинхронне завантаження даних із провайдера без блокування UI-потоку.

Що таке Content Provider?

Content Provider — це компонент Android, який керує доступом до централізованого сховища даних і надає їх іншим застосункам через єдиний контрактний інтерфейс. Він приховує деталі реалізації сховища: дані можуть зберігатися в SQLite, файловій системі, хмарі або бути результатом мережевого запиту.

Android включає вбудовані Content Provider для системних даних — ContactsContract, MediaStore, CalendarContract, CallLog. Сторонні застосунки також можуть створювати власні провайдери для безпечного обміну даними. Кожен провайдер реєструється в AndroidManifest.xml із authority — унікальним рядком, який утворює першу частину URI.

Як працює Content Provider

Content Provider працює за моделлю клієнт-сервер. Провайдер виступає сервером, який реалізує шість обов'язкових методів: query, insert, update, delete, getType та onCreate. Клієнт (інший застосунок) звертається до провайдера через ContentResolver, який транслює виклики у відповідні методи провайдера через IPC-механізм Android.

URI та authority

Кожен 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 — об'єкт, який містить результат і дозволяє клієнту ітерувати записами.

Основні методи Content Provider

Клас ContentProvider вимагає реалізації шести абстрактних методів. Кожен метод приймає URI та повертає результат, що відповідає типу операції. Система викликає ці методи з будь-якого процесу, тому вони мають бути потокобезпечними й не блокувати виконання надовго.

Метод query

Метод query приймає URI, масив стовпців projection, рядок selection з аргументами та порядок сортування. Він повертає Cursor із даними. У реалізації потрібно розібрати URI за допомогою UriMatcher і виконати відповідний SQL-запит до бази даних.

Методи insert, update, delete

Ці методи змінюють дані в сховищі. insert отримує ContentValues — пари ключ-значення — і повертає URI нового запису. update та delete приймають selection для фільтрації записів і повертають кількість порушених рядків. Після зміни даних провайдер зобов'язаний повідомити про це через ContentResolver.notifyChange.

МетодПризначенняПовернення
queryОтримання даних за URICursor або null
insertДодавання нового записуURI нового запису
updateОновлення існуючих записівint (кількість рядків)
deleteВидалення записівint (кількість рядків)
getTypeMIME-тип для URIString
onCreateІніціалізація провайдераboolean

ContentResolver та URI в Android

ContentResolver — це єдина точка доступу для роботи з усіма Content Provider у системі. Клієнтський застосунок ніколи не викликає методи провайдера безпосередньо — тільки через ContentResolver, який Android отримує з контексту. CRUD-операції в ContentResolver мають ті самі назви, що й у провайдері, але приймають URI замість прямих посилань.

UriMatcher

Для розбору вхідних URI всередині провайдера використовується UriMatcher. Він дозволяє зіставити URI з числовим кодом — наприклад, URI content://authority/items дає код 1, а content://authority/items/# — код 2. Це позбавляє від ручного парсингу рядка URI в кожному методі.

kotlin
// Використання 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 на Kotlin

Створення власного Content Provider починається з успадкування від класу ContentProvider. Провайдер працює з SQLite базою через SQLiteOpenHelper та використовує UriMatcher для визначення типу запиту. Розглянемо мінімальну реалізацію для керування списком нотаток.

Реєстрація в маніфесті

Провайдер реєструється в AndroidManifest.xml усередині тегу application. Атрибут authorities задає унікальний ідентифікатор, а exported визначає, чи можуть інші застосунки звертатися до провайдера. Без exported=true провайдер доступний лише всередині вашого застосунку.

kotlin
// Приклад 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.

Дозволи на рівні URI

Android підтримує тимчасові дозволи через прапорці FLAG_GRANT_READ_URI_PERMISSION та FLAG_GRANT_WRITE_URI_PERMISSION. Це корисно, коли застосунок передає URI файлу іншому застосунку через Intent — отримувач отримує доступ лише до конкретного URI на обмежений час. Система відкликає тимчасовий дозвіл після завершення роботи застосунку-отримувача.

Для системних провайдерів Android вимагає вказання конкретних дозволів у маніфесті застосунку. Наприклад, для доступу до контактів потрібен READ_CONTACTS, для календаря — READ_CALENDAR. Починаючи з Android 6, ці дозволи запитуються під час виконання, а не при встановленні.

Часті запитання

Що таке Content Provider в Android?

Content Provider — це компонент Android, який надає стандартний інтерфейс для обміну даними між застосунками через ContentResolver. Він абстрагує спосіб зберігання (SQLite, файли, мережа) та забезпечує безпечний доступ до даних із контролем дозволів на читання та запис.

Що таке authority в Content Provider?

Authority — це унікальний рядок-ідентифікатор провайдера, який вказується в AndroidManifest.xml. Вона утворює першу частину URI content://authority/path і використовується системою для маршрутизації викликів ContentResolver до потрібного провайдера. Authority має бути унікальною серед усіх застосунків на пристрої.

Як працює UriMatcher в Content Provider?

UriMatcher зіставляє URI з числовими кодами. Ви додаєте шаблони через addURI, а потім викликаєте match для отримання коду за вхідним URI. Це дозволяє в методах query, insert, update, delete визначити, яка таблиця або запис запитані, та виконати відповідну операцію з базою даних.

Чи потрібно закривати Cursor після роботи з ContentResolver?

Так, Cursor обов'язково потрібно закривати після використання. Якщо Cursor не закрити, відбудеться витік пам'яті, оскільки він тримає Binder-посилання на дані провайдера. У Kotlin використовуйте функцію use для автоматичного закриття, а в Java — try-with-resources або виклик cursor.close() у finally.

Чим відрізняється Content Provider від SQLiteDatabase?

Content Provider — це компонент для міжзастосункового доступу до даних, тоді як SQLiteDatabase — внутрішнє сховище одного застосунку. Content Provider надає URI-інтерфейс та контроль дозволів, а SQLiteDatabase працює безпосередньо з базою без механізмів безпеки на рівні ОС.

Підсумки

  • Content Provider — це стандартний компонент Android для безпечного обміну даними між застосунками через єдиний інтерфейс ContentResolver.
  • URI схеми content://authority/path/id ідентифікує провайдер, таблицю та конкретний запис — кожен сегмент має строге призначення.
  • UriMatcher спрощує розбір вхідних URI всередині провайдера, позбавляючи від ручного парсингу рядків у кожному CRUD-методі.
  • Провайдер реєструється в AndroidManifest.xml з атрибутами authorities та exported — це системний механізм, без якого провайдер не буде працювати.
  • ContentValues передаються в insert та update як набір пар ключ-значення, а Cursor повертається з query для ітерації за результатами.
  • Дозволи readPermission та writePermission у маніфесті контролюють доступ до даних, а FLAG_GRANT_URI_PERMISSION надає тимчасовий доступ до конкретного URI.
  • Для асинхронного завантаження даних із Content Provider використовуйте CursorLoader, Room із ContentProvider або LoaderManager — це запобігає блокуванню UI-потоку.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також