Файлы расширения APK,Файлы расширения APK

Google Play требует, чтобы размер сжатого APK-файла, который загружают пользователи, не превышал 100 МБ. Для большинства приложений этого достаточно места для всего кода и ресурсов приложения. Однако некоторым приложениям требуется больше места для высококачественной графики, мультимедийных файлов или других крупных ресурсов. Раньше, если размер сжатой загрузки вашего приложения превышал 100 МБ, вам приходилось самостоятельно размещать и загружать дополнительные ресурсы, когда пользователь открывает приложение. Хостинг и обслуживание дополнительных файлов могут быть дорогостоящими, а пользовательский опыт часто не идеален. Чтобы облегчить вам этот процесс и сделать его более приятным для пользователей, Google Play позволяет вам прикрепить два больших файла расширения, дополняющих ваш APK.

Google Play размещает файлы расширения для вашего приложения и бесплатно доставляет их на устройство. Файлы расширения сохраняются в общем хранилище устройства (SD-карта или раздел, подключаемый через USB; также известное как «внешнее» хранилище), где ваше приложение может получить к ним доступ. На большинстве устройств Google Play загружает файлы расширения одновременно с загрузкой APK, поэтому в вашем приложении есть все необходимое, когда пользователь открывает его в первый раз. Однако в некоторых случаях ваше приложение должно загружать файлы из Google Play при запуске.

Если вы не хотите использовать файлы расширения, а размер сжатой загрузки вашего приложения превышает 100 МБ, вместо этого вам следует загрузить приложение с помощью пакетов Android App Bundle , которые позволяют загрузить сжатый размер загрузки до 200 МБ. Кроме того, поскольку использование пакетов приложений откладывает создание APK и подписание в Google Play, пользователи загружают оптимизированные APK, содержащие только код и ресурсы, необходимые для запуска вашего приложения. Вам не нужно создавать, подписывать и управлять несколькими APK-файлами или файлами расширений, а пользователи получают меньшие по размеру и более оптимизированные загрузки.

Обзор

Каждый раз, когда вы загружаете APK с помощью консоли Google Play, у вас есть возможность добавить в APK один или два файла расширения. Каждый файл может иметь размер до 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()
}

Ява

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.

Процесс загрузки с высокого уровня выглядит так:

  1. Пользователь выбирает установку вашего приложения из Google Play.
  2. Если Google Play может загружать файлы расширения (что характерно для большинства устройств), он загружает их вместе с APK.

    Если Google Play не может загрузить файлы расширения, он загружает только APK.

  3. Когда пользователь запускает ваше приложение, оно должно проверить, сохранены ли уже файлы расширения на устройстве.
    1. Если да, ваше приложение готово к работе.
    2. Если нет, ваше приложение должно загрузить файлы расширения по HTTP из Google Play. Ваше приложение должно отправить запрос клиенту Google Play с помощью службы лицензирования приложений Google Play, которая отвечает именем, размером файла и URL-адресом для каждого файла расширения. Используя эту информацию, вы затем загружаете файлы и сохраняете их в нужном месте хранения .

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

Контрольный список разработки

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

  1. Сначала определите, должен ли размер загрузки вашего приложения в сжатом виде превышать 100 МБ. Пространство очень ценно, и общий размер загружаемого файла должен быть как можно меньшим. Если ваше приложение использует более 100 МБ для предоставления нескольких версий ваших графических ресурсов для разных плотностей экрана, рассмотрите возможность публикации нескольких APK-файлов , в которых каждый APK-файл содержит только ресурсы, необходимые для целевых экранов. Для достижения наилучших результатов при публикации в Google Play загрузите пакет Android App Bundle , который включает в себя весь скомпилированный код и ресурсы вашего приложения, но не позволяет создавать APK и подписываться на Google Play.
  2. Определите, какие ресурсы приложения следует отделить от вашего APK, и упакуйте их в файл, который будет использоваться в качестве основного файла расширения.

    Обычно при обновлении основного файла расширения следует использовать только второй файл расширения исправления. Однако если ваши ресурсы превышают лимит в 2 ГБ для основного файла расширения, вы можете использовать файл исправления для остальных ваших ресурсов.

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

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

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

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

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

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

Завершив разработку приложения, следуйте руководству по тестированию файлов расширения .

Правила и ограничения

