Read external storage что это
Хранение данных и файлов
В целом хранение файлов и данных можно условно разделить на две группы: во внутреннем или внешнем хранилище. Но разница между ними довольна тонка. В целом политика Гугла в отношение данных ужесточается с каждой версии системы.
Android поддерживает различные варианты хранения данных и файлов.
В зависимости от ваших потребностей, нужно выбрать нужный вариант хранения данных.
Следует быть осторожным при работе с внутренним и внешним хранилищем. Внутренне хранилище всегда есть в системе, но оно может быть не слишком большим по объёму. Вдобавок к внутреннему хранилищу, устройство может иметь внешнее хранилище. В старых моделях таким хранилищем выступала съёмная SD-карта. Сейчас чаще используют встроенную и недоступную для извлечения флеш-память. Если ваше приложение слишком большое, можно попросить систему устанавливать программу во внешнее хранилище, указав просьбу в манифесте.
В разных версиях Android требования к разрешению для работы с внешним хранилищем постоянно менялись. На данный момент (Android 10, API 29) требования выглядят следующим образом.
Приложение может иметь доступ к собственным файлам, которые находятся во внешнем хранилище. Также может получить доступ к определённым общим файлам на внешнем хранилище.
Доступ к общим файлам достигается через FileProvider API или контент-провайдеры.
Для просмотра файлов через студию используйте инструмент Device File Explorer.
Внешняя карта памяти
Попробуем немного разобраться с этим зоопарком. Но помните, что процесс путаницы продолжается.
При подготовке материала я опирался на письма некоторых читателей сайта, которые присылали свои мысли по этому поводу. Спасибо им за структуризацию материала.
Вот что я (кажется) понял, попытавшись загрузить картинку с внешней SD карточки.
External это не External
«EXTERNAL_STORAGE» называется так не потому, что это внешняя память по отношению к устройству, а потому что она выглядит как внешняя память для компьютера, если устройство подключить кабелем к компьютеру. Причём именно выглядит, потому что обмен идёт по протоколу MTP – устройство только показывает компьютеру список папок и файлов, а при необходимости открыть или скопировать файл он специально загружается на компьютер, в отличие от настоящей флешки, файлы которой становятся файлами в файловой системе самого компьютера. Обмен по MTP позволяет устройству продолжать работать, когда оно подключено к компьютеру.
Emulated это не Emulated
Сначала я пытался прочесть файл с карточки на эмуляторе (из этого так ничего и не вышло). Функция getExternalStorageDirectory() давала мне /storage/emulated/0, и я думал, что «emulated» – это потому что на эмуляторе. Но когда я подцепил реальный планшет, слово «emulated» никуда не исчезло. Я стал рыться в интернете и обнаружил, что «Emulated storage is provided by exposing a portion of internal storage through an emulation layer and has been available since Android 3.0.» – то есть это просто кусок внутренней памяти, которая путём какой-то эмуляции делается доступной для пользователя, в отличие от собственно внутренней памяти.
При этом с точки зрения системы доступная для пользователя папка называется /storage/emulated/0, а при подключении к компьютеру по USB это просто одна из двух главных папок устройства – у меня в Windows Explorer она называется Tablet. Вторая папка у меня называется Card, и это и есть настоящая внешняя карточка.
Оказалось, что несистемным приложениям в принципе запрещено напрямую обращаться к съёмной карточке! Похоже, что это было так всегда, но вот начиная с версии Android 6 Marshmallow написано: внешняя карточка может быть определена как Portable либо Adoptable. Adoptable – это как бы «усыновляемая» память которая может быть «adopted», то есть взята в систему (примерно как кот с улицы в дом – это тоже называется to adopt) и использована как внутренняя. Для этого ее надо особым образом отформатировать и не вынимать, иначе не факт, что система продолжит нормально работать.
Portable – это нормальная съёмная карточка, но несистемным приложениям запрещено обращаться из программ к файлам на ней! Вот что написано в https://source.android.com/devices/storage/traditional.html:
Android 6.0 supports portable storage devices which are only connected to the device for a short period of time, like USB flash drives. When a user inserts a new portable device, the platform shows a notification to let them copy or manage the contents of that device. In Android 6.0, any device that is not adopted is considered portable. Because portable storage is connected for only a short time, the platform avoids heavy operations such as media scanning. Third-party apps must go through the Storage Access Framework to interact with files on portable storage; direct access is explicitly blocked for privacy and security reasons.
Если я правильно понял, этот самый Storage Access Framework позволяет работать с документом на карточке через диалог (открыть файл/сохранить файл), а вот прочитать или записать файл на карточке непосредственно из программы невозможно.
Общий вывод – реально из программы можно работать только с файлами на предоставляемой пользователю части встроенной памяти устройства, а на съёмной карточке – нет.
Состояние на текущий момент
Гугл утверждает, что с версии Android 10 Q стандартный доступ к файлам будет прекращён. Ещё в Android 4.4 появился Storage Access Framework, который и должен стать заменой для работы с файлами.
Методы Environment.getExternalStorageDirectory() и Environment.getExternalStoragePublicDirectory() признаны устаревшими и будут недоступны. Даже если они будут возвращать корректные значения, ими вы не сможете воспользоваться.
В Android 7.0 добавили исключение FileUriExposedException, чтобы разработчики перестали использовать схему file://Uri.
Можно создавать файлы в корневой папке карточки при помощи Environment.getExternalStorageDirectory(), а также папки с вложенными файлами. Если папка уже существует, то у вас не будет доступа на запись (если это не ваша папка).
Если вы что-то записали, то сможете и прочитать. Чужое читать нельзя.
Кстати, разрешения на чтение и запись файлов не требуются, а READ_EXTERNAL_STORAGE и WRITE_EXTERNAL_STORAGE объявлены устаревшими.
Другие приложения не могут получить доступ к файлам вашего приложения. Файлы, которые вы создали через getExternalFilesDir(), доступны через Storage Access Framework, кроме файлов, созданных в корне карточки (что-то я совсем запутался). Ещё можно дать доступ через FileProvider.
При подключении USB-кабеля через getExternalFilesDir(), вы можете увидеть свои файлы и папки, а также файлы и папки пользователя. При этом файлы и папки пользователя на корневой папке вы не увидите. Вам не поможет даже adb или Device File Explorer студии.
Что делать?
Пользуйтесь методами класса Context, типа getExternalFilesDir(), getExternalCacheDir(), getExternalMediaDirs(), getObbDir() и им подобными, чтобы найти место для записи.
Используйте Storage Access Framework.
Используйте MediaStore для мультимедийных файлов.
Используйте FileProvider, чтобы файлы были видимы другим приложениям через ACTION_VIEW/ACTION_SEND.
Android 10: Появился новый флаг android:allowExternalStorageSandbox=»false» и метод Environment.isExternalStorageSandboxed() для работы с песочницей. Флаг android:requestLegacyExternalStorage=»true» для приложений, которые ещё используют старую модель доступа к файлам.
Как временное решение можно добавить в блок манифеста application атрибут android:requestLegacyExternalStorage=»true», чтобы доступ к файлам был как раньше в Android 4.4-9.0.
Android 11
Если вы создаёте файловый менеджер, то ему нужны возможности для просмотра файлов. Для этого следует установить разрешение MANAGE_EXTERNAL_STORAGE или использовать атрибут android:requestLegacyExternalStorage=»true» (см. выше).
Read external storage что это
Android использует файловую систему, которая аналогична дисковым файловым системам на других платформах. Эта лекция описывает, как работать с файловой системой Android для чтения и записи файлов с помощью File API (перевод документации [1]).
Объект File подходит для чтения или записи больших объемов данных в порядке от начала до конца, без пропусков. Например, это хорошо подходит для файлов картинок или для различных обменов данными через сеть. Здесь будет показано, как выполнять базовые файловые операции в Вашем приложении. Подразумевается, что Вы знакомы с файловой системой Linux и стандартной системой ввода/вывода файлов в (standard file input/output API) в java.io.
[Выбор между внутренним и внешним хранилищем (Internal Storage, External Storage)]
Internal storage (внутреннее, неизвлекаемое хранилище): | External storage (внешнее, извлекаемое хранилище): |
• Оно всегда доступно. • Файлы, которые сохранены здесь, по умолчанию доступны только Вашего приложения. • Не требуется запрашивать разрешения на доступ к internal storage для Вашего приложения. • Когда пользователь деинсталлирует Ваше приложение, то система также удалит все файлы приложения с internal storage. Вывод: external storage лучшее место для файлов, которые не требуют ограничений на доступ и для файлов, которые Вы хотите сделать общими с другими приложениями, или если Вы хотите, чтобы пользователь мог получить доступ к файлам с помощью компьютера. |
Совет: несмотря на то, что приложения по умолчанию устанавливаются в internal storage, Вы можете указать атрибут android:installLocation в файле манифеста, после чего Ваше приложение может быть установлено и на external storage. Пользователи ценят эту опцию, когда размер APK очень велик, и размер external storage space больше, чем internal storage. Дополнительную информацию см. в документации App Install Location [2].
[Получение разрешения для приложения на доступ к External Storage]
Чтобы иметь возможность записи в external storage, Вы должны запросить в файле манифеста разрешение WRITE_EXTERNAL_STORAGE :
Но если Ваше приложение использует разрешение WRITE_EXTERNAL_STORAGE, то это неявно дает ему также разрешение использовать и чтение external storage.
Вам не нужно получать никаких разрешений на сохранение файлов в internal storage. Ваше приложение всегда имеет разрешение на чтение и запись файлов в свой внутренний каталог на internal storage.
[Сохранение файла в Internal Storage]
Когда сохраняется файл в internal storage, Вы можете запросить подходящую директорию для объекта файла File вызовом одного из двух методов:
getFilesDir() возвращает объект File, представляющий внутренний каталог Вашего приложения.
getCacheDir() возвращает объект File, представляющий внутренний каталог временных файлов кэша Вашего приложения. Обязательно удаляйте оттуда каждый файл, когда он больше не нужен, и реализуйте разумный предел размера для объема памяти, который используете в любой момент времени, такой как предел в 1 мегабайт. Если система Android обнаружит, что на внутреннем хранилище недостаточно места, то она может удалить Ваши файлы кэша без предупреждения.
Чтобы создать новый файл в одной из этих директорий, Вы можете использовать конструктор File(), передав ему File, предоставленный одним из этих методов, которые укажут каталог на internal storage. Пример:
Альтернативно Вы можете вызвать openFileOutput(), чтобы получить FileOutputStream, который записывает файл в Вашей внутренней директории. Например, здесь показано, как записать некий текст в файл:
Примечание: каталог internal storage Вашего приложения указывается на основе имени пакета приложения в специальном месте файловой системы Android. Технически другое приложение может прочитать Ваши внутренние файлы, если Вы установите файловый режим с разрешенным чтением. Однако для этого другое приложение должно также знать имя пакета Вашего приложения и имена используемых Вашим приложением файлов. Другие приложения не могут просматривать Ваши внутренние директории, и не могут получить доступ на чтение или запись, за исключением случая, когда Вы явно установите файл как читаемый и/или записываемый. Таким образом, пока Вы используете MODE_PRIVATE для Ваших файлов на internal storage, то они никогда не будут доступны для других приложений.
[Сохранение файла в External Storage]
Несмотря на то, что external storage может быть модифицировано пользователем и другими приложениями, есть две категории файлов, которые могут быть сохранены здесь:
Если Вы хотите сохранить файлы, которые являются частными (private) для Вашего приложения, Вы можете получить подходящую директорию вызовом метода getExternalFilesDir() и передачей ему имени, указывающего тип директории, который Вам нужен. Каждая директория, созданная таким способом, будет добавлена к родительской директории, в которой инкапсулированы все файлы внешнего хранилища Вашего приложения, которые система удалит, когда пользователь деинсталлирует Ваше приложение. Например, вот метод, которым Вы можете создать директорию индивидуального фотоальбома:
Если ни одно из предварительно определенных имен поддиректорий не подходит для Ваших файлов, то Вы можете вместо этого вызвать getExternalFilesDir() и передать null. Это возвратит корневую частную директорию для Вашего приложения на external storage.
[Опрос количества свободного места]
Однако система не гарантирует, что Вы можете записать столько байт, сколько показывает вызов getFreeSpace(). Если возвращенное количество всего на несколько мегабайт больше, чем Вам нужно сохранить, или если файловая система уже заполнена меньше, чем на 90%, то вероятно сохранение будет безопасным. Иначе возможно, что записать данные в хранилище не получится.
Внимание: Вам не обязательно проверять количество свободного места перед сохранения файла. Вместо этого Вы можете попробовать записать файл сразу же, и затем перехватить исключение IOException, если оно произойдет. Вы возможно, должны так поступить, когда не знаете, сколько места Вам нужно. Например, если Вы меняете способ кодирования файла перед его сохранением, преобразовывая картинку PNG в JPEG, то Вы не будете знать размер файла заранее.
[Удаление файла]
Вы всегда должны удалять файлы, которые Вам больше не нужны. Самый прямой способ удаления файла состоит в том, чтобы иметь этот файл открытым и вызвать delete() для самого себя.
Если файл сохранен на internal storage, Вы можете также запросить Context, чтобы найти и удалить файл вызовом deleteFile():
Внимание: когда пользователь деинсталлирует Ваше приложение, система Android удалит следующее:
• Все файлы, сохраненные Вашим приложением на internal storage.
• Все файлы, сохраненные Вашим приложением с использованием getExternalFilesDir().
Однако Вы должны регулярно удалять все кэшируемые файлы, создаваемые с getCacheDir(), и также регулярно удалять файлы, которые Вам больше не нужны.
[Пример записи файла на sdcard0]
Предположим, что необходимо записать какой-нибудь тестовый файл (с именем myFile.txt) в папку myFolder на внешний носитель, который виден в системе Android как sdcard0. Т. е. полный путь должен выглядеть примерно так:
Базовый путь до External Storage
Проверка доступности носителя данных в External Storage
Вторая проблема состоит в доступности на запись носителя данных. Дело в том, что записать на носитель можно не всегда, например если он смонтирован как флешка USB (когда Ваш телефон подключен к компьютеру в режиме Mass Storage Device, USB MSD). Проверить доступность носителя можно следующей функцией:
Разрешение доступа к носителю данных в файле манифеста
Функция, которая сохраняет файл, принимая полный путь до файла filePath и сохраняемый текст FileContent:
Вызов функции SaveFile, который выполняет задачу сохранения файла в External-носителе:
[Пример записи файла на extSdCard]
Получение полного корневого пути до извлекаемой карты SD не так прост, как до External Storage, поскольку в API Android для этого почему-то не предусмотрены специальные простые функции. Приходится получать путь окольными путями, через имена системных папок. Вот код функции, которая получает путь до извлекаемой карты SD:
Вызов функции SaveFile, который выполняет задачу сохранения файла на извлекаемой карте SD:
[Сохранение бинарного файла (массива byte[])]
Примеры вызовов getAbsolutePath:
Вызов | Результат вызова |
Environment.getRootDirectory.getAbsolutePath() | /system |
Environment.getExternalStorageDirectory().getAbsolutePath() | /storage/sdcard0 |
Environment.getExternalStoragePublicDirectory(null).getAbsolutePath() | завершится с ошибкой |
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS).getAbsolutePath() | /storage/sdcard0/Alarms |
[Ссылки]
Комментарии
В новых Android доступ на запись надо оформлять не в Манифесте. Вот что пишут: «Android added new permission model for Android 6.0 (Marshmallow). What Are Runtime Permissions? With Android 6.0 Marshmallow, Google introduced a new permission model that allows users to better understand why an application may be requesting specific permissions. Rather than the user blindly accepting all permissions at install time, the user is now prompted to accept permissions as they become necessary during application use.
Android Storage: Internal, External, Removable. Часть 1/3
Всем, кто, несмотря ни на что, сумел сделать правильный выбор.
Это перевод серии статей от Mark Murphy из CommonsWare, широко известного на stackoverflow, а так же автора книг “The Busy Coder’s Guide to Android Development”, “Android’s Architecture Components”. Некоторые термины оставлены не переведенными специально.
Internal Storage
Существует много путаницы в отношении модели хранилища Android. Путаницы стало значительно больше с изменениями Android 4.4 в Storage Model, и с тех пор ситуация не улучшилась. Есть бесчисленное множество вопросов на Stack Overflow и тому подобных ресурсах, где люди явно не совсем разбираются в различных моделях хранилищ Android.
То, что пользователи считают Internal Storage
Пользователь думает, что вся встроенная флешка — это «внутреннее хранилище» (Internal Storage). К счастью, Google начал менять этот термин с Android 8.0, перейдя к «general storage» вместо «internal storage».
Тем не менее, пользователи могут по-прежнему видеть «внутреннее хранилище» в таких местах, как окно проводника в Windows, когда их устройство подключено через USB.
Что Google считает Internal Storage
Увы, то, что видят пользователи это не то же самое, что Android SDK считает «внутренним хранилищем», что приводит к некоторой путанице. Если вы читали документацию на Android по внутреннему хранилищу, то это описание … как минимум туманно (прим. текст изменился со времени написания статьи):
Вы можете сохранять файлы непосредственно во внутренней памяти устройства. По умолчанию файлы, сохраненные во внутреннем хранилище, являются приватными для вашего приложения, и другие приложения не могут получить к ним доступ (также как и пользователь). Когда пользователь удаляет приложение, эти файлы удаляются.
По правде говоря, Android SDK определяет «внутреннее хранилище» как особый каталог, уникальный для вашего приложения, где ваше приложение может размещать файлы. Как было предложено в документации, эти файлы по умолчанию предназначены для чтения и записи для вашего приложения и запрещены для любого другого приложения (исключение: пользователи, работающие с файловыми менеджерами с привилегиями суперпользователя на rooted устройствах могут получить доступ ко всему).
В Context есть несколько базовых методов, которые предоставляют вам доступ к внутреннему хранилищу, в том числе:
Где хранится Internal Storage … Иногда
Где хранится Internal Storage … Остальное время
Однако не всегда внутреннее хранилище вашего приложения находится в указанном месте. Для разработчиков есть одно правило, которое вы должны усвоить из этой серии сообщений в блоге, это:
NEVER HARDCODE PATHS.
Время от времени я вижу, что разработчики делают что-то вроде этого:
File f=new File(«/data/data/their.app.package.name/files/foo.txt»);
Это не преступление, это хуже, это — ошибка.
Правильный ход, да и писать меньше:
File f=new File(getFilesDir(), «foo.txt»);
Что еще более важно, внутреннее хранилище не всегда находится в одном месте. Примечательно, что у нас есть понятие отдельных профилей пользователей (separate user profiles), начиная с Android 4.2 для планшетов и Android 5.0 для телефонов. Каждый пользователь получает свое собственное «внутреннее хранилище». Хотя вышеупомянутый каталог по-прежнему используется для основного пользователя, не гарантируется, что он же будет использоваться для вторичных учетных записей.
Исследуем Internal Storage
Device File Explorer tool в Android Studio 3.0+ может просматривать все внутренние хранилища на эмуляторе, а также внутреннее хранилище отлаживаемых приложений на продакшн устройствах.
Например, чтобы загрузить базу данных из внутреннего хранилища основного пользователя на вашу девелоперскую машину, вы можете использовать:
adb shell ‘run-as your.application.package.name cp /data/data/your.application.package.name/databases/dbname.db /sdcard’
Обратите внимание, что:
Ограничения внутреннего хранилища
На старых устройствах Android 1.x и 2.x внутреннее хранилище обычно находилось в выделенном разделе файловой системы, и этот раздел обычно был довольно крошечным. HTC Dream (a.k.a., T-Mobile G1), оригинальное Android-устройство, обладал огромными 70 МБ встроенной памяти для использования всеми приложениями (это не опечатка, в то время мы измеряли память в мегабайтах).
К тому времени, когда вышли 2.3 устройства, внутреннее хранилище могло быть размером 1 ГБ.
Android 3.0 изменил модель хранилища, так как внутреннее хранилище стало больше объемом. У устройств, которые рекламируют как имеющее 4 ГБ, 8 ГБ, 16 ГБ и т.д. пространства для хранения, обычно имелось все это (минус существующее содержимое) доступное для внутреннего хранилища. Мы рассмотрим, что изменилось в Android 3.0 и его влияние на модель хранилища в следующих постах про внешнее хранилище.
Для Android 1.x и 2.x внутреннее хранилище было действительно только для небольших файлов, и вам нужно было использовать внешнее хранилище для всего остального. Android 3.0+ означает, что для большинства устройств и большинства пользователей внутреннее хранилище отлично подходит для файлов, которые не предназначены для обычного использования другими приложениями или доступны пользователю независимо от вашего приложения. Однако некоторые опытные пользователи обнаруживают, что даже on-board flash недостаточна для того, что они хотят хранить, поэтому они переходят на съемные хранилища… которые представляют собой банку с червями (прим. имеются в виду ἕλμινς) — источник многих непредсказуемых и сложных проблем.
F.A.Q. по Internal Storage
Должен ли я делать файлы во внутреннем хранилище World-Readable или World-Writeable?
android: sharedUserId — это атрибут, который вы можете поместить в манифест, который указывает логический идентификатор пользователя, который будет использоваться для вашего приложения. Любое другое приложение, которое установлено, которое подписывается одним и тем же ключом подписи и запрашивает тот же android:sharedUserId будет использовать одного и того же пользователя Linux с точки зрения безопасности. Эффект заключается в том, что эти два приложения смогут безнаказанно работать с файлами друг друга, так как эти файлы принадлежат одному и тому же пользователю Linux.
Этот атрибут реально предназначен для предварительно установленных приложений, таких как software suite предварительно загруженный производителем устройства, мобильным оператором или разработчиком модифицированной ROM прошивки. В частности, как только вы единожды установите свое приложение, вы не сможете затем безболезненно изменить свое значение android:sharedUserId не заблокировав при этом доступ пользователю к любым существующим файлам… поскольку Android не изменяет права владельца на существующие файлы при изменении учетной записи пользователя Linux, под которой запускается приложение.
Существуют различные риски при одновременном доступе нескольких процессов к файлам. Некоторые подсистемы, такие как SQLite, имеют встроенную логику для решения этой проблемы. Но если вы сами организуете свой собственный доступ к файлу (например, через File и Java I/O), вам нужно что-то делать с одновременным доступом, а это сложно.
Вам также нужно обрабатывать ситуацию, когда одно приложение деинсталлируется, удаляя файлы, которые использовало другое приложение. В модели hub-and-spoke, например, с приложением и набором плагинов, возможно, это не так рискованно. В других моделях, где приложения более равноправны, вы не можете позволить себе потерять данные своего приложения только потому, что пользователь решил удалить какое-то отдельное приложение.
Как запретить пользователям rooted устройств доступ к моим файлам во внутреннем хранилище?
Просто не помещайте файлы во внутреннее хранилище. Пользователи rooted устройств могут получить доступ к тому, что им нужно на устройстве, поэтому единственный способ предотвратить их доступ к вашим данным — не иметь их на устройстве.
Некоторые разработчики попытаются зашифровать свои файлы с помощью жестко запрограммированного пароля, чтобы пользователи rooted устройств не могли использовать эти файлы. Это создаст эффект «лежачего полицейского» на короткое время. Все, что требуется, — это один заинтересованный в реверс-инжиниринге вашего приложения человек, определивший, как расшифровать эти файлы, а затем написавший сообщение в блоге или форуме о том, как это сделать.
В целом, относительно мало людей с rooted устройствами — я оцениваю их на уровне менее 1%. ИМХО, вы преуспеете больше, сосредоточив свою инженерную работу на написании лучшего приложения, вместо того, чтобы тратить время на защиту от рутованных устройств.