Новые способы работы с файлами в ваших приложениях Android для Android 10

С выпуском Android 10 (уровень API 29 или Q) многое изменилось в Android SDK, особенно с точки зрения безопасности, что хорошо, но продолжает развиваться. на каждом этапе эволюции всегда есть плюсы и минусы, и так получилось, что на этот раз разработчики встретили множество споров о минусах. Объем хранилища в android 10 заставляет разработчиков использовать Storage Access Framework почти для всех операций с пользовательскими файлами, игнорируя java.io.file API, потому что это не очень безопасно. Это привело к появлению длинного списка уже опубликованных приложений, которые использовали API java.io.file для миграции.

С Scoped Storage все становится одновременно более ограниченным и простым. Совместимое приложение получает специальную папку для пользовательских данных. У приложений есть собственная папка с песочницей для хранения необходимых файлов, которая недоступна для других приложений. Scoped Storage дает возможность создать вторую папку для файлов, которые создает приложение, мы увидим, как это делается с помощью API поставщика документов, в следующих разделах.

В следующих разделах мы увидим, как можно получить доступ к хранилищу с правильными разрешениями и реализациями, мы также рассмотрим устаревшие переменные и посмотрим, какие альтернативы можно использовать с Scoped Storage, и, наконец, мы увидим, как вы все еще можете использовать java. io.file API в Android 10, если Storage Access Framework не соответствует вашим потребностям практически или оптимально.

Архитектура хранилища приложений

Хранилище для конкретного приложения: оно состоит из выделенных каталогов на внутреннем хранилище, в основном внешнем хранилище или на дополнительных внешних носителях данных устройства. В этих каталогах хранятся файлы, которые предназначены только для использования вашим приложением и не являются доступны другим приложениям на устройстве. Для доступа к ним не требуется разрешения, и содержимое этих каталогов стирается при удалении приложения. Ниже приведены методы, возвращающие каталоги хранилища для конкретных приложений.

getFilesDir (): возвращает каталог для конкретного приложения во внутренней памяти вашего устройства. Файлы в этом каталоге удаляются после удаления приложения. Подробнее об этом здесь .

getExternalFilesDir ( String type ): возвращает каталог приложения на основном внешнем хранилище вашего устройства, файлы здесь также удаляются после удаления. . Подробнее об этом здесь.

getCacheDir (): возвращает каталог кэша для конкретного приложения во внутренней памяти вашего устройства. Файлы, хранящиеся здесь, могут быть удалены, когда на устройстве мало памяти, и они полностью удаляется после удаления приложения.

getExternalCacheDir (): возвращает каталог кэша для конкретного приложения на основном внешнем хранилище вашего устройства, все для getCacheDir () также применяется здесь.

Примечание: основное внешнее хранилище - это несъемное внешнее хранилище устройства, а вторым внешним хранилищем обычно является съемная SD-карта. Если на устройстве нет основного внешнего носителя данных, SD-карта используется автоматически. в качестве основного внешнего хранилища на большинстве устройств, и метод Environment.getExternalStorageDirectory () вернет любой из этих двух путей к носителю, но на данный момент этот метод устарел, и доступ к внешнему хранилищу теперь выполнен с помощью средства выбора системных файлов через намерения.

Общее хранилище: в этих каталогах хранятся файлы, которые приложения собираются предоставить другим приложениям, включая мультимедиа, документы и другие файлы. Общие каталоги хранилища включают Загрузки, Изображения, Фильмы и DCIM. папки на любом из внешних носителей данных устройства, для доступа к ним не требуются разрешения, и доступ к ним осуществляется с помощью средства выбора системных файлов, примеры кода для этого можно увидеть здесь.

Существуют и другие средства хранения приложений, такие как SharedPreferences и базы данных SQLite, но это не наша тема в этой статье.

Доступ к медиафайлам

В мультимедийных файлах Android, которые включают видео, аудио и изображения, можно получить доступ с помощью API MediaStore, но перед этим вашему приложению потребуется одно из двух разрешений: Read_External_Storage и Write_External_Storage, вам следует запросить эти разрешения в файле Manifest.xml приложения, а также во время выполнения, желательно при первом запуске вашего приложения, здесь - это документированное руководство по запросу разрешений во время выполнения.

До версии android 10 MediaStore.Audio.Media.DATA, MediaStore.Video.Media.DATA и MediaStore.Images.Media.DATA, где переменные обычно используется для прямого доступа к мультимедийным файлам с помощью java.io.file api, но с появлением Android 10 и принудительным внедрением хранилища с ограниченным объемом эти переменные теперь устарели, на данный момент при выполнении запроса в хранилище мультимедиа для медиафайлов вы должны создать URI контента, который даст вам доступ к файлу.