Добавление файлов расширения APK — это функция, доступная при загрузке приложения с помощью Play Console. При первой загрузке приложения или обновлении приложения, использующего файлы расширения, вы должны учитывать следующие правила и ограничения:

  1. Размер каждого файла расширения не может превышать 2 ГБ.
  2. Чтобы загрузить файлы расширения из Google Play, пользователь должен приобрести ваше приложение из Google Play . Google Play не будет предоставлять URL-адреса ваших файлов расширения, если приложение было установлено другим способом.
  3. При загрузке из вашего приложения URL-адрес, который Google Play предоставляет для каждого файла, уникален для каждой загрузки, и срок действия каждого из них истекает вскоре после его передачи в ваше приложение.
  4. Если вы обновляете свое приложение новым APK или загружаете несколько APK для одного и того же приложения, вы можете выбрать файлы расширения, которые вы загрузили для предыдущего APK. Имя файла расширения не меняется — оно сохраняет версию, полученную APK, с которым файл был изначально связан.
  5. Если вы используете файлы расширения в сочетании с несколькими APK-файлами , чтобы предоставить разные файлы расширения для разных устройств, вам все равно придется загружать отдельные APK для каждого устройства, чтобы предоставить уникальное значение versionCode и объявить разные фильтры для каждого APK.
  6. Вы не можете обновить свое приложение, изменив только файлы расширения — вам необходимо загрузить новый APK , чтобы обновить свое приложение. Если ваши изменения касаются только ресурсов в файлах расширения, вы можете обновить APK, просто изменив versionCode (и, возможно, также versionName ).

  7. Не сохраняйте другие данные в каталог obb/ . Если вам необходимо распаковать некоторые данные, сохраните их в место, указанное getExternalFilesDir() .
  8. Не удаляйте и не переименовывайте файл расширения .obb (если только вы не выполняете обновление). Это приведет к тому, что Google Play (или само ваше приложение) будет неоднократно загружать файл расширения.
  9. При обновлении файла расширения вручную необходимо удалить предыдущий файл расширения.

Загрузка файлов расширения

В большинстве случаев Google Play загружает и сохраняет файлы расширений на устройство одновременно с установкой или обновлением APK. Таким образом, файлы расширения будут доступны при первом запуске вашего приложения. Однако в некоторых случаях ваше приложение должно само загрузить файлы расширения, запросив их по URL-адресу, предоставленному вам в ответе службы лицензирования приложений Google Play.

Основная логика, необходимая для загрузки файлов расширения, следующая:

  1. При запуске приложения найдите файлы расширения в общем хранилище (в каталоге Android/obb/<package-name>/ ).
    1. Если файлы расширения есть, все готово, и ваше приложение может продолжать работу.
    2. Если файлов расширения нет :
      1. Выполните запрос с помощью лицензирования приложений Google Play, чтобы получить имена, размеры и URL-адреса файлов расширения вашего приложения.
      2. Используйте 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-соединение и сохраняет их в нужном месте в общем хранилище устройства. При встраивании этой процедуры в свое приложение следует принять во внимание несколько вопросов:

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

Чтобы упростить вам эту работу, мы создали библиотеку загрузчика , которая запрашивает URL-адреса файлов расширения через службу лицензирования, загружает файлы расширения, выполняет все перечисленные выше задачи и даже позволяет вашей активности приостанавливать и возобновлять загрузку. . Добавив в свое приложение библиотеку Downloader и несколько перехватчиков кода, почти вся работа по загрузке файлов расширения уже запрограммирована за вас. Таким образом, чтобы обеспечить наилучшее взаимодействие с пользователем с минимальными усилиями с вашей стороны, мы рекомендуем вам использовать библиотеку загрузчика для загрузки файлов расширения. Информация в следующих разделах объясняет, как интегрировать библиотеку в ваше приложение.

Если вы предпочитаете разработать собственное решение для загрузки файлов расширения с использованием URL-адресов Google Play, вам необходимо следовать документации по лицензированию приложения , чтобы выполнить запрос на лицензию, а затем получить имена, размеры и URL-адреса файлов расширения из дополнительных ответов. Вам следует использовать класс APKExpansionPolicy (включенный в библиотеку проверки лицензий) в качестве политики лицензирования, которая фиксирует имена, размеры и URL-адреса файлов расширения из службы лицензирования.

О библиотеке загрузчика

