Все последние улучшения в 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)
}
- Выбор папки: Интент
ACTION_OPEN_DOCUMENT
можно заменить наACTION_OPEN_DOCUMENT_TREE
.
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 и получите место в утвержденном списке.
Приложения с кастомным средством выбора файлов
Что делать, если приложение задействует кастомный выбор файлов, который отображает точные каталоги данных? В этом случае ничего нельзя сделать. Возможно, стоит перейти на системное средство выбора файлов.
Что нужно помнить во время реализации
- Не используйте статический путь. Заблокируйте доступ к пути к файлу.
- Используйте медиахранилище (рекомендуется).
- Медиахранилище следует использовать правильно. Например, не помещайте музыкальные файлы в каталог изображений.
- Немедийные файлы должны находиться в каталоге загрузки (рекомендуется).
- Чтобы предоставить другим приложениям доступ к немедийным файлам, воспользуйтесь системным выбором файлов вместе с SAF (storage access framework). Для полного доступа к этому приложению будет запрошено разрешение среды выполнения.
При реализации можно воспользоваться примером на Github.
Читайте также:
- Выполнение AES/GCM в Android
- Области видимости в Android и Hilt
- Корутины Kotlin: как работать асинхронно в Android
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Niharika Arora: Android Scoped Storage Demystified