Как открыть файл и папку через системный пикер SAF
В кратком ответе: вызовите Intent ACTION_OPEN_DOCUMENT (для файла) или ACTION_OPEN_DOCUMENT_TREE (для папки), сразу сохраните persistable permission через takePersistableUriPermission и работайте с Uri через ContentResolver (openInputStream/openOutputStream). Ниже — готовые примеры и практические советы для безопасной и устойчивой работы.
Быстрый старт — выбрать файл или папку
- Для файлов используйте ACTION_OPEN_DOCUMENT (или ActivityResultContracts.OpenDocument).
- Для директорий — ACTION_OPEN_DOCUMENT_TREE (или ActivityResultContracts.OpenDocumentTree).
- Обязательно запрашивайте persistable permission, если хотите сохранить доступ между запусками.
Пример (Kotlin, Activity/Fragment, Activity Result API):
private val openFileLauncher = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
uri?.let {
contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_READ_URI_PERMISSION)
// читать: contentResolver.openInputStream(it)
}
}
private val openTreeLauncher = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
uri?.let {
contentResolver.takePersistableUriPermission(
it,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
// работать с деревом
}
}
// Запуск
openFileLauncher.launch(arrayOf("application/pdf")) // или arrayOf("*/*")
openTreeLauncher.launch(null)
Всегда вызывайте takePersistableUriPermission сразу после получения Uri — иначе доступ может быть утерян после перезапуска приложения.
Чтение и запись через ContentResolver
- Чтение:
fun readFile(uri: Uri) {
contentResolver.openInputStream(uri)?.use { input ->
val bytes = input.readBytes()
// обработка
}
}
- Запись (в файл):
contentResolver.openOutputStream(targetUri)?.use { out ->
out.write("Новый контент".toByteArray())
}
- Создание файла в выбранной папке (TREE):
fun createFileInTree(treeUri: Uri, fileName: String): Uri? {
val treeId = DocumentsContract.getTreeDocumentId(treeUri)
return DocumentsContract.createDocument(contentResolver, treeIdToUri(treeId), "text/plain", fileName)
}
(Уточните правильный uri для createDocument через DocumentsContract.buildDocumentUriUsingTree если нужно.)
Для мониторинга изменений используйте ContentObserver на соответствующем Uri или периодическую ресканку при необходимости.
Продвинутые темы и совместимость
- Scoped Storage (Android 10+) делает SAF основным способом доступа к медиаконтенту без ALL_FILES_ACCESS.
- SAF поддерживает внешние SD-карты, но поведение OEM-UI может отличаться — тестируйте на реальных девайсах.
- Храните URI и права: persistable permissions сохраняются системой; чтобы отозвать — вызовите releasePersistableUriPermission(uri).
- Ограничение: на устройстве примерно ~1000 persistable permission; не накапливайте ненужные URI.
Не храните тысячи URI — лимит по устройству ~1000. Освобождайте права для неактуальных Uri через releasePersistableUriPermission.
Частые ошибки
- Permission denied после выбора: забыли вызвать takePersistableUriPermission или использовали неверный флаг.
- Файлы на SD не видны: приложение не имеет подходящего права через SAF; проверьте, правильно ли выбран каталог и сохранены ли права.
- Неправильная обработка MIME-типов: используйте arrayOf("/") для общего выбора, конкретный MIME для фильтрации.
- Ошибка при создании файла в дереве: используйте корректный Uri/DocumentId и проверяйте возвращаемое значение createDocument.
FAQ
- Нужно ли просить MANAGE_EXTERNAL_STORAGE? Нет. Для большинства сценариев SAF покрывает доступ без запроса ALL_FILES_ACCESS (который ограничен политикой Play).
- Как сохранить доступ навсегда? Сохраните persistable permission; система хранит её до явного отзыва пользователем или программы.
- Можно ли открыть несколько файлов сразу? Да — используйте ACTION_OPEN_DOCUMENT с Intent.EXTRA_ALLOW_MULTIPLE или соответствующий контракт.
- Как протестировать SD-карту? Запустите эмулятор с смонтированной внешней картой и проверьте на разных реальных устройствах (Samsung, Xiaomi и т.д.), так как UI и поведение могут различаться.
Тестируйте на Android 11+ и реальных устройствах, сохраняйте только нужные URI и работайте через ContentResolver — это покрывает большинство сценариев работы с файлами и гарантирует соответствие политике Google Play.