Чтобы использовать файлы расширения APK с вашим приложением и обеспечить наилучшее взаимодействие с пользователем с минимальными усилиями с вашей стороны, мы рекомендуем вам использовать библиотеку загрузчика, которая включена в пакет библиотеки расширений APK Google Play. Эта библиотека загружает ваши файлы расширения в фоновом режиме, показывает пользователю уведомление о состоянии загрузки, обрабатывает потерю сетевого подключения, возобновляет загрузку, когда это возможно, и многое другое.

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

  • Расширьте специальный подкласс Service и подкласс BroadcastReceiver , каждый из которых потребует от вас всего несколько строк кода.
  • Добавьте в свое основное действие некоторую логику, которая проверяет, были ли уже загружены файлы расширения, и, если нет, запускает процесс загрузки и отображает пользовательский интерфейс прогресса.
  • Реализуйте интерфейс обратного вызова с несколькими методами в своем основном действии, который будет получать обновления о ходе загрузки.

В следующих разделах объясняется, как настроить приложение с помощью библиотеки загрузчика.

Подготовка к использованию библиотеки загрузчика

Чтобы использовать библиотеку загрузчика, вам необходимо загрузить два пакета из диспетчера SDK и добавить соответствующие библиотеки в свое приложение.

Сначала откройте диспетчер Android SDK ( Инструменты > Диспетчер SDK ) и в разделе «Внешний вид и поведение» > «Настройки системы» > Android SDK выберите вкладку «Инструменты SDK» , чтобы выбрать и загрузить:

  • Пакет библиотеки лицензирования Google Play
  • Пакет библиотеки расширений Google Play APK

Создайте новый библиотечный модуль для библиотеки проверки лицензии и библиотеки загрузчика. Для каждой библиотеки:

  1. Выберите «Файл» > «Создать» > «Новый модуль» .
  2. В окне «Создать новый модуль» выберите «Библиотека Android» , а затем нажмите «Далее» .
  3. Укажите имя приложения/библиотеки , например «Библиотека лицензий Google Play» и «Библиотека загрузчика Google Play», выберите «Минимальный уровень SDK» и нажмите «Готово» .
  4. Выберите «Файл» > «Структура проекта» .
  5. Выберите вкладку «Свойства» и в «Репозитории библиотек» введите библиотеку из каталога <sdk>/extras/google/ ( play_licensing/ для библиотеки проверки лицензии или play_apk_expansion/downloader_library/ для библиотеки загрузчика).
  6. Выберите ОК , чтобы создать новый модуль.

Примечание. Библиотека загрузчика зависит от библиотеки проверки лицензии. Обязательно добавьте библиотеку проверки лицензий в свойства проекта библиотеки загрузчика.

Или из командной строки обновите проект, включив в него библиотеки:

  1. Перейдите в каталог <sdk>/tools/ .
  2. Выполните android update project с параметром --library , чтобы добавить в проект как LVL, так и библиотеку загрузчика. Например:
    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, которая называется Zip-библиотека расширения APK. Если вы планируете использовать ZIP-файлы для файлов расширений, мы предлагаем вам также добавить ZIP-библиотеку расширений APK в ваше приложение. Для получения дополнительной информации см. раздел ниже об использовании ZIP-библиотеки расширения APK .

Объявление разрешений пользователя

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

<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>

Примечание. По умолчанию для библиотеки загрузчика требуется уровень API 4, но для Zip-библиотеки расширения APK требуется уровень API 5.

Реализация службы загрузчика

Для выполнения загрузок в фоновом режиме библиотека Downloader предоставляет собственный подкласс Service под названием DownloaderService , который вам следует расширить. Помимо загрузки файлов расширения, DownloaderService также:

  • Регистрирует BroadcastReceiver , который прослушивает изменения в сетевом подключении устройства (рассылка CONNECTIVITY_ACTION ), чтобы при необходимости приостановить загрузку (например, из-за потери подключения) и возобновить загрузку, когда это возможно (соединение установлено).
  • Планирует сигнал тревоги RTC_WAKEUP для повторной попытки загрузки в случаях, когда служба отключается.
  • Создает пользовательское Notification , отображающее ход загрузки и любые ошибки или изменения состояния.
  • Позволяет вашему приложению вручную приостанавливать и возобновлять загрузку.
  • Перед загрузкой файлов расширения проверяется, что общее хранилище подключено и доступно, что файлы еще не существуют и достаточно места. Затем уведомляет пользователя, если что-либо из этого не соответствует действительности.

Все, что вам нужно сделать, — это создать в приложении класс, расширяющий класс DownloaderService , и переопределить три метода для предоставления конкретных сведений о приложении:

