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

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

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

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

Фоновый доступ к местоположению: пользователи должны быть направлены в системные настройки, чтобы предоставить приложениям разрешение на фоновое определение местоположения.

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

Недавно я глубоко погрузился в концепцию Scoped storage, чтобы понять, чего ожидать в будущем, и соответственно подготовить свое Android-приложение к переменам.

Структура хранилища (до Android 10)

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

  • Частное хранилище (Private Storage): все приложения имеют собственный частный каталог во внутреннем хранилище, то есть Android/data/{имя пакета}, невидимый для других приложений.
  • Общее хранилище (Shared Storage): остальная часть хранилища, помимо частных разделов, называлась общим хранилищем. Оно включает в себя все медиа- и немедийные файлы, сохраненные в системе, и приложение с разрешением на хранение может легко получить к ним доступ.

Проблемы с текущим состоянием структуры и доступа

Есть ли, на ваш взгляд, какие-то проблемы в структуре, которую мы рассмотрели выше? Не хотелось ли вам, чтобы Google что-нибудь здесь изменил?

Давайте подробнее остановимся на некоторых проблемах.

  • Допустим, у нас приложение для электронной коммерции, которому нужен доступ к хранилищу только для того, чтобы пользователь смог загрузить фото профиля. Значит, ему нужно предоставить доступ к определенному файлу или файловой системе. Другой пример: нам нужно загрузить или сохранить медиа-файл из чат-приложения, а для этого нужен доступ к одному медиа-файлу. Так зачем же предоставлять доступ к хранилищу целиком?
  • Может оказаться так, что конфиденциальные данные, такие как медицинские рецепты или банковские чеки, будут доступны всем приложениям, установленным на устройстве.
  • После удаления приложения нет возможности убедиться, что все связанные с ним данные и файлы полностью очищены.

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

Хранилище с ограниченной областью видимости

Идея

Google приводит две веские причины, по которым вводится это изменение: безопасность и уменьшение оставшихся после удаления приложений данных.

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

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

  • Улучшенная атрибуция: приложению будет предоставлен доступ к блокам хранения, которые содержат соответствующие данные приложения.

Посмотрите на структуру хранения данных в Android 10 и выше:

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

  • Уменьшение беспорядка в файлах: система свяжет хранилище с приложениями-владельцами, чтобы системе было легче находить файлы, относящиеся к приложению. Таким образом, при удалении приложения все связанные с ним данные также будут удалены.
  • Предотвращение злоупотребления разрешением READ_EXTERNAL_STORAGE: предоставление этого разрешения на сегодняшний день дает приложению доступ ко всему внешнему хранилищу, где хранятся личные фотографии, документы, видео и другие потенциально конфиденциальные файлы. Однако при применении Scoped storage приложения смогут видеть только собственные папки данных и определенные типы носителей, такие как музыкальные файлы, используя другие API хранилища.

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

Изменения в разрешениях доступа к файлам

  • Неограниченный доступ к индивидуальному хранилищу приложений: приложение будет иметь неограниченный доступ к внутреннему и внешнему хранилищу как для чтения, так и для записи. В Android 10 не нужно предоставлять разрешение на хранение для записи файлов в каталог приложений на SD-карте.
  • Неограниченный доступ к коллекциям медиафайлов и загрузок (добавленным собственноручно): вы получаете неограниченный доступ для добавления файлов в коллекции и загрузки приложения. При сохранении изображения, видео или любого другого медиафайла из коллекции не нужно запрашивать разрешение (если файл хранится в организованной коллекции.)
  • Доступ к коллекции медиафайлов других приложений можно получить с помощью разрешения READ_STORAGE_PERMISSION. Разрешение WRITE_STORAGE_PERMISSION со следующей версии станет устаревшим и будет работать так же, как READ_STORAGE_PERMISSION.
  • Для записи и чтения немедийных файлов, добавляемых другими приложениями, понадобятся API-интерфейсы доступа к хранилищу.

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

Отказ от изменений для приложений на Android 10 и выше

Информация с сайта разработчиков Android:

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

