Варианты использования хранилища Android и лучшие практики

Чтобы предоставить пользователям больше контроля над файлами и ограничить беспорядок в файлах, Android 10 представила новую парадигму хранения для приложений, называемую scoped storage . Scoped storage изменяет способ хранения и доступа приложений к файлам на внешнем хранилище устройства. Чтобы помочь вам перенести свое приложение для поддержки scoped storage, следуйте рекомендациям для распространенных вариантов использования хранилища, которые изложены в этом руководстве. Варианты использования разделены на две категории: обработка медиафайлов и обработка немедиафайлов .

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

Дополнительную информацию о хранении файлов и доступе к ним на Android можно найти в обучающих руководствах по хранению данных .

Обработка медиа-файлов

В этом разделе описываются некоторые распространенные варианты использования для обработки медиафайлов (видео, изображения и аудиофайлы) и объясняется высокоуровневый подход, который может использовать ваше приложение. В следующей таблице суммируются все эти варианты использования и ссылки на каждый из разделов, которые содержат более подробную информацию.

Вариант использования Краткое содержание
Показать все файлы изображений и видео Используйте один и тот же подход для всех версий Android.
Показывать изображения или видео из определенной папки Используйте один и тот же подход для всех версий Android.
Доступ к информации о местоположении из фотографий Используйте один подход, если ваше приложение использует хранилище с ограниченным доступом. Используйте другой подход, если ваше приложение отказывается от хранилища с ограниченным доступом.
Определите место хранения новых загрузок Используйте один подход, если ваше приложение использует хранилище с ограниченным доступом. Используйте другой подход, если ваше приложение отказывается от хранилища с ограниченным доступом.
Экспортировать пользовательские медиафайлы на устройство Используйте один и тот же подход для всех версий Android.
Изменение или удаление нескольких медиафайлов за одну операцию Используйте один подход для Android 11. Для Android 10 откажитесь от ограниченного хранилища и используйте вместо этого подход для Android 9 и более ранних версий.
Импортируйте одно изображение, которое уже существует Используйте один и тот же подход для всех версий Android.
Захват одного изображения Используйте один и тот же подход для всех версий Android.
Делитесь медиафайлами с другими приложениями Используйте один и тот же подход для всех версий Android.
Делитесь медиафайлами с помощью определенного приложения Используйте один и тот же подход для всех версий Android.
Доступ к файлам из кода или библиотек, использующих прямые пути к файлам Используйте один подход для Android 11. Для Android 10 откажитесь от ограниченного хранилища и используйте вместо этого подход для Android 9 и более ранних версий.

Показывать файлы изображений или видео из нескольких папок

Запросите коллекцию медиа с помощью API query() . Чтобы отфильтровать или отсортировать файлы медиа, настройте параметры projection , selection , selectionArgs и sortOrder .

Показывать изображения или видео из определенной папки

Используйте этот подход:

  1. Следуя рекомендациям, изложенным в разделе Запрос разрешений для приложений , запросите разрешение READ_EXTERNAL_STORAGE .
  2. Извлекать медиафайлы на основе значения MediaColumns.DATA , которое содержит абсолютный путь в файловой системе к медиаэлементу на диске.

Примечание: При доступе к существующему медиафайлу вы можете использовать значение столбца DATA в своей логике. Это потому, что это значение имеет допустимый путь к файлу. Однако не думайте, что файл всегда доступен. Будьте готовы обрабатывать любые ошибки ввода-вывода на основе файлов, которые могут возникнуть.

С другой стороны, для создания или обновления медиафайла не используйте столбец DATA . Вместо этого используйте столбцы DISPLAY_NAME и RELATIVE_PATH .

Доступ к информации о местоположении из фотографий

Если ваше приложение использует ограниченное хранилище, следуйте инструкциям в разделе «Информация о местоположении на фотографиях» руководства по хранению мультимедиа.

Определите место хранения новых загрузок

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

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

На устройствах Android 11 и более поздних версий файлы внутри внешнего каталога приложения недоступны для других приложений, даже если вы используете DownloadManager для загрузки этих файлов.

Экспортировать пользовательские медиафайлы на устройство

Определите правильное место по умолчанию для хранения пользовательских медиафайлов:

Изменение или удаление нескольких медиафайлов за одну операцию

Внедрите логику, основанную на версиях Android, на которых работает ваше приложение.

Работает на Android 11

Используйте этот подход:

  1. Создайте ожидающее намерение для запроса на запись или удаление вашего приложения с помощью MediaStore.createWriteRequest() или MediaStore.createTrashRequest() , а затем запросите у пользователя разрешение на редактирование набора файлов, вызвав это намерение.
  2. Оцените ответ пользователя:

    • Если разрешение было предоставлено, продолжите операцию изменения или удаления.
    • Если разрешение не было предоставлено, объясните пользователю, почему функция вашего приложения требует разрешения.

Узнайте больше о том, как управлять группами медиафайлов с помощью этих методов, доступных в Android 11 и более поздних версиях.

Работает на Android 10