getPublicKey()
Это должно возвращать строку, которая представляет собой открытый ключ RSA в кодировке Base64 для вашей учетной записи издателя, доступный на странице профиля в 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
}

Ява

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 из библиотеки загрузчика, который проверяет состояние загрузки и при необходимости перезапускает ее.

Вам просто нужно переопределить метод 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()
        }
    }
}

Ява

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 необходимо выполнить следующие процедуры:

  1. Проверьте, загрузились ли файлы.

    Библиотека загрузчика включает в себя несколько API-интерфейсов класса Helper , которые помогут в этом процессе:

    • getExpansionAPKFileName(Context, c, boolean mainFile, int versionCode)
    • doesFileExist(Context c, String fileName, long fileSize)

    Например, пример приложения, представленный в пакете расширения Apk, вызывает следующий метод в методе 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
    }
    

    Ява

    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, приложение должно начать загрузку.

  2. Запустите загрузку, вызвав статический метод 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 , библиотека загрузчика начинает загрузку, и вам следует обновить пользовательский интерфейс активности, чтобы отобразить ход загрузки (см. следующий шаг). Если ответ 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
    }
    

    Ява

    @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
    }
    
  3. Когда метод startDownloadServiceIfRequired() возвращает что-либо , кроме NO_DOWNLOAD_REQUIRED , создайте экземпляр IStub , вызвав DownloaderClientMarshaller.CreateStub(IDownloaderClient client, Class<?> downloaderService) . IStub обеспечивает привязку вашей активности к службе загрузчика, так что ваша активность получает обратные вызовы о ходе загрузки.

    Чтобы создать IStub с помощью вызова CreateStub() , вы должны передать ему реализацию интерфейса IDownloaderClient и вашу реализацию DownloaderService . В следующем разделе « Получение прогресса загрузки» обсуждается интерфейс IDownloaderClient , который обычно следует реализовать в классе 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
            }
    

    Ява

            // 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()
    }
    

    Ява

    @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)
        }
    }
}

Ява

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() . Это возвращает идентификатор ресурса для одной из строк, включенных в библиотеку загрузчика. Например, строка «Загрузка приостановлена, поскольку вы находитесь в роуминге» соответствует STATE_PAUSED_ROAMING .

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

Совет: примеры этих обратных вызовов, которые обновляют пользовательский интерфейс процесса загрузки, см. в разделе SampleDownloaderActivity в образце приложения, поставляемом с пакетом расширения Apk.

Некоторые общедоступные методы интерфейса IDownloaderService , которые могут оказаться вам полезными:

requestPauseDownload()
Приостанавливает загрузку.
requestContinueDownload()
Возобновляет приостановленную загрузку.
setDownloadFlags(int flags)
Устанавливает пользовательские предпочтения для типов сетей, в которых можно загружать файлы. Текущая реализация поддерживает один флаг FLAGS_DOWNLOAD_OVER_CELLULAR , но вы можете добавить и другие. По умолчанию этот флаг не включен, поэтому для загрузки файлов расширения пользователю необходимо подключиться к Wi-Fi. Возможно, вы захотите указать предпочтения пользователя для включения загрузки через сотовую сеть. В этом случае вы можете позвонить:

Котлин

remoteService = DownloaderServiceMarshaller.CreateProxy(m).apply {
    ...
    setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR)
}

Ява

remoteService
    .setDownloadFlags(IDownloaderService.FLAGS_DOWNLOAD_OVER_CELLULAR);

Используйте APKExpansionPolicy

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

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

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

  • getExpansionURLCount()
  • getExpansionURL(int index)
  • getExpansionFileName(int index)
  • getExpansionFileSize(int index)

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

Чтение файла расширения

После того, как ваши файлы расширения APK сохраняются на устройстве, то, как вы читаете файлы, зависит от типа используемого вами файла. Как обсуждалось в обзоре , ваши файлы расширения могут быть любым видом файла, который вы хотите, но переименованы в отдельный формат имени файла и сохраняются в <shared-storage>/Android/obb/<package-name>/ .

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

ПРИМЕЧАНИЕ. Когда ваше приложение запускается, вы всегда должны проверять, доступно ли пространство для внешнего хранилища и читается, вызывая getExternalStorageState() . Это возвращает одну из нескольких возможных строк, которые представляют состояние внешнего хранилища. Для того, чтобы он был читаемым вашим приложением, возвращаемое значение должно быть MEDIA_MOUNTED .

