Как работают 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 — кратко

  1. Контракт (константы 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"
  }
}
  1. Реализация провайдера (минимум методов):
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
}
  1. Регистрация в 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"/>
  1. Клиент использует 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 оправдан, как его реализовать и как не допустить типичных ошибок при публикации данных наружу.