Согласно “Что такое Scoped Storage в Android 11?”:

Любое приложение, предназначенное для Android 11 или более поздней версии, должно использовать новые API хранилища, включая хранилище с ограниченной областью видимости. Изменения в соглашении разработчика Google Play гласят, что, начиная с 1 августа 2020 года, все новые приложения, представленные в Google Play, должны быть нацелены на Android 10 или более позднюю версию, а все обновления существующих приложений должны быть ориентированы на Android 10 или более позднюю версию с 1 ноября 2020 года. Если все продолжится в том же духе, то в следующем году приложения, скорее всего, будет обязательно ориентировать уже на Android 11.

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

Операции хранения

Кратко рассмотрим некоторые часто выполняемые операции хранения и способы их выполнения:

  • Выбор файла: используйте ACTION_OPEN_DOCUMENT  —  он открывает системное приложение для выбора файлов, предоставляя пользователю возможность выбрал файл, который нужно открыть. Чтобы отобразить только те типы файлов, которые поддерживаются приложением, укажите тип MIME.
// Код запроса для выбора PDF-документа.
const val PICK_PDF_FILE = 2

fun openFile(pickerInitialUri: uri) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"

        // При необходимости укажите URI для файла, который должен появиться в системном средстве выбора файлов при его загрузке. 
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, PICK_PDF_FILE)
}
fun openDirectory(pickerInitialUri: Uri) {
    // Выберите папку с помощью системного средства выбора файлов
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
        // Предоставьте доступ на чтение к файлам и подпапкам в выбранной папке
        flags = Intent.FLAG_GRANT_READ_URI_PERMISSION

        // При необходимости укажите URI для папки, которая должна быть открыта в системном средстве выбора файлов при его загрузке.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, your-request-code)
}

Примечание: этот доступ будет действителен до тех пор, пока пользователь не перезагрузит устройство. Если приложение хочет сохранить доступ, получая доступ к URI с помощью преобразователя содержимого, преобразователь содержимого должен вызвать метод takePersistableUriPermission.

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

  • Создание файла: чтобы сохранить файл в определенном месте, используйте ACTION_CREATE_DOCUMENT.

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

// Код запроса для создания PDF-документа.
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // При необходимости укажите URI для каталога, который должен быть открыт в системном средстве выбора файлов, прежде чем ваше приложение создаст документ.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

Разрешение на определение локации медиа-файлов

Как говорится в документации разработчика Android:

Если приложение предназначено для Android 10 (уровень API 29) или выше, то для того, чтобы оно могло извлекать неотредактированные метаданные Exif из фотографий, необходимо объявить разрешение ACCESS_MEDIA_LOCATION в манифесте приложения, а затем запросить это разрешение во время выполнения.

<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>

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

Приложения-менеджеры файлов

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

Чтобы приложение запрашивало у пользователя “доступ ко всем файлам”, нужно выполнить следующие действия:
1. Объявить разрешение MANAGE_EXTERNAL_STORAGE в манифесте.
2. С помощью действия ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION направить пользователей на страницу системных настроек, где они могут включить следующую опцию: “Разрешить доступ для управления всеми файлами”.

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

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

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

Приложения с кастомным средством выбора файлов

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

Что нужно помнить во время реализации

  1. Не используйте статический путь. Заблокируйте доступ к пути к файлу.
  2. Используйте медиахранилище (рекомендуется).
  3. Медиахранилище следует использовать правильно. Например, не помещайте музыкальные файлы в каталог изображений.
  4. Немедийные файлы должны находиться в каталоге загрузки (рекомендуется).
  5. Чтобы предоставить другим приложениям доступ к немедийным файлам, воспользуйтесь системным выбором файлов вместе с SAF (storage access framework). Для полного доступа к этому приложению будет запрошено разрешение среды выполнения.

При реализации можно воспользоваться примером на Github.

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Niharika Arora: Android Scoped Storage Demystified

Предыдущая статьяКак я устроил пожизненный запас чесночных пицца-палочек с помощью Python и Selenium
Следующая статьяКак добавить в проект тестирование скриншотов с Cypress