- Загрузить файл из внешнего хранилища с помощью селектора
- 2 ответа
- Uri с com.android.externalstorage.documents, расположенными на не первичной томе
- Как некоторые приложения получают доступ к содержимому подпапок «… / Android /…» на Android 11 без рута?
- Задний план
- Проблема
- Что я нашел и попробовал
- Вопросы
- 3 ответа
- Хранение данных и файлов
- Внешняя карта памяти
- Состояние на текущий момент
- Что делать?
- Android 11
Загрузить файл из внешнего хранилища с помощью селектора
Итак, я пытаюсь загрузить простой текстовый файл, например:
И, конечно же, поймать такой результат:
Он отлично работает на genymotion и на моем устройстве, если я использую файловый проводник, который я установил (проводник, см. Изображение выше), теперь, если использовать селектор прямо следующим образом:
Он говорит, что не может найти указанный файл. (FileNotFoundException)
Теперь я понял, что URI, которые я получаю от этих двух средств выбора файлов, разные.
content: //com.android.externalstorage.documents/document/primary%3ADownload%2Ffile.txt 4
2 ответа
Я попытался использовать преобразователь содержимого, чтобы получить путь к файлу из URI следующим образом
Путь к файлу отсутствует. Uri не обязательно указывает на file, к которому вы можете получить доступ в своей локальной файловой системе, так же как URL-адрес этой веб-страницы не обязательно указывает на файл, к которому вы можете получить доступ в своей локальной файловой системе. Uri может:
- представляют файл, хранящийся во внутренней памяти другого приложения
- представляют значение столбца BLOB в базе данных
- представляют собой файл, хранящийся в «облаке», который будет загружен по запросу
- и т.п.
Используйте ContentResolver и такие методы, как openInputStream() , чтобы читать содержимое, представленное Uri , точно так же, как вы использовали бы HttpUrlConnection для чтения содержимого, представленного URL-адресом для этого Страница в Интернете. openInputStream() принимает Uri в качестве параметра и возвращает InputStream . Вы бы использовали этот InputStream так же, как и любой другой InputStream (например, FileInputStream , InputStream из HttpUrlConnection ). Точный механизм этого будет во многом зависеть от базовых данных (например, читать в строку, перейдите к BitmapFactory , чтобы декодировать Bitmap ).
Ответ @ CommonsWare должен быть правильным способом решения этой проблемы, но я не уверен, как реализовать его решение. В итоге я сделал то, что рекомендует @Paul Burke по этой ссылке:
ИЗМЕНИТЬ
В моем конкретном случае так закончился мой код. Я оставляю это здесь на будущее. (Я использую объяснение @ CommonsWare) см. Его ответ выше.
Источник
Uri с com.android.externalstorage.documents, расположенными на не первичной томе
У меня проблема. У меня есть Uri который читает что-то вроде:
Я могу найти StorageVolume через StorageManager.getStorageVolumes()
Если объем является основным: StorageVolume.isPrimary() — я понимаю, что могу разобрать путь как:
Но как найти путь, если объем не является основным? В конце концов, мне нужно иметь простой объект File ссылается его абсолютный путь.
PS Конечно, у меня есть все необходимое разрешение: либо статические объявлены через манифест или во время выполнения обоих.
Я понимаю, что могу разобрать путь как
Не надежно. Вы принимаете определенную внутреннюю реализацию кода, который может отличаться от производителя устройства и версии ОС.
В конце концов, мне нужно иметь простой объект File, на который ссылается его абсолютный путь.
Шаг # 1: используйте ContentResolver и openInputStream() чтобы получить InputStream на содержимое, идентифицированное Uri
Шаг № 2: Откройте FileOutputStream на некоторый файл, который вы контролируете (например, в getCacheDir() )
Шаг 3: Скопируйте содержимое из InputStream в FileOutputStream
Шаг 4: используйте полученный файл, удалив его, когда он больше не нужен
Конечно, у меня есть все необходимое разрешение
Конечно, нет, поскольку у вас нет разрешений на доступ к файлам на съемном носителе, вне очень специфических мест (например, второй и последующие пути, возвращаемые getExternalFilesDirs() ).
Источник
Как некоторые приложения получают доступ к содержимому подпапок «… / Android /…» на Android 11 без рута?
Задний план
Существуют различные ограничения хранилища для Android 10 и 11 , который также включает новое разрешение ( MANAGE_EXTERNAL_STORAGE) для доступа все файлы (но он не разрешает доступ действительно ко всем файлам), в то время как предыдущее разрешение на хранение было уменьшено, чтобы предоставить доступ только к медиафайлам:
- Приложения могут свободно обращаться к подпапке «media».
- Приложения никогда не могут получить доступ к подпапке «данные» и особенно к содержимому.
- Для папки «obb», если приложению разрешено устанавливать приложения, оно может добраться до нее (скопировать туда файлы). Иначе не может.
- Используя USB или root, вы все равно можете связаться с ними, а как конечный пользователь вы можете связаться с ними через встроенный файловый менеджер «Files».
Проблема
Я заметил приложение, которое каким-то образом преодолевает это ограничение (здесь) с названием» X-plore»: после входа в папку «Android / data» вас попросят предоставить к ней доступ. (как-то напрямую используя SAF), и когда вы его предоставите, вы получите доступ ко всему во всех папках папки «Android».
Это означает, что, возможно, еще есть способ достичь этого, но проблема в том, что я по какой-то причине не смог сделать образец, который делает то же самое.
Что я нашел и попробовал
Кажется, это приложение нацелено на API 29 (Android 10), и что оно еще не использует новое разрешение, и что у него есть флаг requestLegacyExternalStorage. Я не знаю, сработает ли тот же трюк, который они используют, при таргетинге на API 30, но могу сказать, что в моем случае, работающем на Pixel 4 с Android 11, он работает нормально.
Итак, я попытался сделать то же самое:
Я сделал образец POC, предназначенный для Android API 29, с предоставленными разрешениями на хранилище (всех видов), включая устаревший флаг.
Я попытался запросить доступ напрямую к папке «Android» (на основе здесь), который, к сожалению, не сработал по какой-то причине (продолжал переходить в папку DCIM, не знаю почему):
Пробовал разные комбинации флагов.
При запуске приложения, когда я сам добираюсь до папки «Android» вручную, так как это не сработало, я предоставил доступ к этой папке, как и к другому приложению.
Получив результат, я пытаюсь получить файлы и папки по пути, но получить их не удается:
Таким образом, с помощью DocumentFile.fromTreeUri я все еще мог получить просто папку «media», которая бесполезна, и, используя класс File, я мог видеть только, что есть также папки «data» и «obb», но все еще не мог добраться до их содержимого .
Так что это вообще не сработало.
Позже я обнаружил другое приложение, использующее этот трюк, под названием «MiXplorer». В этом приложении ему не удалось напрямую запросить папку «Android» (возможно, он даже не пытался), но он предоставляет вам полный доступ к ней и ее подпапкам, как только вы это разрешите. И он нацелен на API 30, поэтому это означает, что он не работает только потому, что вы нацеливаетесь на API 29.
Я заметил (кто-то написал мне), что с некоторыми изменениями в коде я могу запросить доступ к каждой из подпапок отдельно (имеется в виду запрос на «данные» и новый запрос на «obb»), но это не то, что я здесь вижу, а приложения.
То есть, чтобы попасть в папку «Android», я использую этот Uri в качестве параметра для Intent.EXTRA_INITIAL_URI:
Однако, как только вы получите к нему доступ, вы не сможете получить из него список файлов ни через File, ни через SAF.
Но, как я уже писал, странно то, что если вы попробуете что-то подобное, вместо того чтобы перейти к «Android / data», вы сможете получить его содержимое:
Вопросы
- Как я могу запросить намерение непосредственно в папке «Android», которое фактически позволит мне получить к нему доступ и позволит мне получить подпапки и их содержимое?
- Есть ли для этого другая альтернатива? Может быть, используя adb и / или root, я мог бы предоставить SAF доступ к этой конкретной папке?
3 ответа
Вот как это работает в X-plore:
Когда на Build.VERSION.SDK_INT>=30 , [Внутреннее хранилище] / Android / data недоступно, java File.canRead() или File.canWrite() возвращает false, поэтому нам нужно переключиться на альтернативную файловую систему для файлов внутри этой папки (и, возможно, также obb).
Вы уже знаете, как работает структура доступа к хранилищу, поэтому я просто подробно расскажу, что именно нужно сделать.
Вы звоните ContentResolver.getPersistedUriPermissions() , чтобы узнать, есть ли у вас уже сохраненное разрешение для этой папки. Изначально у вас его нет, поэтому вы спрашиваете разрешения у пользователя:
Чтобы запросить доступ, используйте startActivityForResult с Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).putExtra(DocumentsContract.EXTRA_INITIAL_URI, DocumentsContract.buildDocumentUri(«com.android.externalstorage.documents», «primary:Android»)) Здесь вы устанавливаете с помощью EXTRA_INITIAL_URI , что средство выбора должно запускаться непосредственно в папке Android в основном хранилище, потому что нам нужен доступ к папке Android. Когда ваше приложение будет нацелено на API30, средство выбора не позволит выбрать корень хранилища, а также, получив разрешение на папку Android, вы можете работать как с папками data , так и obb внутри, с одним запросом разрешения. .
Когда пользователь подтверждает двумя щелчками мыши, в onActivityResult вы получите Uri в данных, который должен быть content://com.android.externalstorage.documents/tree/primary%3AAndroid . Сделайте необходимые проверки, чтобы убедиться, что пользователь подтвердил правильность папки. Затем позвоните contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION) , чтобы сохранить разрешение, и все готово.
Итак, мы вернулись к ContentResolver.getPersistedUriPermissions() , который содержит список предоставленных разрешений (их может быть больше), тот, который вы предоставили выше, выглядит так: UriPermission
Теперь вы хотите работать с ContentResolver и DocumentsContract , используя этот «древовидный» uri и ваш относительный путь к файлам внутри папки Android. Вот пример для вывода списка папок data : data/ — это путь относительно предоставленного uri «дерева». Создайте окончательный uri, используя либо DocumentsContract.buildChildDocumentsUriUsingTree() (для вывода списка файлов), либо DocumentsContract.buildDocumentUriUsingTree() (для работы с отдельными файлами), например: DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri), DocumentsContract.getTreeDocumentId(treeUri)+»/data/») , вы получите uri = content://com.android.externalstorage.documents/tree/primary%3AAndroid/document/primary%3AAndroid%2Fdata%2F/children , подходящий для перечисление файлов в папке данных. Теперь вызовите ContentResolver.query(uri, . ) и обработайте данные в Cursor , чтобы получить список папок.
Подобным образом вы работаете с другими функциями SAF для чтения / записи / переименования / перемещения / удаления / создания, которые вы, вероятно, уже знаете, используя ContentResolver или методы DocumentsContract .
- ему не нужно android.permission.MANAGE_EXTERNAL_STORAGE
- он работает на целевом API 29 или 30
- он работает только с основным хранилищем, а не с внешними SD-картами
- для всех файлов внутри папки данных вам необходимо использовать SAF (файл java не будет работать), просто используйте иерархию файлов относительно папки Android
- в будущем Google может залатать эту дыру в своих намерениях «безопасности», и это может не сработать после некоторого обновления безопасности.
РЕДАКТИРОВАТЬ: образец кода, основанный на образце Github Cheticamp. В примере показано содержимое (и количество файлов) каждой из подпапок папки «Android»:
Имея ту же проблему, я использовал приведенный ниже код для создания новой папки / подпапки во внешнем хранилище. он также работает в Android 11 с устройством пикселей 2xl и пикселей 3
Проверьте это и дайте мне знать, если возникнут проблемы
Для этого не требуется никакого специального разрешения
андроид : requestLegacyExternalStorage = » истина»
В вашем файле манифеста и проверьте
Ну, я попробовал этот код, и он работает на Android API 29, Samsung Galaxy 20FE:
Я вызываю это с помощью кнопки onClick:
В пользовательском интерфейсе я нажимаю «показать внутреннюю память», перехожу в каталог Android и нажимаю «разрешить». После этого при отладке, если я попытаюсь перечислить все файлы под android, я получаю список всех каталогов в Data. Если это то, что вы ищете.
Источник
Хранение данных и файлов
В целом хранение файлов и данных можно условно разделить на две группы: во внутреннем или внешнем хранилище. Но разница между ними довольна тонка. В целом политика Гугла в отношение данных ужесточается с каждой версии системы.
Android поддерживает различные варианты хранения данных и файлов.
- Специфичные для приложения файлы. Доступ к файлам имеет только приложение, их создавшее. Файлы могут находиться во внутреннем и внешнем хранилище. У других приложений нет доступа (кроме случаев, когда файлы хранятся на внешнем хранилище). Методы getFilesDir(), getCacheDir(), getExternalFilesDir(), getExternalCacheDir(). Разрешений на доступ не требуется. Файлы удаляются, когда приложение удаляется пользователем.
- Разделяемое хранилище. Приложение может создавать файлы, которыми готово поделиться с другими приложениями — медиафайлы (картинки, видео, аудио), документы. Для медифайлов требуется разрешение READ_EXTERNAL_STORAGE или WRITE_EXTERNAL_STORAGE.
- Настройки. Хранение простых данных по принципу ключ-значение. Доступно внутри приложения. Реализовано через Jetpack Preferences. Настройки удаляются, когда приложение удаляется пользователем.
- Базы данных. Хранение данных в SQLite. На данный момент реализовано через библиотеку Room. Доступ только у родного приложения.
В зависимости от ваших потребностей, нужно выбрать нужный вариант хранения данных.
Следует быть осторожным при работе с внутренним и внешним хранилищем. Внутренне хранилище всегда есть в системе, но оно может быть не слишком большим по объёму. Вдобавок к внутреннему хранилищу, устройство может иметь внешнее хранилище. В старых моделях таким хранилищем выступала съёмная SD-карта. Сейчас чаще используют встроенную и недоступную для извлечения флеш-память. Если ваше приложение слишком большое, можно попросить систему устанавливать программу во внешнее хранилище, указав просьбу в манифесте.
В разных версиях Android требования к разрешению для работы с внешним хранилищем постоянно менялись. На данный момент (Android 10, API 29) требования выглядят следующим образом.
Приложение может иметь доступ к собственным файлам, которые находятся во внешнем хранилище. Также может получить доступ к определённым общим файлам на внешнем хранилище.
Доступ к общим файлам достигается через FileProvider API или контент-провайдеры.
Для просмотра файлов через студию используйте инструмент Device File Explorer.
Внешняя карта памяти
Когда появились первые устройства на Android, то практически у всех были внешние карточки памяти, которые вставлялись в телефон. Обычно там хранили фотки, видео и свои файлы. Всё было понятно — были различные методы для доступа к файловой системе. А потом началась чехарда. В телефонах также была и собственная «внешняя» память. Она вроде как и внешняя, но вставлена на заводе и вытащить её пользователь не мог, т.е. практически внутренняя. Затем пошла мода на телефоны, у которых была только такая внутреннее-внешняя карта. Пользователи поворчали, но привыкли. Сейчас встречаются оба варианта. Как правило, у телефонов с спрятанной картой больше памяти и выше степень водонепроницаемости.
Подобные фокусы с картой породили и другую проблему — Гугл озаботился безопасностью файлов и стала думать, как осложнить жизнь разработчику. С выходом каждой новой версии системы компания то давала добро на полный доступ к карточке, то ограничивала, то давала права с ограничениями, то откатывала свои решения назад. Короче, запутались сами и запутали всех.
Попробуем немного разобраться с этим зоопарком. Но помните, что процесс путаницы продолжается.
При подготовке материала я опирался на письма некоторых читателей сайта, которые присылали свои мысли по этому поводу. Спасибо им за структуризацию материала.
Вот что я (кажется) понял, попытавшись загрузить картинку с внешней 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, и это и есть настоящая внешняя карточка.
Нет стандартных средств добраться из приложения до файлов на внешней карточке. Все попытки добраться до настоящей внешней карточки делаются с помощью неких трюков. Самое интересное, что я нашел, это статья на http://futurewithdreams.blogspot.com/2014/01/get-external-sdcard-location-in-android.html — парень читает таблицу смонтированных устройств /proc/mounts, таблицу volume daemons /system/etc/vold.fstab, сравнивает их и выбирает те тома, которые оказываются съёмными (с помощью Environment.isExternalStorageRemovable()).
Оказалось, что несистемным приложениям в принципе запрещено напрямую обращаться к съёмной карточке! Похоже, что это было так всегда, но вот начиная с версии 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 позволяет работать с документом на карточке через диалог (открыть файл/сохранить файл), а вот прочитать или записать файл на карточке непосредственно из программы невозможно.
Общий вывод – реально из программы можно работать только с файлами на предоставляемой пользователю части встроенной памяти устройства, а на съёмной карточке – нет.
Это напоминает войну Microsoft с пользователями и разработчиками по поводу диска C:, компания уговаривала не устраивать беспорядок в корне этого диска, а ещё лучше — перенести свои файлы на другой диск. Но явных запретов не было.
Состояние на текущий момент
Гугл утверждает, что с версии 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» (см. выше).
Источник