Как работают Content Providers в Android и как с ними работать
Content Provider — это компонент Android, который предоставляет стандартизованный API (через URI и CRUD‑операции) для доступа к данным вашего приложения из других приложений и самой системы. Если кратко: используйте Content Provider, когда нужно безопасно публиковать данные наружу, интегрироваться с системными pickers или организовать общий API между вашими приложениями.
Оглавление {{TOC_AUTOMATIC}}
Что такое Content Provider и когда он нужен
Content Provider инкапсулирует источник данных (SQLite/Room, файлы, память или сеть) и выдаёт доступ через content:// URI и методы query/insert/update/delete. Он удобен, когда:
- данные должны быть доступны другим приложениям (виджеты, экспорты, плагины);
- нужна интеграция с системными компонентами (MediaStore, Contacts, DocumentPicker);
- требуется реактивное уведомление об изменениях через ContentObserver;
- вы хотите единый интерфейс для множества клиентов.
Когда провайдер не нужен: если данные используются только внутри одного приложения и достаточно слоёв репозитория/DAO — Content Provider только усложнит архитектуру.
Как создать и зарегистрировать Content Provider — кратко
- Контракт (константы URI, имена колонок). Пример:
object NotesContract {
const val AUTHORITY = "com.example.notes.provider"
val BASE_URI = Uri.parse("content://$AUTHORITY")
object Notes {
val CONTENT_URI = BASE_URI.buildUpon().appendPath("notes").build()
const val _ID = "_id"; const val TITLE = "title"; const val BODY = "body"
}
}
- Реализация провайдера (минимум методов):
class NotesProvider: ContentProvider() {
private lateinit var dbHelper: NotesDbHelper
override fun onCreate() = run { dbHelper = NotesDbHelper(context!!); true }
override fun query(uri: Uri, proj: Array<String>?, sel: String?, selArgs: Array<String>?, sort: String?) =
dbHelper.readableDatabase.query("notes", proj, sel, selArgs, null, null, sort).also {
it.setNotificationUri(context!!.contentResolver, uri)
}
override fun insert(uri: Uri, values: ContentValues?) = /* вставка + notifyChange */
override fun update(...) = /* обновление + notifyChange */
override fun delete(...) = /* удаление + notifyChange */
override fun getType(uri: Uri) = null
}
- Регистрация в AndroidManifest.xml:
<provider
android:name=".data.NotesProvider"
android:authorities="com.example.notes.provider"
android:exported="true"
android:readPermission="com.example.notes.permission.READ"
android:writePermission="com.example.notes.permission.WRITE"/>
- Клиент использует ContentResolver: query/insert/update/delete по CONTENT_URI.
Безопасность и лучшие практики
- Не экспортируйте провайдер без необходимости: android:exported="false" если данные только внутри приложения.
- Разделяйте права чтения и записи (readPermission / writePermission).
- Используйте grantUriPermission + FLAG_GRANT_* для временного шаринга (FileProvider — для файлов).
- Валидация входных параметров: проверяйте URI, selection/selectionArgs, не подставляйте строки в SQL без подготовки.
- Отдавайте минимальный набор данных, не раскрывайте внутренние детали (названия таблиц, SQL‑схемы).
- Всегда вызывайте notifyChange и устанавливайте setNotificationUri у курсора для работы ContentObserver/Loader/LiveData.
Частая ошибка — оставить android:exported="true" без адекватных разрешений и проверок: это позволяет любому приложению читать/писать ваши данные.
Если нужно лишь безопасно поделиться файлами — используйте FileProvider из AndroidX: он упрощает конфигурирование путей и временную выдачу прав через Intent.
Частые ошибки
- Неправильный authority (должен быть уникален, обычно совпадает с package name).
- Забыл вызвать cursor.setNotificationUri → UI не обновляется при изменениях.
- Проигнорировал getType() — системные клиенты могут ожидать корректный MIME.
- Жёстко связал внешнее API с реализацией на SQLite — ломает клиентов при миграциях.
FAQ
- Нужно ли Content Provider для Room? Нет, Room покрывает локальные запросы. Provider нужен только для обмена данными с другими приложениями или интеграции с системой.
- Как правильно шарить один файл? Используйте FileProvider и Intent с флагом FLAG_GRANT_READ_URI_PERMISSION.
- Как отладить конфликт authority? Проверьте manifest всех установленных приложений и используйте уникальное имя (обычно package + ".provider").
Этот набор правил и примеров позволит быстро понять, когда Content Provider оправдан, как его реализовать и как не допустить типичных ошибок при публикации данных наружу.