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

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

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

Чтобы узнать больше о том, как хранить файлы и получать к ним доступ на 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. Разверните код, который использует File API для перемещения файлов из их текущего местоположения в /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) или ниже, вы можете временно отказаться от хранилища с ограниченной областью действия в рабочем приложении. Однако если вы ориентируетесь на 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 просмотрите следующие материалы:

Сообщения в блоге