Как работать с content://, ContentProvider и файлами в Android

В двух словах: content:// — схема URI для доступа к данным, которыми управляет ContentProvider; для чтения/записи используйте ContentResolver, для простого шаринга файлов — FileProvider, а для выбора/доступа к внешним документам — Storage Access Framework (SAF). Ниже — практические примеры и рекомендации, чтобы сразу применить в проекте.

Что такое ContentProvider и когда он нужен

ContentProvider инкапсулирует доступ к данным (табличным и файловым) и предоставляет единый API через ContentResolver. Используйте его, если нужно:

  • открыть данные для других приложений (виджеты, sync, сторонние клиенты);
  • реализовать единый CRUD‑интерфейс с контролем прав. Если данные используются только внутри приложения — ContentProvider можно не делать (Room/файлы хватит).

Для простого шаринга файлов (поделиться фото или PDF через Intent) обычно быстрее и безопаснее настроить FileProvider из androidx.core, а не писать свой ContentProvider.

Основные операции: читать и писать content:// URI

Примеры Kotlin — минимально и надёжно.

Чтение как InputStream:

context.contentResolver.openInputStream(uri)?.use { input ->
    val bytes = input.readBytes()
    // обработать bytes
}

Запись через OutputStream:

context.contentResolver.openOutputStream(uri)?.use { out ->
    out.write(data)
}

Если нужен seek/descriptor:

context.contentResolver.openFileDescriptor(uri, "r")?.use { pfd ->
    FileInputStream(pfd.fileDescriptor).use { fis ->
        // читать с поддержкой seek
    }
}

Если вы получаете uri из ACTION_OPEN_DOCUMENT и хотите долгосрочный доступ:

val flags = intent.flags and (Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
context.contentResolver.takePersistableUriPermission(uri, flags)

FileProvider, SAF и дизайн решения

FileProvider

  • Настраивается в манифесте и res/xml/file_paths.xml.
  • Возвращает content:// URI для локальных файлов и позволяет выдавать временные права через FLAG_GRANT_READ_URI_PERMISSION. Пример генерации URI и отправки:
val uri = FileProvider.getUriForFile(context, "${BuildConfig.APPLICATION_ID}.fileprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
    putExtra(Intent.EXTRA_STREAM, uri)
    type = "image/jpeg"
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(intent, "Share"))

Storage Access Framework (DocumentsProvider)

  • Используйте, когда нужно дать пользователю выбирать файлы/папки вне приложения или работать с облачными провайдерами.
  • Подходит для Scoped Storage на Android 11+.

Не передавайте file:// URI между приложениями — это приведёт к исключению. Не выставляйте android:exported="true" для провайдера без необходимости.

Частые ошибки

  • Нет FLAG_GRANT_READ_URI_PERMISSION при отправке Intent → получатель не откроет URI.
  • Неправильный authority в manifest → getUriForFile/ContentResolver не найдёт провайдера.
  • Не закрытые потоки/ParcelFileDescriptor → утечки ресурсов.
  • Доступ по file:// вместо content:// → SecurityException на современных версиях.

FAQ

  • Нужно ли всегда делать ContentProvider для базы данных? Только если данные будут использоваться другими приложениями или системными компонентами.
  • Когда выбирать SAF вместо FileProvider? Если пользователь должен выбирать файлы/папки или нужен доступ к облачному хранилищу.
  • Как дать долгосрочный доступ к uri из picker? Вызвать takePersistableUriPermission после получения результата.

Проверочный чек‑лист перед релизом:

  • Правильный authority и android:grantUriPermissions для FileProvider.
  • addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) при шаринге.
  • Закрыты все потоки и дескрипторы.
  • Минимальные разрешения/права для провайдера, проверка MIME/размера перед обработкой внешних потоков.

Если хотите, подготовлю:

  • готовый пример ContentProvider + Room (Kotlin) с UI;
  • файл res/xml/file_paths.xml под вашу структуру;
  • инструкцию по миграции под Scoped Storage.