Если ваше приложение предназначено для Android 10 (уровень API 29), откажитесь от использования хранилища с ограниченной областью действия и продолжайте использовать подход для Android 9 и более ранних версий для выполнения этой операции.

Работает на Android 9 или ниже

Используйте этот подход:

  1. Следуя рекомендациям, изложенным в разделе Запрос разрешений для приложений , запросите разрешение WRITE_EXTERNAL_STORAGE .
  2. Используйте API MediaStore для изменения или удаления медиафайлов.

Импортируйте одно изображение, которое уже существует

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

Представьте свой собственный пользовательский интерфейс

Используйте этот подход:

  1. Следуя рекомендациям, изложенным в разделе Запрос разрешений для приложений , запросите разрешение READ_EXTERNAL_STORAGE .
  2. Используйте API query() для запроса медиа-коллекции .
  3. Отображайте результаты в пользовательском интерфейсе вашего приложения.

Используйте системный селектор

Используйте намерение ACTION_GET_CONTENT , которое просит пользователя выбрать изображение для импорта.

Если вы хотите отфильтровать типы изображений, которые системный селектор предлагает пользователю на выбор, вы можете использовать setType() или EXTRA_MIME_TYPES .

Захват одного изображения

Если вы хотите захватить одно изображение для использования в вашем приложении (например, для использования в качестве фотографии для профиля пользователя), используйте намерение ACTION_IMAGE_CAPTURE , чтобы попросить пользователя сделать фотографию с помощью камеры устройства. Система сохраняет захваченную фотографию в таблице MediaStore.Images .

Делитесь медиафайлами с другими приложениями

Используйте метод insert() для добавления записей непосредственно в MediaStore. Для получения дополнительной информации см. раздел «Добавление элемента» руководства по хранению медиа.

Делитесь медиафайлами с помощью определенного приложения

Используйте компонент Android FileProvider , как описано в руководстве по настройке общего доступа к файлам .

Доступ к файлам из кода или библиотек, использующих прямые пути к файлам

Внедрите логику, основанную на версиях Android, на которых работает ваше приложение.

Работает на Android 11

Используйте этот подход:

  1. Следуя рекомендациям, изложенным в разделе Запрос разрешений для приложений , запросите разрешение READ_EXTERNAL_STORAGE .
  2. Доступ к файлам осуществляется с помощью прямых путей к файлам.

Более подробную информацию см. в разделе о том, как открывать медиафайлы с использованием прямых путей к файлам .

Работает на Android 10

Если ваше приложение предназначено для Android 10 (уровень API 29), откажитесь от использования хранилища с ограниченной областью действия и продолжайте использовать подход для Android 9 и более ранних версий для выполнения этой операции.

Работает на Android 9 или ниже

Используйте этот подход:

  1. Следуя рекомендациям, изложенным в разделе Запрос разрешений для приложений , запросите разрешение WRITE_EXTERNAL_STORAGE .
  2. Доступ к файлам осуществляется с помощью прямых путей к файлам.

Обработка файлов, не являющихся медиафайлами

В этом разделе описываются некоторые из распространенных вариантов использования для обработки немедиафайлов и объясняется высокоуровневый подход, который может использовать ваше приложение. В следующей таблице суммируются все эти варианты использования и ссылки на каждый из разделов, которые содержат более подробную информацию.

Вариант использования Краткое содержание
Открыть файл документа Используйте один и тот же подход для всех версий Android.
Запись в файлы на вторичных томах хранения Используйте один подход для Android 11. Используйте другой подход для более ранних версий Android.
Перенести существующие файлы из устаревшего места хранения Перенесите файлы в хранилище с ограниченным доступом, когда это возможно. Откажитесь от хранилища с ограниченным доступом для Android 10, когда это необходимо.
Делитесь контентом с другими приложениями Используйте один и тот же подход для всех версий Android.
Кэшировать немедийные файлы Используйте один и тот же подход для всех версий Android.
Экспорт файлов, не являющихся медиафайлами, на устройство Используйте один подход, если ваше приложение использует хранилище с ограниченным доступом. Используйте другой подход, если ваше приложение отказывается от хранилища с ограниченным доступом.

Открыть файл документа

Используйте намерение ACTION_OPEN_DOCUMENT , чтобы попросить пользователя выбрать файл для открытия с помощью системного средства выбора. Если вы хотите отфильтровать типы файлов, которые системное средство выбора будет представлять пользователю для выбора, вы можете использовать setType() или EXTRA_MIME_TYPES .

Например, вы можете найти все файлы PDF, ODT и TXT, используя следующий код:

Котлин

startActivityForResult(
        Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
            addCategory(Intent.CATEGORY_OPENABLE)
            type = "*/*"
            putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(
                    "application/pdf", // .pdf
                    "application/vnd.oasis.opendocument.text", // .odt
                    "text/plain" // .txt
            ))
        },
        REQUEST_CODE
      )

Ява

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
                "application/pdf", // .pdf
                "application/vnd.oasis.opendocument.text", // .odt
                "text/plain" // .txt
        });
        startActivityForResult(intent, REQUEST_CODE);

Запись в файлы на вторичных томах хранения

