Как считать файлы без permission на доступ к файловой системе
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Отличный и очень практичный вопрос. В современных версиях Android (начиная с API 29 / Android 10) прямой подход без какого-либо разрешения для чтения файлов сильно ограничен политиками конфиденциальности и изоляции приложений, но возможен в определенных, четко очерченных сценариях.
Ключевой принцип — управление доступом через интенты и системные пикеры, что позволяет пользователю самостоятельно выбрать файлы, к которым приложению будет предоставлен временный или постоянный доступ.
Основные способы без разрешения READ_EXTERNAL_STORAGE
1. Использование системного файлового пикера (ACTION_GET_CONTENT / ACTION_OPEN_DOCUMENT)
Это основной и рекомендованный способ. Вы запускаете активность системы, пользователь выбирает файл, и ваше приложение получает URI на этот файл с временными правами на чтение.
private val pickFileLauncher = registerForActivityResult(
ActivityResultContracts.GetContent() // Используйте OpenDocument для более точечного выбора
) { uri: Uri? ->
uri?.let { readContentFromUri(it) }
}
fun selectFile() {
// Можно указать MIME-тип, например "image/*", "application/pdf"
pickFileLauncher.launch("*/*")
}
private fun readContentFromUri(uri: Uri) {
contentResolver.openInputStream(uri)?.use { inputStream ->
// Читаем данные файла через inputStream
val text = inputStream.bufferedReader().readText()
// Работаем с содержимым...
}
}
- ACTION_GET_CONTENT (
GetContent()): Более классический пикер. - ACTION_OPEN_DOCUMENT (
OpenDocument()): Часть фреймворка Storage Access Framework (SAF). Предоставляет более устойчивый долгосрочный доступ (можно запросить черезtakePersistableUriPermission).
2. Чтение файлов из собственных (private) директорий приложения
Разрешения не требуются для доступа к внутреннему и внешнему приватному хранилищу вашего приложения.
// Чтение из внутренней памяти (папка, удаляемая с приложением)
val internalFile = File(context.filesDir, "my_data.txt")
val content = internalFile.readText()
// Чтение из внешнего приватного хранилища (папка в Android/data/, доступная пока приложение установлено)
val externalPrivateFile = File(context.getExternalFilesDir(null), "log.txt")
val logs = externalPrivateFile.readText()
3. Чтение файлов, созданных через MediaStore (особенно для Android 10+)
Для доступа к общим медиафайлам (фото, видео, аудио) можно использовать MediaStore API без разрешения на хранение, но с соответствующими разрешениями на доступ к медиа.
- На Android 10 (API 29) и выше: Разрешение
READ_EXTERNAL_STORAGEне нужно для доступа к собственным медиафайлам приложения и публичным коллекциям (MediaStore.Images,.Video,.Audio,.Downloadsв некоторых случаях). - На Android 13 (API 33) и выше: Введены гранулярные разрешения (
READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO) вместо общегоREAD_EXTERNAL_STORAGE.
Пример запроса изображений через MediaStore (на Android 10+):
val projection = arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME)
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
projection,
null,
null,
"${MediaStore.Images.Media.DATE_ADDED} DESC"
)
cursor?.use {
while (it.moveToNext()) {
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
val name = it.getString(it.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME))
val contentUri = ContentUris.withAppendedId(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
id
)
// Можно использовать contentUri для загрузки изображения (например, в Glide/Picasso)
// Glide.with(context).load(contentUri).into(imageView)
}
}
4. Получение файлов от других приложений (через Intent.ACTION_SEND)
Если ваше приложение принимает контент, например, через "Поделиться".
// В манифесте для Activity
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
// В коде Activity
when (intent?.action) {
Intent.ACTION_SEND -> {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
uri?.let { readContentFromUri(it) } // Используем тот же метод, что и в п.1
}
}
Важные ограничения и нюансы
- Временный доступ: URI, полученный через пикер, часто дает доступ только до перезагрузки устройства или пока ваше приложение не будет завершено системой. Для долгосрочного доступа используйте
takePersistableUriPermission. - Пути файлов (
FileAPI) устарели: Не пытайтесь получить абсолютный путь изUri(например, черезUri.getPath()). Для работы с содержимым всегда используйтеContentResolverи потоки (openInputStream,openOutputStream). - Директория Downloads на Android 10+: Прямой доступ к файлам в общих папках, не являющихся медиа, затруднен. Используйте
ACTION_OPEN_DOCUMENTилиMediaStore.Downloads(для API 29+). - Legacy-режим: Для таргетинга на API 28 и ниже с сохранением старого поведения на новых устройствах можно использовать
requestLegacyExternalStorage="true"в манифесте. Но это временное решение, не одобряемое для новых приложений.
Итог: Полноценно "ходить" по файловой системе без разрешений нельзя и не нужно. Современный Android-разработчик должен использовать интенты, ContentResolver и URI, предоставляемые системой, уважая выбор пользователя и обеспечивая его конфиденциальность. Основной инструмент — это registerForActivityResult с контрактами GetContent или OpenDocument.