Получение имен файлов

Как описано в обзоре , ваши файлы расширения APK сохраняются с использованием конкретного формата имени файла:

[main|patch].<expansion-version>.<package-name>.obb

Чтобы получить местоположение и имена ваших файлов расширения, вы должны использовать методы getExternalStorageDirectory() и getPackageName() для построения пути к вашим файлам.

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

Котлин

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()
}

Ява

// 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;
}

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

Есть много способов определить номер версии файла расширения. Одним из простых способов является сохранение версии в файле SharedPreferences при начале загрузки, запрашивая имя файла расширения с помощью метода APKExpansionPolicy класса getExpansionFileName(int index) . Затем вы можете получить код версии, прочитав файл SharedPreferences , когда вы хотите получить доступ к файлу расширения.

Для получения дополнительной информации о чтении из общего хранилища см. В документации хранения данных .

Использование библиотеки ZIP расширения APK

Пакет расширения APK на рынке Google включает в себя библиотеку APK Expansion Library (расположенная в <sdk>/extras/google/google_market_apk_expansion/zip_file/ ). Это дополнительная библиотека, которая помогает вам прочитать ваши файлы расширения, когда они сохраняются в виде ZIP -файлов. Использование этой библиотеки позволяет легко читать ресурсы из ваших файлов расширения ZIP в качестве виртуальной файловой системы.

Библиотека ZIP расширения APK включает в себя следующие классы и API:

APKExpansionSupport
Предоставляет некоторые методы для доступа к именам файлов расширения и файлов ZIP:
getAPKExpansionFiles()
Тот же метод, указанный выше, который возвращает полный путь файла в оба файла расширения.
getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion)
Возвращает ZipResourceFile представляющий сумму как основного файла, так и файла Patch. То есть, если вы указываете как mainVersion , так и patchVersion , это возвращает ZipResourceFile , который обеспечивает доступ к чтению ко всем данным, при этом данные файла патча объединены поверх основного файла.
ZipResourceFile
Представляет zip -файл на общем хранилище и выполняет всю работу, чтобы предоставить виртуальную файловую систему на основе ваших файлов zip. Вы можете получить экземпляр, используя APKExpansionSupport.getAPKExpansionZipFile() или с ZipResourceFile , пропустив его путь к вашему файлу расширения. Этот класс включает в себя множество полезных методов, но вам, как правило, вам не нужно получить доступ к большинству из них. Пара важных методов:
getInputStream(String assetPath)
Предоставляет InputStream для чтения файла в файле ZIP. assetPath должен быть пути к желаемому файлу относительно корня содержания файла Zip.
getAssetFileDescriptor(String assetPath)
Предоставляет AssetFileDescriptor для файла в файле ZIP. assetPath должен быть пути к желаемому файлу относительно корня содержания файла Zip. Это полезно для определенных API -интерфейсов Android, которые требуют AssetFileDescriptor , например, некоторые API -интерфейсы MediaPlayer .
APEZProvider
Большинству приложений не нужно использовать этот класс. Этот класс определяет ContentProvider , который маршал данные из файлов ZIP через Uri поставщика контента, чтобы предоставить доступ к файлам для определенных API Android, которые ожидают доступ Uri к медиа -файлам. Например, это полезно, если вы хотите воспроизвести видео с VideoView.setVideoURI() .

Пропуск сжатия молнии медиа -файлов

Если вы используете свои файлы расширения для хранения медиа -файлов, zip -файл по -прежнему позволяет использовать вызовы воспроизведения Android Media, которые обеспечивают элементы управления смещением и длиной (например, MediaPlayer.setDataSource() и SoundPool.load() ). Чтобы это работало, вы не должны выполнять дополнительное сжатие в медиа -файлах при создании пакетов ZIP. Например, при использовании инструмента zip вы должны использовать опцию -n , чтобы указать суффиксы файла, которые не следует сжимать:

zip -n .mp4;.ogg main_expansion media_files

Чтение из zip -файла

При использовании библиотеки ZIP расширения APK, чтение файла из вашего ZIP обычно требует следующего:

Котлин

// 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 {
    ...
}

Ява

// 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);

Приведенный выше код обеспечивает доступ к любому файлу, который существует либо в вашем основном файле расширения, либо в файле расширения исправления, считывая из объединенной карты всех файлов из обоих файлов. Все, что вам нужно для предоставления метода getAPKExpansionFile() - это ваше приложение android.content.Context и номер версии как для основного файла расширения, так и для файла расширения.