Вторичные тома хранения включают карты SD. Вы можете получить доступ к информации о данном томе хранения, используя класс StorageVolume .

Внедрите логику, основанную на версии Android, на которой работает ваше приложение.

Работает на Android 11

Используйте этот подход:

  1. Используйте модель хранилища с ограниченной областью действия .
  2. Ориентируйтесь на Android 10 (уровень API 29) или ниже.
  3. Объявите разрешение WRITE_EXTERNAL_STORAGE .
  4. Выполните один из следующих типов доступа:
    • Доступ к файлам с использованием API MediaStore .
    • Прямой доступ к пути к файлу с использованием API, таких как File или fopen() .

Работает на старых версиях

Используйте Storage Access Framework , который позволяет пользователям выбирать место на вторичном томе хранилища, куда ваше приложение может записать файл.

Перенести существующие файлы из устаревшего места хранения

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

Поддерживайте доступ к старому месту хранения для миграции данных.

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

Если ваше приложение предназначено для Android 11
  1. Установите для флага preserveLegacyExternalStorage значение true , чтобы сохранить устаревшую модель хранения и ваше приложение могло перенести данные пользователя при обновлении до новой версии вашего приложения, предназначенной для Android 11.

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

Если ваше приложение предназначено для Android 10

Откажитесь от использования хранилища с ограниченным доступом , чтобы упростить поддержку поведения вашего приложения в разных версиях Android.

Перенос данных приложения

Когда ваше приложение будет готово к миграции, используйте следующий подход:

  1. Android 10 или ниже.
  2. Откажитесь от использования ограниченного хранилища , чтобы ваше приложение имело доступ к файлам, которые необходимо перенести.
  3. Разверните код, который использует API File для перемещения файлов из их текущего местоположения в /sdcard/ в местоположение, доступное с помощью хранилища с ограниченной областью действия:

    1. Переместите все файлы частного приложения в каталог, возвращаемый методом getExternalFilesDir() .
    2. Переместите все общие файлы, не являющиеся медиафайлами, в подкаталог, предназначенный для приложения, в каталоге Downloads/ .
  4. Удалите устаревшие каталоги хранения вашего приложения из каталога /sdcard/ .

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

После того как пользователи перенесут свои данные, опубликуйте еще одно обновление вашего приложения, ориентированное на Android 11.

Делитесь контентом с другими приложениями

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

Кэшировать немедийные файлы

Выбор подхода зависит от типа файлов, которые необходимо кэшировать.

  • Небольшие файлы или файлы, содержащие конфиденциальную информацию : используйте Context#getCacheDir() .
  • Большие файлы или файлы, не содержащие конфиденциальной информации : используйте Context#getExternalCacheDir() .

Экспорт файлов, не являющихся медиафайлами, на устройство

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

Обработка файлов, специфичных для приложения

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

Внутренние каталоги хранения

Система не позволяет другим приложениям получать доступ к этим местоположениям, а на Android 10 (уровень API 29) и выше эти местоположения зашифрованы. Эти местоположения являются хорошим местом для хранения конфиденциальных данных, к которым может получить доступ только ваше приложение.

Внешние каталоги хранения

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

На устройствах с Android 4.4 (уровень API 19) и выше вашему приложению не нужно запрашивать какие-либо разрешения, связанные с хранилищем, для доступа к каталогам приложения во внешнем хранилище.

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

Временно отказаться от ограниченного хранения

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

Откажитесь от участия в тестах

На Android 10 (уровень API 29) и выше тесты вашего приложения по умолчанию выполняются в изолированной среде хранения. Эта изолированная среда не позволяет вашему приложению получать доступ к файлам за пределами каталога, специфичного для приложения, и общедоступных каталогов.

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

-e no-isolated-storage 1

Этот флаг влияет на все поведение инструментированного тестового случая и влияет на весь вызванный тестовый код. Поэтому при использовании этого флага вы не можете проверить совместимость своего приложения с хранилищем с областью действия. Для тестового вывода лучше вместо этого записывать в хранилище с областью действия приложения, которое может быть прочитано оболочкой. Затем вы можете извлечь этот каталог с областью действия приложения. Чтобы определить, из какого каталога извлекать, вызовите getExternalMediaDirs() .

Откажитесь от участия в вашем производственном приложении

Если ваше приложение нацелено на Android 10 (уровень API 29) или ниже, вы можете временно отказаться от хранилища scoped в вашем производственном приложении. Однако, если вы нацелены на Android 10, вам необходимо установить значение requestLegacyExternalStorage на true в файле манифеста вашего приложения:

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting
       Android 10. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Чтобы проверить, как приложение, ориентированное на Android 10 или ниже, ведет себя при использовании хранилища с ограниченной областью действия, вы можете включить это поведение, установив значение requestLegacyExternalStorage на false . Если вы проводите тестирование на устройстве под управлением Android 11, вы также можете использовать флаги совместимости приложений , чтобы проверить поведение вашего приложения с хранилищем с ограниченной областью действия или без него.

Дополнительные ресурсы

Дополнительную информацию о хранилище Android можно найти в следующих материалах:

Записи в блоге