Чтобы получить contentUri файла изображения при выполнении запроса в MediaStore, мы должны использовать MediaStore.Images.Media. EXTERNAL_CONTENT_URI или MediaStore.Images.Media. INTERNAL_CONTENT_URI для внутреннего содержания и MediaStore.Images.Media. _ID файла изображения. сам ContentUri создается, как показано ниже.

int id = cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID));<Uri contentUri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));

затем вы можете использовать этот ContentUri для доступа к файлу изображения с помощью объекта AssertFileDescriptor

try {
    AssetFileDescriptor file = context.getContentResolver().openAssetFileDescriptor(contentUri, "r"); catch (FileNotFoundException e) {
e.printStackTrace();
}

вы также можете использовать ContentUri напрямую для загрузки изображения в ImageView с помощью Glide

ImageView picture = findViewById(R.id.picture);
Glide.with(context).load(contentUri)        
.apply(new RequestOptions().placeholder(R.drawable.ic_mediafacer).centerCrop())        .into(picture);

Процедура получения contentUri для аудио- и видеофайлов точно такая же, как и выше, при использовании их соответствующих классов, после чего ваше приложение теперь может напрямую обращаться к медиафайлам.

Использование DocumentFile и FileSystem Picker для выполнения файловой операции на внешнем хранилище

С прекращением поддержки метода Environment.getExternalStorageDirectory () и принятием ограниченного хранилища у разработчиков не остается другого выбора, кроме как использовать поставщик документов и средство выбора файловой системы для выполнения операций с файлами на внешних носителях, таких как в качестве SDCard и других внешних поставщиков файлов, таких как Google Диск.

Основная причина, по которой разработчики неодобрительно отзывались о принудительном внедрении Storage Access Framework, была связана с проблемами производительности при ее использовании, вы быстро видите, что с такой записью на reddit androiddev « Ужасная производительность инфраструктуры доступа к хранилищу »Помимо производительности там, где к этому привели и другие факторы, многие другие провайдеры файлов, такие как dropbox, onedrive и многие библиотеки и API, которые работают с файлами, не поддерживают Storage Access Framework, ко всему этому добавлен код для его реализации. громоздкий и требует довольно небольшой настройки, поэтому многие разработчики хотят вернуться к api java.io.file.

Чтобы получить доступ к файлам SD-карты, ваше приложение должно запросить разрешение на это, желательно при первом запуске, это делается путем запуска намерения с действием ACTION_OPEN_DOCUMENT_TREE и кодом намерения по вашему выбору, это откроет средство выбора файловой системы, в котором вы затем выбираете расположение SD-карты

Когда приведенный выше код будет выполнен, вы должны увидеть изображения, похожие на это

После выбора sdcard предоставляется разрешение на запись, после чего ваше приложение теперь может получать доступ к файлам sdcard с помощью DocumentFile и selectedDir, который является Uri с местоположением SD-карты, обычно рекомендуется сохранять PickedDir в sharedPreferences и извлекать его каждый раз при использовании, см. Код ниже.

public void writeFile(DocumentFile pickedDir) {
try {
DocumentFile picFile = pickedDir.createFile("image/jpeg","picture.jpg");        OutputStream out = getContentResolver().openOutputStream(file.getUri());        try {
// write the image content
} finally {
out.close();
}
} catch (IOException e) {
throw new RuntimeException("Something went wrong : " + e.getMessage(), e);
}
}

Чтобы получать файлы, клиентские приложения должны активировать намерения ACTION_CREATE_DOCUMENT или ACTION_OPEN_DOCUMENT. Намерения могут включать дополнительную информацию для фильтрации файлов, например получение файлов определенного типа путем указания типа mime, ниже приведен пример кода для выполнения операций с файлами в отношении Storage Access Framework.

Наконец, в последней версии Android 10 приложения могут иметь обычное или устаревшее хранилище, что означает, что все ведет себя так же, как в Android 4.4–9.0, что также позволяет использовать Java.io.File api, чтобы использовать устаревшее хранилище, вы должны установить для requestLegacyExternalStorage в разделе Appliction вашего Manifest.xml значение «True».

android: requestLegacyExternalStorage = "true"

После этого вы можете продолжить работу с хранилищем, как это было до Android 10.

Адаптируясь к обновлениям, мы с нетерпением ждем изменений в следующих выпусках Android.

Больше подобных статей можно найти в моем блоге Техника Android.