Как работать с DocumentFile и Storage Access Framework
SAF (Storage Access Framework) даёт приложению доступ к выбранным пользователем файлам и папкам через Uri; DocumentFile — удобная обёртка для CRUD‑операций над этими Uri. Чтобы доступ сохранялся между сессиями — обязательно вызвать takePersistableUriPermission и хранить строковое представление Uri.
Зачем это нужно и когда использовать
- Нужен доступ к файлам вне getExternalFilesDir() на Android 11+ (Scoped Storage).
- Для файловых менеджеров, редакторов, бэкапа и импорта/экспорта.
- Не нужно, если достаточно работать в собственной папке приложения.
Настройка проекта и базовый flow
- Добавьте зависимость:
implementation "androidx.documentfile:documentfile:1.0.1"
implementation "androidx.activity:activity-ktx:1.9.2"
- Запускайте системный 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.