← Назад к вопросам

Как считать файлы без permission на доступ к файловой системе

2.0 Middle🔥 141 комментариев
#Android компоненты#Работа с данными

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отличный и очень практичный вопрос. В современных версиях 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.
  • Пути файлов (File API) устарели: Не пытайтесь получить абсолютный путь из 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.