Как работать с DocumentFile и Storage Access Framework

SAF (Storage Access Framework) даёт приложению доступ к выбранным пользователем файлам и папкам через Uri; DocumentFile — удобная обёртка для CRUD‑операций над этими Uri. Чтобы доступ сохранялся между сессиями — обязательно вызвать takePersistableUriPermission и хранить строковое представление Uri.

Зачем это нужно и когда использовать

  • Нужен доступ к файлам вне getExternalFilesDir() на Android 11+ (Scoped Storage).
  • Для файловых менеджеров, редакторов, бэкапа и импорта/экспорта.
  • Не нужно, если достаточно работать в собственной папке приложения.

Настройка проекта и базовый flow

  1. Добавьте зависимость:
implementation "androidx.documentfile:documentfile:1.0.1"
implementation "androidx.activity:activity-ktx:1.9.2"
  1. Запускайте системный picker и сохраняйте права навсегда:
  • Рекомендуемый способ: Activity Result API.

Пример выбора папки (ActivityResultLauncher):

val openFolderLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
    if (uri != null) {
        contentResolver.takePersistableUriPermission(uri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
        prefs.edit().putString("tree_uri", uri.toString()).apply()
        // DocumentFile.fromTreeUri(context, uri) — дальше работаем с ним
    }
}

Загрузка сохранённого Uri:

fun loadTreeUri(): Uri? = prefs.getString("tree_uri", null)?.let { Uri.parse(it) }

Основные операции с DocumentFile

  • Получить корень: val root = DocumentFile.fromTreeUri(context, treeUri)!!
  • Перечислить: root.listFiles()
  • Создать папку: root.createDirectory("MyFolder")
  • Создать файл: folder.createFile("text/plain", "note.txt")
  • Читать: contentResolver.openInputStream(file.uri)?.use { it.reader().readText() }
  • Писать: contentResolver.openOutputStream(file.uri)?.use { it.write(bytes) }
  • Удалить: file.delete()

Быстрый старт: вызовите OpenDocumentTree и сразу сохраните права через takePersistableUriPermission — этого достаточно для большинства задач.

Таблица: сравнение DocumentFile и File API

ЧтоDocumentFile (SAF)File API
Доступ к внешним файламПо выбору пользователя (Uri)Через разрешения (ограничено)
Совместимость Android 11+ДаТолько внутренняя папка
Сохранение доступаtakePersistableUriPermissionЧерез runtime‑permissions
УдобствоРабота с Uri, нужен ContentResolverПрямые пути, проще API

Не рассчитывайте на корректный размер файла через documentFile.length() — SAF на некоторых провайдерах возвращает 0 или неточные значения. Используйте openInputStream для проверки реального размера.

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

  • Не вызывают takePersistableUriPermission — доступ теряется после перезапуска.
  • Используют deprecated startActivityForResult вместо Activity Result API.
  • Пробуют создать DocumentFile из невалидного Uri (проверяйте null).
  • Ожидают File API‑поведение (пути, доступы, блокировки).

FAQ

  • Нужно ли запрашивать READ_EXTERNAL_STORAGE? Нет — для выбранных через SAF ресурсов это не требуется.
  • Можно ли копировать файлы между treeUri разных провайдеров? Да, но придётся читать через InputStream и писать через OutputStream на целевой Uri.
  • Как отозвать доступ? Удалить сохранённый Uri из prefs и попросить пользователя убрать доступ в настройках системного управления доступом.

Используйте SAF + DocumentFile, когда нужно уважать приватность пользователя и совместимость с Scoped Storage — это единственный надёжный путь работать с внешними файлами на современных Android.