Google Play требует, чтобы размер загружаемого пользователями сжатого APK-файла не превышал 100 МБ. Для большинства приложений этого достаточно для всего кода и ресурсов приложения. Однако некоторым приложениям требуется больше места для высококачественной графики, медиафайлов или других больших ресурсов. Ранее, если размер загружаемого сжатого файла вашего приложения превышал 100 МБ, вам приходилось самостоятельно размещать и загружать дополнительные ресурсы при открытии приложения пользователем. Размещение и предоставление дополнительных файлов может быть дорогостоящим, а пользовательский опыт часто оставляет желать лучшего. Чтобы упростить этот процесс для вас и сделать его более приятным для пользователей, Google Play позволяет вам прикрепить два больших файла расширения, которые дополняют ваш APK-файл.
Google Play размещает файлы расширения для вашего приложения и предоставляет их устройству бесплатно. Файлы расширения сохраняются в общем хранилище устройства (SD-карта или раздел, монтируемый на USB-накопитель; также известный как «внешнее» хранилище), где ваше приложение может получить к ним доступ. На большинстве устройств Google Play загружает файлы расширения одновременно с загрузкой APK-файла, поэтому ваше приложение будет иметь все необходимое, когда пользователь откроет его в первый раз. Однако в некоторых случаях вашему приложению необходимо загрузить файлы из Google Play при запуске.
Если вы хотите избежать использования файлов расширения, и размер сжатого файла загрузки вашего приложения превышает 100 МБ, вам следует загрузить приложение с помощью Android App Bundles , который позволяет загружать сжатые файлы размером до 500 МБ. Кроме того, поскольку использование пакетов приложений откладывает генерацию и подпись APK-файлов в Google Play, пользователи загружают оптимизированные APK-файлы, содержащие только необходимый код и ресурсы для запуска вашего приложения. Вам не нужно создавать, подписывать и управлять несколькими APK-файлами или файлами расширения, а пользователи получают более компактные и оптимизированные файлы для загрузки.
Обзор
При каждой загрузке APK-файла через Google Play Console у вас есть возможность добавить один или два дополнительных файла. Каждый файл может иметь размер до 2 ГБ и любой формат на ваш выбор, но мы рекомендуем использовать сжатый файл для экономии трафика во время загрузки. Концептуально каждый дополнительный файл выполняет свою роль:
- Основной файл расширения — это главный файл расширения для дополнительных ресурсов, необходимых вашему приложению.
- Дополнительный файл патча является необязательным и предназначен для небольших обновлений основного файла расширения.
Хотя вы можете использовать два дополнительных файла по своему усмотрению, мы рекомендуем, чтобы основной дополнительный файл содержал основные ресурсы и обновлялся редко, если вообще обновляется; дополнительный файл с патчами должен быть меньше по размеру и служить «носителем патчей», обновляясь с каждым крупным релизом или по мере необходимости.
Однако, даже если для обновления вашего приложения требуется только новый файл расширения, вам все равно необходимо загрузить новый APK-файл с обновленным versionCode в манифесте. (Play Console не позволяет загружать файл расширения в существующий APK-файл.)
Примечание: файл расширения патча семантически идентичен основному файлу расширения — вы можете использовать каждый файл по своему усмотрению.
формат имени файла
Каждый загружаемый вами файл расширения может быть в любом выбранном вами формате (ZIP, PDF, MP4 и т. д.). Вы также можете использовать инструмент JOBB для инкапсуляции и шифрования набора файлов ресурсов и последующих патчей для этого набора. Независимо от типа файла, Google Play рассматривает их как непрозрачные двоичные блоки и переименовывает файлы, используя следующую схему:
[main|patch].<expansion-version>.<package-name>.obb
Данная схема состоит из трех компонентов:
-
mainилиpatch - Указывает, является ли файл основным или файлом расширения для патчей. Для каждого APK-файла может быть только один основной файл и один файл патчей.
-
<expansion-version> - Это целое число, соответствующее коду версии APK-файла, с которым впервые связывается расширение (оно совпадает со значением
android:versionCodeприложения).Слово «Первый» выделено потому, что, хотя Play Console позволяет повторно использовать загруженный файл расширения с новым APK-файлом, имя файла расширения не меняется — он сохраняет версию, примененную к нему при первой загрузке файла.
-
<package-name> - Имя пакета вашего приложения в стиле Java.
Например, предположим, что версия вашего APK-файла — 314159, а имя пакета — com.example.app. Если вы загрузите основной файл расширения, он будет переименован следующим образом:
main.314159.com.example.app.obb
Место хранения
Когда Google Play загружает файлы расширения на устройство, он сохраняет их в общем хранилище системы. Для обеспечения корректной работы нельзя удалять, перемещать или переименовывать файлы расширения. В случае, если вашему приложению необходимо самостоятельно загрузить файлы из Google Play, необходимо сохранить их в том же самом месте.
Метод getObbDir() возвращает конкретное местоположение файлов расширения в следующем формате:
<shared-storage>/Android/obb/<package-name>/
-
<shared-storage>— это путь к общему пространству хранения, доступному изgetExternalStorageDirectory(). -
<package-name>— это имя пакета вашего приложения в стиле Java, доступное изgetPackageName().
Для каждого приложения в этом каталоге никогда не бывает более двух файлов расширения. Один — это основной файл расширения, а другой — файл расширения для исправлений (если необходимо). Предыдущие версии перезаписываются при обновлении приложения новыми файлами расширения. Начиная с Android 4.4 (уровень API 19), приложения могут читать файлы расширения OBB без разрешения на доступ к внешнему хранилищу. Однако некоторые реализации Android 6.0 (уровень API 23) и более поздних версий по-прежнему требуют разрешения, поэтому вам потребуется объявить разрешение READ_EXTERNAL_STORAGE в манифесте приложения и запросить разрешение во время выполнения следующим образом:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
Для Android версии 6 и выше разрешение на использование внешнего хранилища необходимо запрашивать во время выполнения. Однако некоторые реализации Android не требуют разрешения на чтение OBB-файлов. Следующий фрагмент кода показывает, как проверить наличие доступа на чтение перед запросом разрешения на использование внешнего хранилища:
Котлин
val obb = File(obb_filename) var open_failed = false try { BufferedReader(FileReader(obb)).also { br -> ReadObbFile(br) } } catch (e: IOException) { open_failed = true } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission() }
Java
File obb = new File(obb_filename); boolean open_failed = false; try { BufferedReader br = new BufferedReader(new FileReader(obb)); open_failed = false; ReadObbFile(br); } catch (IOException e) { open_failed = true; } if (open_failed) { // request READ_EXTERNAL_STORAGE permission before reading OBB file ReadObbFileWithPermission(); }
Если вам необходимо распаковать содержимое файлов расширения, не удаляйте файлы расширения OBB после этого и не сохраняйте распакованные данные в той же директории. Распакованные файлы следует сохранять в директории, указанной функцией getExternalFilesDir() . Однако, по возможности, лучше использовать формат файлов расширения, позволяющий читать данные непосредственно из файла, а не распаковывать их. Например, мы предоставили библиотечный проект под названием APK Expansion Zip Library , который считывает данные непосредственно из ZIP-файла.
Внимание: В отличие от APK-файлов, любые файлы, сохраненные в общем хранилище, могут быть прочитаны пользователем и другими приложениями.
Совет: Если вы упаковываете медиафайлы в ZIP-архив, вы можете использовать вызовы воспроизведения медиафайлов с элементами управления смещением и длиной (например, MediaPlayer.setDataSource() и SoundPool.load() ) без необходимости распаковывать ZIP-архив. Для этого необходимо избегать дополнительного сжатия медиафайлов при создании ZIP-архивов. Например, при использовании инструмента zip следует использовать параметр -n для указания суффиксов файлов, которые не следует сжимать:
zip -n .mp4;.ogg main_expansion media_files
Процесс загрузки
В большинстве случаев Google Play загружает и сохраняет файлы расширения одновременно с загрузкой APK-файла на устройство. Однако в некоторых случаях Google Play не может загрузить файлы расширения, или пользователь мог удалить ранее загруженные файлы расширения. Для обработки таких ситуаций ваше приложение должно иметь возможность самостоятельно загружать файлы при запуске основной активности, используя URL-адрес, предоставленный Google Play.
Процесс загрузки на высоком уровне выглядит следующим образом:
- Пользователь выбирает установить ваше приложение из Google Play.
- Если Google Play может загрузить файлы расширения (что происходит на большинстве устройств), он загружает их вместе с APK-файлом.
Если Google Play не может загрузить файлы расширения, он загружает только APK-файл.
- Когда пользователь запускает ваше приложение, оно должно проверить, сохранены ли уже файлы расширения на устройстве.
- Если да, то ваше приложение готово к запуску.
- В противном случае, ваше приложение должно загрузить файлы расширения по протоколу HTTP из Google Play. Ваше приложение должно отправить запрос клиенту Google Play, используя службу лицензирования приложений Google Play, которая ответит именем, размером файла и URL-адресом каждого файла расширения. Имея эту информацию, вы затем загружаете файлы и сохраняете их в соответствующем месте хранения .
Внимание: крайне важно включить необходимый код для загрузки файлов расширения из Google Play на случай, если эти файлы еще не установлены на устройстве при запуске приложения. Как обсуждалось в следующем разделе о загрузке файлов расширения , мы предоставили вам библиотеку, которая значительно упрощает этот процесс и выполняет загрузку из сервиса с минимальным количеством кода с вашей стороны.
Контрольный список разработки
Вот краткое описание задач, которые необходимо выполнить для использования файлов расширения в вашем приложении:
- Сначала определите, нужно ли вашему приложению загружать сжатый файл размером более 100 МБ. Место на диске ценно, поэтому общий размер загружаемого файла должен быть как можно меньше. Если ваше приложение использует более 100 МБ для предоставления нескольких версий графических ресурсов для разных плотностей экрана, рассмотрите возможность публикации нескольких APK-файлов, каждый из которых содержит только ресурсы, необходимые для целевых экранов. Для достижения наилучших результатов при публикации в Google Play загрузите Android App Bundle , который включает весь скомпилированный код и ресурсы вашего приложения, но откладывает генерацию и подпись APK-файла в Google Play.
- Определите, какие ресурсы приложения следует отделить от APK-файла, и упакуйте их в файл, который будет использоваться в качестве основного файла расширения.
Обычно при обновлении основного файла расширения следует использовать только второй файл расширения. Однако, если ваши ресурсы превышают лимит в 2 ГБ для основного файла расширения, вы можете использовать файл расширения для остальных ресурсов.
- Разработайте приложение таким образом, чтобы оно использовало ресурсы из файлов расширения, расположенных в общей памяти устройства.
Помните, что удалять, перемещать или переименовывать файлы расширения категорически запрещено.
Если ваше приложение не требует определенного формата, мы рекомендуем создать ZIP-архивы для файлов расширения, а затем прочитать их с помощью библиотеки APK Expansion Zip Library .
- Добавьте в основное окно приложения логику, которая проверяет наличие файлов расширения на устройстве при запуске. Если файлов нет на устройстве, используйте службу лицензирования приложений Google Play, чтобы запросить URL-адреса файлов расширения, а затем загрузите и сохраните их.
Чтобы значительно сократить объем необходимого кода и обеспечить удобство использования во время загрузки, мы рекомендуем использовать библиотеку Downloader для реализации процесса загрузки.
Если вы создадите собственный сервис загрузки вместо использования библиотеки, имейте в виду, что вы не должны изменять имена файлов расширения и должны сохранять их в соответствующем месте хранения .
После завершения разработки приложения следуйте инструкциям по тестированию файлов расширения .
Правила и ограничения
Добавление файлов расширения APK — это функция, доступная при загрузке приложения через Play Console. При первой загрузке приложения или обновлении приложения, использующего файлы расширения, необходимо учитывать следующие правила и ограничения:
- Размер каждого файла расширения не должен превышать 2 ГБ.
- Для загрузки файлов расширения из Google Play пользователь должен был получить ваше приложение из Google Play . Google Play не предоставит URL-адреса файлов расширения, если приложение было установлено другим способом.
- При загрузке файлов из вашего приложения URL-адрес, предоставляемый Google Play для каждого файла, является уникальным для каждой загрузки, и срок действия каждого из них истекает вскоре после его предоставления вашему приложению.
- Если вы обновляете приложение с помощью нового APK-файла или загружаете несколько APK-файлов для одного и того же приложения, вы можете выбрать файлы расширения, которые вы загружали для предыдущего APK-файла. Имя файла расширения не меняется — оно сохраняет версию, полученную APK-файлом, с которым этот файл был первоначально связан.
- Если вы используете файлы расширения в сочетании с несколькими APK-файлами для предоставления разных файлов расширения для разных устройств, вам все равно необходимо загружать отдельные APK-файлы для каждого устройства, чтобы указать уникальное значение
versionCodeи объявить разные фильтры для каждого APK-файла. - Вы не можете обновить приложение, изменив только файлы расширения — для обновления приложения необходимо загрузить новый APK-файл . Если ваши изменения касаются только ресурсов в файлах расширения, вы можете обновить APK-файл, просто изменив
versionCode(и, возможно, такжеversionName). - Не сохраняйте другие данные в каталог
obb/. Если вам необходимо распаковать какие-либо данные, сохраните их в место, указанное функциейgetExternalFilesDir(). - Не удаляйте и не переименовывайте файл расширения
.obb(если только вы не выполняете обновление). Это приведет к тому, что Google Play (или само ваше приложение) будет повторно загружать файл расширения. - При ручном обновлении файла расширения необходимо удалить предыдущий файл расширения.
Загрузка файлов расширения
В большинстве случаев Google Play загружает и сохраняет файлы расширения на устройство одновременно с установкой или обновлением APK-файла. Таким образом, файлы расширения доступны при первом запуске вашего приложения. Однако в некоторых случаях вашему приложению необходимо самостоятельно загрузить файлы расширения, запросив их по URL-адресу, предоставленному вам в ответе от службы лицензирования приложений Google Play.
Для загрузки файлов расширения вам потребуется следующая основная логика:
- При запуске приложения найдите файлы расширения в общем хранилище (в каталоге
Android/obb/<package-name>/).- Если файлы расширения присутствуют, значит, все в порядке, и ваше приложение может продолжать работу.
- Если файлы расширения отсутствуют :
- Отправьте запрос через раздел лицензирования приложений Google Play, чтобы получить имена, размеры и URL-адреса файлов расширения вашего приложения.
- Используйте URL-адреса, предоставленные Google Play, для загрузки и сохранения файлов расширения. Файлы необходимо сохранить в общую папку (
Android/obb/<package-name>/) и использовать точное имя файла, указанное в ответе Google Play.Примечание: URL-адрес, предоставляемый Google Play для файлов расширения, уникален для каждой загрузки, и срок действия каждого из них истекает вскоре после его предоставления вашему приложению.
Если ваше приложение бесплатное (не платное), то вы, вероятно, не использовали сервис лицензирования приложений . Он предназначен в первую очередь для обеспечения соблюдения лицензионных правил вашего приложения и гарантии того, что пользователь имеет право использовать ваше приложение (он законно заплатил за него в Google Play). Для упрощения работы с файлами расширения сервис лицензирования был усовершенствован и теперь предоставляет вашему приложению ответ, включающий URL-адрес файлов расширения вашего приложения, размещенных в Google Play. Таким образом, даже если ваше приложение бесплатное для пользователей, вам необходимо включить библиотеку проверки лицензий (LVL) для использования файлов расширения APK. Конечно, если ваше приложение бесплатное, вам не нужно принудительно проверять лицензии — вам просто нужно, чтобы библиотека выполнила запрос, возвращающий URL-адрес ваших файлов расширения.
Примечание: независимо от того, является ли ваше приложение бесплатным или нет, Google Play возвращает URL-адреса файлов расширения только в том случае, если пользователь загрузил ваше приложение из Google Play.
Помимо LVL-файла, вам потребуется набор кода, который загружает файлы расширения по HTTP-соединению и сохраняет их в нужном месте на общей памяти устройства. При внедрении этой процедуры в ваше приложение следует учитывать несколько моментов:
- На устройстве может не хватать места для файлов расширения, поэтому перед началом загрузки следует проверить это и предупредить пользователя, если места недостаточно.
- Загрузка файлов должна происходить в фоновом режиме, чтобы избежать блокировки взаимодействия с пользователем и позволить пользователю покинуть приложение, пока загрузка завершается.
- В процессе запроса и загрузки могут возникать различные ошибки, которые необходимо корректно обрабатывать.
- В процессе загрузки может происходить сбой в работе сети, поэтому следует оперативно реагировать на такие изменения и, в случае прерывания, возобновлять загрузку при первой возможности.
- Пока загрузка происходит в фоновом режиме, следует отображать уведомление, которое показывает ход загрузки, сообщает пользователю о завершении и возвращает пользователя в ваше приложение при выборе.
Чтобы упростить вам эту работу, мы разработали библиотеку Downloader Library , которая запрашивает URL-адреса файлов расширения через службу лицензирования, загружает файлы расширения, выполняет все перечисленные выше задачи и даже позволяет вашему приложению приостанавливать и возобновлять загрузку. Добавив библиотеку Downloader Library и несколько хуков кода в ваше приложение, вы практически полностью реализуете всю работу по загрузке файлов расширения. Таким образом, чтобы обеспечить наилучший пользовательский опыт с минимальными усилиями с вашей стороны, мы рекомендуем использовать библиотеку Downloader Library для загрузки файлов расширения. Информация в следующих разделах объясняет, как интегрировать библиотеку в ваше приложение.
Если вы предпочитаете разработать собственное решение для загрузки файлов расширения с использованием URL-адресов Google Play, вам необходимо следовать документации по лицензированию приложения , чтобы выполнить запрос на получение лицензии, а затем получить имена, размеры и URL-адреса файлов расширения из дополнительных данных ответа. В качестве политики лицензирования следует использовать класс APKExpansionPolicy (входящий в библиотеку проверки лицензий), который получает имена, размеры и URL-адреса файлов расширения от службы лицензирования.
О библиотеке загрузчиков
Чтобы использовать файлы расширения APK в вашем приложении и обеспечить наилучший пользовательский опыт с минимальными усилиями с вашей стороны, мы рекомендуем использовать библиотеку Downloader, которая входит в пакет Google Play APK Expansion Library. Эта библиотека загружает ваши файлы расширения в фоновом режиме, отображает пользователю уведомление о статусе загрузки, обрабатывает потерю сетевого соединения, возобновляет загрузку, если это возможно, и многое другое.
Для реализации загрузки дополнительных файлов с помощью библиотеки Downloader Library вам потребуется всего лишь:
- Создайте специальный подкласс
Serviceи подклассBroadcastReceiver, для каждого из которых вам потребуется всего несколько строк кода. - Добавьте в основное приложение логику, которая проверяет, были ли уже загружены файлы расширения, и если нет, запускает процесс загрузки и отображает индикатор выполнения.
- В основной активности реализуйте интерфейс обратного вызова с несколькими методами, который будет получать обновления о ходе загрузки.
В следующих разделах объясняется, как настроить ваше приложение с помощью библиотеки Downloader.
Подготовка к использованию библиотеки загрузчиков.
Для использования библиотеки Downloader необходимо загрузить два пакета из SDK Manager и добавить соответствующие библиотеки в ваше приложение.
Сначала откройте Менеджер SDK Android ( Инструменты > Менеджер SDK ), затем в разделе Внешний вид и поведение > Системные настройки > Android SDK выберите вкладку «Инструменты SDK» , чтобы выбрать и загрузить:
- Пакет лицензионной библиотеки Google Play
- Пакет расширения библиотеки APK Google Play
Создайте новый библиотечный модуль для библиотеки проверки лицензий и библиотеки загрузчиков. Для каждой библиотеки:
- Выберите Файл > Создать > Новый модуль .
- В окне «Создать новый модуль» выберите «Библиотека Android» , а затем нажмите «Далее» .
- Укажите название приложения/библиотеки, например, "Google Play License Library" или "Google Play Downloader Library", выберите минимальный уровень SDK , а затем нажмите "Готово ".
- Выберите Файл > Структура проекта .
- Выберите вкладку «Свойства» , а в поле «Репозиторий библиотеки» укажите библиотеку из каталога
<sdk>/extras/google/(play_licensing/для библиотеки проверки лицензий илиplay_apk_expansion/downloader_library/для библиотеки загрузчика). - Нажмите ОК , чтобы создать новый модуль.
Примечание: Библиотека загрузчика зависит от библиотеки проверки лицензий. Обязательно добавьте библиотеку проверки лицензий в свойства проекта библиотеки загрузчика.
Или же, из командной строки, обновите свой проект, чтобы включить необходимые библиотеки:
- Перейдите в каталог
<sdk>/tools/. - Выполните команду
android update projectс опцией--library, чтобы добавить в проект библиотеки LVL и Downloader. Например:android update project --path ~/Android/MyApp \ --library ~/android_sdk/extras/google/market_licensing \ --library ~/android_sdk/extras/google/market_apk_expansion/downloader_library
Добавив в приложение библиотеку проверки лицензий и библиотеку загрузки, вы сможете быстро интегрировать возможность загрузки файлов расширения из Google Play. Формат файлов расширения и способ их чтения из общего хранилища — это отдельная задача, которую следует рассмотреть в зависимости от потребностей вашего приложения.
Совет: В пакет расширения APK входит пример приложения, демонстрирующий использование библиотеки загрузчика в приложении. В примере используется третья библиотека, доступная в пакете расширения APK, называемая библиотекой расширения APK в формате ZIP. Если вы планируете использовать ZIP-файлы для файлов расширения, мы рекомендуем также добавить библиотеку расширения APK в формате ZIP в ваше приложение. Для получения дополнительной информации см. раздел ниже об использовании библиотеки расширения APK в формате ZIP .
Объявление прав пользователя
Для загрузки файлов расширения библиотека Downloader требует предоставления нескольких разрешений, которые необходимо указать в файле манифеста вашего приложения. К ним относятся:
<manifest ...> <!-- Required to access Google Play Licensing --> <uses-permission android:name="com.android.vending.CHECK_LICENSE" /> <!-- Required to download files from Google Play --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Required to keep CPU alive while downloading files (NOT to keep screen awake) --> <uses-permission android:name="android.permission.WAKE_LOCK" /> <!-- Required to poll the state of the network connection and respond to changes --> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- Required to check whether Wi-Fi is enabled --> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!-- Required to read and write the expansion files on shared storage --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
Примечание: По умолчанию библиотека Downloader требует API уровня 4, а библиотека APK Expansion Zip — API уровня 5.
Реализация службы загрузки
Для выполнения загрузок в фоновом режиме библиотека Downloader предоставляет собственный подкласс Service под названием DownloaderService , который вам следует расширить. Помимо загрузки файлов расширения, DownloaderService также:
- Регистрирует объект
BroadcastReceiver, который отслеживает изменения сетевого подключения устройства (широковещательное сообщениеCONNECTIVITY_ACTION), чтобы при необходимости приостанавливать загрузку (например, из-за потери связи) и возобновлять ее, когда это возможно (подключение установлено). - Настраивает срабатывание сигнала
RTC_WAKEUPдля повторной попытки загрузки в случае завершения работы службы. - Создаёт пользовательское
Notification, отображающее ход загрузки, а также любые ошибки или изменения состояния. - Позволяет вашему приложению вручную приостанавливать и возобновлять загрузку.
- Перед загрузкой файлов расширения проверяется, смонтировано ли и доступно ли общее хранилище, не существуют ли файлы уже и достаточно ли места. Затем пользователь получает уведомление, если какое-либо из этих условий не выполняется.
Всё, что вам нужно сделать, это создать в своём приложении класс, который расширяет класс DownloaderService , и переопределить три метода для предоставления конкретных сведений о приложении:
-
getPublicKey() - Эта функция должна возвращать строку, представляющую собой закодированный в Base64 открытый ключ RSA для вашей учетной записи издателя, доступный на странице профиля в Play Console (см. раздел «Настройка лицензирования »).
-
getSALT() - Эта функция должна возвращать массив случайных байтов, который
Policyлицензирования использует для созданияObfuscator. Соль гарантирует, что ваш обфусцированный файлSharedPreferencesв котором хранятся данные лицензирования, будет уникальным и недоступным для обнаружения. -
getAlarmReceiverClassName() - Эта функция должна возвращать имя класса
BroadcastReceiverв вашем приложении, который должен получать оповещение о необходимости перезапуска загрузки (что может произойти, если служба загрузки неожиданно остановится).
Например, вот полная реализация DownloaderService :
Котлин
// You must use the public key belonging to your publisher account const val BASE64_PUBLIC_KEY = "YourLVLKey" // You should also modify this salt val SALT = byteArrayOf( 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 ) class SampleDownloaderService : DownloaderService() { override fun getPublicKey(): String = BASE64_PUBLIC_KEY override fun getSALT(): ByteArray = SALT override fun getAlarmReceiverClassName(): String = SampleAlarmReceiver::class.java.name }
Java
public class SampleDownloaderService extends DownloaderService { // You must use the public key belonging to your publisher account public static final String BASE64_PUBLIC_KEY = "YourLVLKey"; // You should also modify this salt public static final byte[] SALT = new byte[] { 1, 42, -12, -1, 54, 98, -100, -12, 43, 2, -8, -4, 9, 5, -106, -107, -33, 45, -1, 84 }; @Override public String getPublicKey() { return BASE64_PUBLIC_KEY; } @Override public byte[] getSALT() { return SALT; } @Override public String getAlarmReceiverClassName() { return SampleAlarmReceiver.class.getName(); } }
Внимание: Необходимо обновить значение параметра BASE64_PUBLIC_KEY , указав в нем открытый ключ, принадлежащий вашей учетной записи издателя. Ключ можно найти в консоли разработчика в разделе информации о вашем профиле. Это необходимо даже при тестировании загрузок.
Не забудьте указать сервис в файле манифеста:
<app ...> <service android:name=".SampleDownloaderService" /> ... </app>
Внедрение приемника сигналов тревоги
Для отслеживания хода загрузки файлов и перезапуска загрузки при необходимости, DownloaderService планирует отправку сигнала RTC_WAKEUP , который передает Intent в BroadcastReceiver вашего приложения. Необходимо определить BroadcastReceiver таким образом, чтобы он вызывал API из библиотеки Downloader, проверяющий статус загрузки и перезапускающий ее при необходимости.
Вам достаточно переопределить метод onReceive() , чтобы вызвать DownloaderClientMarshaller.startDownloadServiceIfRequired() .
Например:
Котлин
class SampleAlarmReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired( context, intent, SampleDownloaderService::class.java ) } catch (e: PackageManager.NameNotFoundException) { e.printStackTrace() } } }
Java
public class SampleAlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { try { DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, SampleDownloaderService.class); } catch (NameNotFoundException e) { e.printStackTrace(); } } }
Обратите внимание, что именно для этого класса необходимо вернуть имя в методе getAlarmReceiverClassName() вашего сервиса (см. предыдущий раздел).
Не забудьте указать получателя в файле манифеста:
<app ...> <receiver android:name=".SampleAlarmReceiver" /> ... </app>
Начало загрузки
Основная активность в вашем приложении (та, которая запускается иконкой в меню запуска) отвечает за проверку наличия файлов расширения на устройстве и запуск их загрузки, если их там нет.
Для начала загрузки с помощью библиотеки Downloader необходимо выполнить следующие действия:
- Проверьте, были ли загружены файлы.
Библиотека Downloader включает в себя несколько API в классе
Helper, которые помогают в этом процессе:-
getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode) -
doesFileExist(Context c, String fileName, long fileSize)
Например, в примере приложения, входящем в пакет Apk Expansion, в методе
onCreate()активности вызывается следующий метод для проверки наличия файлов расширения на устройстве:Котлин
fun expansionFilesDelivered(): Boolean { xAPKS.forEach { xf -> Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion).also { fileName -> if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false } } return true }
Java
boolean expansionFilesDelivered() { for (XAPKFile xf : xAPKS) { String fileName = Helpers.getExpansionAPKFileName(this, xf.isBase, xf.fileVersion); if (!Helpers.doesFileExist(this, fileName, xf.fileSize, false)) return false; } return true; }
В данном случае каждый объект
XAPKFileсодержит номер версии и размер известного файла расширения, а также логическое значение, указывающее, является ли он основным файлом расширения. (Подробнее см. классSampleDownloaderActivityв примере приложения.)Если этот метод возвращает false, то приложение должно начать загрузку.
-
- Начните загрузку, вызвав статический метод
DownloaderClientMarshaller.startDownloadServiceIfRequired(Context c, PendingIntent notificationClient, Class<?> serviceClass).Данный метод принимает следующие параметры:
-
context:Contextвашего приложения. -
notificationClient: ОбъектPendingIntentдля запуска основной активности. Он используется вNotification, которое создаетDownloaderServiceдля отображения хода загрузки. Когда пользователь выбирает уведомление, система вызывает указанный здесь объектPendingIntentи должна открыть активность, отображающую ход загрузки (обычно ту же активность, которая запустила загрузку). -
serviceClass: ОбъектClassдля вашей реализацииDownloaderService, необходимый для запуска службы и начала загрузки при необходимости.
Метод возвращает целое число, указывающее, требуется ли загрузка. Возможные значения:
-
NO_DOWNLOAD_REQUIRED: Возвращается, если файлы уже существуют или загрузка уже идёт. -
LVL_CHECK_REQUIRED: Возвращается, если для получения URL-адресов файлов расширения требуется проверка лицензии. -
DOWNLOAD_REQUIRED: Возвращается, если URL-адреса файлов расширения уже известны, но еще не были загружены.
Поведение переменных
LVL_CHECK_REQUIREDиDOWNLOAD_REQUIREDпо сути одинаково, и обычно вам не нужно о них беспокоиться. В вашей основной активности, которая вызываетstartDownloadServiceIfRequired(), вы можете просто проверить, является ли ответNO_DOWNLOAD_REQUIRED. Если ответ отличается отNO_DOWNLOAD_REQUIRED, библиотека Downloader начинает загрузку, и вам следует обновить пользовательский интерфейс вашей активности, чтобы отображать ход загрузки (см. следующий шаг). Если ответ NO_DOWNLOAD_REQUIRED , то файлы доступныNO_DOWNLOAD_REQUIREDи ваше приложение может запуститься.Например:
Котлин
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { val pendingIntent = // Build an Intent to start this activity from the Notification Intent(this, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP }.let { notifierIntent -> PendingIntent.getActivity( this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT ) } // Start the download service (if required) val startResult: Int = DownloaderClientMarshaller.startDownloadServiceIfRequired( this, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return } // If the download wasn't necessary, fall through to start the app } startApp() // Expansion files are available, start the app }
Java
@Override public void onCreate(Bundle savedInstanceState) { // Check if expansion files are available before going any further if (!expansionFilesDelivered()) { // Build an Intent to start this activity from the Notification Intent notifierIntent = new Intent(this, MainActivity.getClass()); notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); ... PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT); // Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize this activity to show // download progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // This is where you do set up to display the download // progress (next step) ... return; } // If the download wasn't necessary, fall through to start the app } startApp(); // Expansion files are available, start the app }
-
- Если метод
startDownloadServiceIfRequired()возвращает значение, отличное отNO_DOWNLOAD_REQUIRED, создайте экземплярIStub, вызвавDownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService).IStubобеспечивает связь между вашей активностью и службой загрузки, так что ваша активность получает обратные вызовы о ходе загрузки.Для создания экземпляра
IStubс помощью вызоваCreateStub()необходимо передать ему реализацию интерфейсаIDownloaderClientи реализацию вашегоDownloaderService. В следующем разделе, посвященном получению информации о ходе загрузки, рассматривается интерфейсIDownloaderClient, который обычно следует реализовывать в классеActivity, чтобы можно было обновлять пользовательский интерфейс Activity при изменении состояния загрузки.Мы рекомендуем вызывать метод
CreateStub()для создания экземпляраIStubв методеonCreate()вашего действия, после того какstartDownloadServiceIfRequired()начнет загрузку.Например, в предыдущем примере кода для
onCreate()вы можете отреагировать на результат методаstartDownloadServiceIfRequired()следующим образом:Котлин
// Start the download service (if required) val startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired( this@MainActivity, pendingIntent, SampleDownloaderService::class.java ) // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService::class.java) // Inflate layout that shows download progress setContentView(R.layout.downloader_ui) return }
Java
// Start the download service (if required) int startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(this, pendingIntent, SampleDownloaderService.class); // If download has started, initialize activity to show progress if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) { // Instantiate a member instance of IStub downloaderClientStub = DownloaderClientMarshaller.CreateStub(this, SampleDownloaderService.class); // Inflate layout that shows download progress setContentView(R.layout.downloader_ui); return; }
После возврата из метода
onCreate()ваша активность получает вызовonResume(), где вам следует вызватьconnect()дляIStub, передав емуContextвашего приложения. И наоборот, вам следует вызватьdisconnect()в коллбэкеonStop()вашей активности.Котлин
override fun onResume() { downloaderClientStub?.connect(this) super.onResume() } override fun onStop() { downloaderClientStub?.disconnect(this) super.onStop() }
Java
@Override protected void onResume() { if (null != downloaderClientStub) { downloaderClientStub.connect(this); } super.onResume(); } @Override protected void onStop() { if (null != downloaderClientStub) { downloaderClientStub.disconnect(this); } super.onStop(); }
Вызов метода
connect()уIStubпривязывает вашу активность кDownloaderService, так что ваша активность получает обратные вызовы, касающиеся изменений состояния загрузки, через интерфейсIDownloaderClient.
Получение информации о ходе загрузки
Для получения уведомлений о ходе загрузки и взаимодействия с DownloaderService необходимо реализовать интерфейс IDownloaderClient из библиотеки Downloader. Как правило, активность, используемая для запуска загрузки, должна реализовывать этот интерфейс, чтобы отображать ход загрузки и отправлять запросы в службу.
Необходимые методы интерфейса для IDownloaderClient :
-
onServiceConnected(Messenger m) - После создания экземпляра
IStubв вашей активности вы получите вызов этого метода, который передаст объектMessenger, связанный с вашим экземпляромDownloaderService. Чтобы отправлять запросы к сервису, например, для приостановки и возобновления загрузок, необходимо вызватьDownloaderServiceMarshaller.CreateProxy(), чтобы получить интерфейсIDownloaderServiceподключенный к сервису.Рекомендуемый вариант реализации выглядит следующим образом:
Котлин
private var remoteService: IDownloaderService? = null ... override fun onServiceConnected(m: Messenger) { remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { downloaderClientStub?.messenger?.also { messenger -> onClientUpdated(messenger) } } }
Java
private IDownloaderService remoteService; ... @Override public void onServiceConnected(Messenger m) { remoteService = DownloaderServiceMarshaller.CreateProxy(m); remoteService.onClientUpdated(downloaderClientStub.getMessenger()); }
После инициализации объекта
IDownloaderServiceвы можете отправлять команды службе загрузки, например, для приостановки и возобновления загрузки (requestPauseDownload()иrequestContinueDownload()). -
onDownloadStateChanged(int newState) - Служба загрузки вызывает эту функцию при изменении состояния загрузки, например, когда загрузка начинается или завершается.
Значение
newStateбудет одним из нескольких возможных значений, указанных в одной из константSTATE_*классаIDownloaderClient.Чтобы предоставить пользователям полезное сообщение, вы можете запросить соответствующую строку для каждого состояния, вызвав метод
Helpers.getDownloaderStringResourceIDFromState(). Это вернет идентификатор ресурса для одной из строк, входящих в состав библиотеки Downloader. Например, строка "Загрузка приостановлена, потому что вы находитесь в роуминге" соответствует состояниюSTATE_PAUSED_ROAMING. -
onDownloadProgress(DownloadProgressInfo progress) - Служба загрузки обращается к этому объекту для передачи информации
DownloadProgressInfo, которая содержит различные сведения о процессе загрузки, включая предполагаемое оставшееся время, текущую скорость, общий прогресс и итоговые значения, чтобы вы могли обновить пользовательский интерфейс отслеживания хода загрузки.
Совет: Примеры таких функций обратного вызова, обновляющих интерфейс отображения хода загрузки, можно найти в классе SampleDownloaderActivity в демонстрационном приложении, входящем в состав пакета Apk Expansion.
Вот некоторые общедоступные методы интерфейса IDownloaderService , которые могут вам пригодиться:
-
requestPauseDownload() - Приостанавливает загрузку.
-
requestContinueDownload() - Возобновляет приостановленную загрузку.
-
setDownloadFlags(int flags) - Устанавливает пользовательские настройки для типов сетей, в которых разрешена загрузка файлов. Текущая реализация поддерживает один флаг,
FLAGS_DOWNLOAD_OVER_CELLULAR, но вы можете добавить и другие. По умолчанию этот флаг отключен , поэтому пользователь должен находиться в сети Wi-Fi для загрузки файлов расширения. Возможно, вам потребуется указать пользовательские настройки для разрешения загрузки через сотовую сеть. В этом случае вы можете вызвать:Котлин
remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply { ... setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR) }
Java
remoteService .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);
Использование APKExpansionPolicy
If you decide to build your own downloader service instead of using the Google Play Downloader Library , you should still use the APKExpansionPolicy that's provided in the License Verification Library. The APKExpansionPolicy class is nearly identical to ServerManagedPolicy (available in the Google Play License Verification Library) but includes additional handling for the APK expansion file response extras.
Note: If you do use the Downloader Library as discussed in the previous section, the library performs all interaction with the APKExpansionPolicy so you don't have to use this class directly.
The class includes methods to help you get the necessary information about the available expansion files:
-
getExpansionURLCount() -
getExpansionURL(int index) -
getExpansionFileName(int index) -
getExpansionFileSize(int index)
For more information about how to use the APKExpansionPolicy when you're not using the Downloader Library , see the documentation for Adding Licensing to Your App , which explains how to implement a license policy such as this one.
Reading the Expansion File
Once your APK expansion files are saved on the device, how you read your files depends on the type of file you've used. As discussed in the overview , your expansion files can be any kind of file you want, but are renamed using a particular file name format and are saved to <shared-storage>/Android/obb/<package-name>/ .
Regardless of how you read your files, you should always first check that the external storage is available for reading. There's a chance that the user has the storage mounted to a computer over USB or has actually removed the SD card.
Note: When your app starts, you should always check whether the external storage space is available and readable by calling getExternalStorageState() . This returns one of several possible strings that represent the state of the external storage. In order for it to be readable by your app, the return value must be MEDIA_MOUNTED .
Getting the file names
As described in the overview , your APK expansion files are saved using a specific file name format:
[main|patch].<expansion-version>.<package-name>.obb
To get the location and names of your expansion files, you should use the getExternalStorageDirectory() and getPackageName() methods to construct the path to your files.
Here's a method you can use in your app to get an array containing the complete path to both your expansion files:
Kotlin
fun getAPKExpansionFiles(ctx: Context, mainVersion: Int, patchVersion: Int): Array<String> { val packageName = ctx.packageName val ret = mutableListOf<String>() if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { // Build the full path to the app's expansion files val root = Environment.getExternalStorageDirectory() val expPath = File(root.toString() + EXP_PATH + packageName) // Check that expansion file path exists if (expPath.exists()) { if (mainVersion > 0) { val strMainPath = "$expPath${File.separator}main.$mainVersion.$packageName.obb" val main = File(strMainPath) if (main.isFile) { ret += strMainPath } } if (patchVersion > 0) { val strPatchPath = "$expPath${File.separator}patch.$mainVersion.$packageName.obb" val main = File(strPatchPath) if (main.isFile) { ret += strPatchPath } } } } return ret.toTypedArray() }
Java
// The shared path to all app expansion files private final static String EXP_PATH = "/Android/obb/"; static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) { String packageName = ctx.getPackageName(); Vector<String> ret = new Vector<String>(); if (Environment.getExternalStorageState() .equals(Environment.MEDIA_MOUNTED)) { // Build the full path to the app's expansion files File root = Environment.getExternalStorageDirectory(); File expPath = new File(root.toString() + EXP_PATH + packageName); // Check that expansion file path exists if (expPath.exists()) { if ( mainVersion > 0 ) { String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb"; File main = new File(strMainPath); if ( main.isFile() ) { ret.add(strMainPath); } } if ( patchVersion > 0 ) { String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb"; File main = new File(strPatchPath); if ( main.isFile() ) { ret.add(strPatchPath); } } } } String[] retArray = new String[ret.size()]; ret.toArray(retArray); return retArray; }
You can call this method by passing it your app Context and the desired expansion file's version.
There are many ways you could determine the expansion file version number. One simple way is to save the version in a SharedPreferences file when the download begins, by querying the expansion file name with the APKExpansionPolicy class's getExpansionFileName(int index) method. You can then get the version code by reading the SharedPreferences file when you want to access the expansion file.
For more information about reading from the shared storage, see the Data Storage documentation.
Using the APK Expansion Zip Library
The Google Market Apk Expansion package includes a library called the APK Expansion Zip Library (located in <sdk>/extras/google/google_market_apk_expansion/zip_file/ ). This is an optional library that helps you read your expansion files when they're saved as ZIP files. Using this library allows you to easily read resources from your ZIP expansion files as a virtual file system.
The APK Expansion Zip Library includes the following classes and APIs:
-
APKExpansionSupport - Provides some methods to access expansion file names and ZIP files:
-
getAPKExpansionFiles() - The same method shown above that returns the complete file path to both expansion files.
-
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) - Returns a
ZipResourceFilerepresenting the sum of both the main file and patch file. That is, if you specify both themainVersionand thepatchVersion, this returns aZipResourceFilethat provides read access to all the data, with the patch file's data merged on top of the main file.
-
-
ZipResourceFile - Represents a ZIP file on the shared storage and performs all the work to provide a virtual file system based on your ZIP files. You can get an instance using
APKExpansionSupport.getAPKExpansionZipFile()or with theZipResourceFileby passing it the path to your expansion file. This class includes a variety of useful methods, but you generally don't need to access most of them. A couple of important methods are:-
getInputStream(String assetPath) - Provides an
InputStreamto read a file within the ZIP file. TheassetPathmust be the path to the desired file, relative to the root of the ZIP file contents. -
getAssetFileDescriptor(String assetPath) - Provides an
AssetFileDescriptorfor a file within the ZIP file. TheassetPathmust be the path to the desired file, relative to the root of the ZIP file contents. This is useful for certain Android APIs that require anAssetFileDescriptor, such as someMediaPlayerAPIs.
-
-
APEZProvider - Most apps don't need to use this class. This class defines a
ContentProviderthat marshals the data from the ZIP files through a content providerUriin order to provide file access for certain Android APIs that expectUriaccess to media files. For example, this is useful if you want to play a video withVideoView.setVideoURI().
Skipping ZIP compression of media files
If you're using your expansion files to store media files, a ZIP file still allows you to use Android media playback calls that provide offset and length controls (such as MediaPlayer.setDataSource() and SoundPool.load() ). In order for this to work, you must not perform additional compression on the media files when creating the ZIP packages. For example, when using the zip tool, you should use the -n option to specify the file suffixes that should not be compressed:
zip -n .mp4;.ogg main_expansion media_files
Reading from a ZIP file
When using the APK Expansion Zip Library, reading a file from your ZIP usually requires the following:
Kotlin
// Get a ZipResourceFile representing a merger of both the main and patch files val expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a merger of both the main and patch files ZipResourceFile expansionFile = APKExpansionSupport.getAPKExpansionZipFile(appContext, mainVersion, patchVersion); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
The above code provides access to any file that exists in either your main expansion file or patch expansion file, by reading from a merged map of all the files from both files. All you need to provide the getAPKExpansionFile() method is your app android.content.Context and the version number for both the main expansion file and patch expansion file.
If you'd rather read from a specific expansion file, you can use the ZipResourceFile constructor with the path to the desired expansion file:
Kotlin
// Get a ZipResourceFile representing a specific expansion file val expansionFile = ZipResourceFile(filePathToMyZip) // Get an input stream for a known file inside the expansion file ZIPs expansionFile.getInputStream(pathToFileInsideZip).use { ... }
Java
// Get a ZipResourceFile representing a specific expansion file ZipResourceFile expansionFile = new ZipResourceFile(filePathToMyZip); // Get an input stream for a known file inside the expansion file ZIPs InputStream fileStream = expansionFile.getInputStream(pathToFileInsideZip);
For more information about using this library for your expansion files, look at the sample app's SampleDownloaderActivity class, which includes additional code to verify the downloaded files using CRC. Beware that if you use this sample as the basis for your own implementation, it requires that you declare the byte size of your expansion files in the xAPKS array.
Testing Your Expansion Files
Before publishing your app, there are two things you should test: Reading the expansion files and downloading the files.
Testing file reads
Before you upload your app to Google Play, you should test your app's ability to read the files from the shared storage. All you need to do is add the files to the appropriate location on the device shared storage and launch your app:
- On your device, create the appropriate directory on the shared storage where Google Play will save your files.
For example, if your package name is
com.example.android, you need to create the directoryAndroid/obb/com.example.android/on the shared storage space. (Plug in your test device to your computer to mount the shared storage and manually create this directory.) - Manually add the expansion files to that directory. Be sure that you rename your files to match the file name format that Google Play will use.
For example, regardless of the file type, the main expansion file for the
com.example.androidapp should bemain.0300110.com.example.android.obb. The version code can be whatever value you want. Just remember:- The main expansion file always starts with
mainand the patch file starts withpatch. - The package name always matches that of the APK to which the file is attached on Google Play.
- The main expansion file always starts with
- Now that the expansion file(s) are on the device, you can install and run your app to test your expansion file(s).
Here are some reminders about handling the expansion files:
- Do not delete or rename the
.obbexpansion files (even if you unpack the data to a different location). Doing so will cause Google Play (or your app itself) to repeatedly download the expansion file. - Do not save other data into your
obb/directory . If you must unpack some data, save it into the location specified bygetExternalFilesDir().
Testing file downloads
Because your app must sometimes manually download the expansion files when it first opens, it's important that you test this process to be sure your app can successfully query for the URLs, download the files, and save them to the device.
To test your app's implementation of the manual download procedure, you can publish it to the internal test track, so it's only available to authorized testers. If everything works as expected, your app should begin downloading the expansion files as soon as the main activity starts.
Note: Previously you could test an app by uploading an unpublished "draft" version. This functionality is no longer supported. Instead, you must publish it to an internal, closed, or open testing track. For more information, see Draft Apps are No Longer Supported .
Updating Your app
One of the great benefits to using expansion files on Google Play is the ability to update your app without re-downloading all of the original assets. Because Google Play allows you to provide two expansion files with each APK, you can use the second file as a "patch" that provides updates and new assets. Doing so avoids the need to re-download the main expansion file which could be large and expensive for users.
The patch expansion file is technically the same as the main expansion file and neither the Android system nor Google Play perform actual patching between your main and patch expansion files. Your app code must perform any necessary patches itself.
If you use ZIP files as your expansion files, the APK Expansion Zip Library that's included with the Apk Expansion package includes the ability to merge your patch file with the main expansion file.
Note: Even if you only need to make changes to the patch expansion file, you must still update the APK in order for Google Play to perform an update. If you don't require code changes in the app, you should simply update the versionCode in the manifest.
As long as you don't change the main expansion file that's associated with the APK in the Play Console, users who previously installed your app will not download the main expansion file. Existing users receive only the updated APK and the new patch expansion file (retaining the previous main expansion file).
Here are a few issues to keep in mind regarding updates to expansion files:
- There can be only two expansion files for your app at a time. One main expansion file and one patch expansion file. During an update to a file, Google Play deletes the previous version (and so must your app when performing manual updates).
- When adding a patch expansion file, the Android system does not actually patch your app or main expansion file. You must design your app to support the patch data. However, the Apk Expansion package includes a library for using ZIP files as expansion files, which merges the data from the patch file into the main expansion file so you can easily read all the expansion file data.