Если вы предпочитаете прочитать из определенного файла расширения, вы можете использовать конструктор ZipResourceFile с путем к желаемому файлу расширения:

Котлин

// 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 {
    ...
}

Ява

// 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);

Для получения дополнительной информации об использовании этой библиотеки для ваших файлов расширения, посмотрите на класс SampleDownloaderActivity приложения приложения, который включает в себя дополнительный код для проверки загруженных файлов с использованием CRC. Остерегайтесь, что если вы используете этот пример в качестве основы для вашей собственной реализации, требуется, чтобы вы объявили байт размером с ваши файлы расширения в массиве xAPKS .

Тестирование файлов расширения

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

Файл тестирования считывает

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

  1. На вашем устройстве создайте соответствующий каталог на общем хранилище, где Google Play сохранит ваши файлы.

    Например, если имя вашего пакета является com.example.android , вам необходимо создать каталог Android/obb/com.example.android/ в общем пространстве хранения. (Подключите ваше испытательное устройство к компьютеру, чтобы установить общее хранилище и вручную создать этот каталог.)

  2. Вручную добавьте файлы расширения в этот каталог. Убедитесь, что вы переименуете свои файлы в соответствии с форматом имени файла , который будет использовать Google Play.

    Например, независимо от типа файла, основной файл расширения для приложения com.example.android должен быть main.0300110.com.example.android.obb . Код версии может быть любым значением, которое вы хотите. Просто помните:

    • Основной файл расширения всегда начинается с main , а патч -файл начинается с patch .
    • Имя пакета всегда соответствует имени APK, к которому файл прикреплен в Google Play.
  3. Теперь, когда файлы расширения (ы) находятся на устройстве, вы можете установить и запустить свое приложение для проверки файлов расширения.

Вот некоторые напоминания об обращении с файлами расширения:

  • Не удаляйте и не переименуйте файлы расширения .obb (даже если вы распакуете данные в другое место). Это приведет к тому, что Google Play (или ваше приложение) неоднократно загружает файл расширения.
  • Не сохраняйте другие данные в свой каталог obb/ . Если вы должны распаковать некоторые данные, сохраните их в местоположение, указанное getExternalFilesDir() .

Тестирование загрузки файлов

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

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

Примечание. Ранее вы можете проверить приложение, загрузив неопубликованную версию «черновика». Эта функциональность больше не поддерживается. Вместо этого вы должны опубликовать его на внутреннем, закрытом или открытом треке тестирования. Для получения дополнительной информации см. Приложения, которые больше не поддерживаются .

Обновление вашего приложения

Одним из замечательных преимуществ использования файлов расширения в Google Play является возможность обновлять ваше приложение без повторной загрузки всех исходных активов. Поскольку Google Play позволяет вам предоставлять два файла расширения с каждым APK, вы можете использовать второй файл в качестве «патча», который предоставляет обновления и новые активы. Это избегает необходимости повторной загрузки основного файла расширения, который может быть большим и дорогим для пользователей.

Файл расширения патча технически такой же, как и основной файл расширения, и ни система Android, ни Google Play не выполняют фактическое исправление между вашим основным и патч -файлами. Код вашего приложения должен выполнять любые необходимые исправления.

Если вы используете zip -файлы в качестве файлов расширения, библиотека ZIP расширения APK , которая включена в пакет расширения APK, включает в себя возможность объединить ваш пататный файл с основным файлом расширения.

Примечание. Даже если вам нужно только внести изменения в файл расширения патча, вы все равно должны обновить APK, чтобы Google Play выполнил обновление. Если вам не требуются изменения кода в приложении, вам следует просто обновить versionCode в манифесте.

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

Вот несколько вопросов, которые следует учитывать в отношении обновлений для файлов расширения:

  • Для вашего приложения может быть только два файла расширения. Один основной файл расширения и один файл расширения патча. Во время обновления в файл Google Play удаляет предыдущую версию (и, так и ваше приложение, при выполнении ручных обновлений).
  • При добавлении файла расширения патча система Android на самом деле не исправляет ваше приложение или основной файл расширения. Вы должны спроектировать свое приложение для поддержки данных. Тем не менее, пакет расширения APK включает в себя библиотеку для использования файлов ZIP в качестве файлов расширения, которые объединяют данные из файла патча в основной файл расширения, чтобы вы могли легко прочитать все данные файла расширения.