Настройка доставки по требованию

Модули функций позволяют вам отделить определенные функции и ресурсы от базового модуля вашего приложения и включить их в ваш пакет приложений. С помощью Play Feature Delivery пользователи могут, например, позже загрузить и установить эти компоненты по требованию после того, как они уже установили базовый APK вашего приложения.

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

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

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

Эта страница поможет вам добавить модуль функций в проект вашего приложения и настроить его для доставки по требованию. Перед началом убедитесь, что вы используете Android Studio 3.5 или выше и Android Gradle Plugin 3.5.0 или выше.

Настройте новый модуль для доставки по требованию

Самый простой способ создать новый модуль функций — использовать Android Studio 3.5 или выше. Поскольку модули функций имеют неотъемлемую зависимость от базового модуля приложения, вы можете добавлять их только в существующие проекты приложений.

Чтобы добавить модуль функций в проект приложения с помощью Android Studio, выполните следующие действия:

  1. Если вы еще этого не сделали, откройте проект своего приложения в среде IDE.
  2. Выберите Файл > Создать > Новый модуль в строке меню.
  3. В диалоговом окне «Создать новый модуль» выберите «Модуль динамических функций» и нажмите «Далее» .
  4. В разделе «Настройка нового модуля» выполните следующие действия:
    1. Выберите базовый модуль приложения для вашего проекта приложения из раскрывающегося меню.
    2. Укажите имя модуля . IDE использует это имя для идентификации модуля как подпроекта Gradle в файле настроек Gradle . При сборке пакета приложений Gradle использует последний элемент имени подпроекта для внедрения атрибута <manifest split> в манифест функционального модуля .
    3. Укажите имя пакета модуля. По умолчанию Android Studio предлагает имя пакета, которое объединяет имя корневого пакета базового модуля и имя модуля, которое вы указали на предыдущем шаге.
    4. Выберите минимальный уровень API, который должен поддерживать модуль. Это значение должно совпадать со значением базового модуля.
  5. Нажмите Далее .
  6. В разделе «Параметры загрузки модуля» выполните следующие действия:

    1. Укажите заголовок модуля , используя до 50 символов. Платформа использует этот заголовок для идентификации модуля пользователям, например, когда подтверждает, хочет ли пользователь загрузить модуль. По этой причине базовый модуль вашего приложения должен включать заголовок модуля как строковый ресурс , который вы можете перевести. При создании модуля с помощью Android Studio IDE добавляет строковый ресурс в базовый модуль для вас и вставляет следующую запись в манифест функционального модуля:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. В раскрывающемся меню под Install-time include выберите Do Not include module at install-time . Android Studio внедряет следующее в манифест модуля, чтобы отразить ваш выбор:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. Установите флажок рядом с Fusing , если вы хотите, чтобы этот модуль был доступен для устройств под управлением Android 4.4 (уровень API 20) и ниже и был включен в multi-APK. Это означает, что вы можете включить поведение по требованию для этого модуля и отключить Fusing, чтобы исключить его из устройств, которые не поддерживают загрузку и установку разделенных APK. Android Studio внедряет следующее в манифест модуля, чтобы отразить ваш выбор:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. Нажмите «Готово» .

После того, как Android Studio закончит создание вашего модуля, проверьте его содержимое самостоятельно на панели Project (выберите View > Tool Windows > Project в строке меню). Код по умолчанию, ресурсы и организация должны быть аналогичны стандартному модулю приложения.

Далее вам необходимо реализовать функционал установки по требованию с использованием библиотеки Play Feature Delivery.

Включите библиотеку Play Feature Delivery Library в свой проект

Прежде чем начать, вам необходимо добавить библиотеку Play Feature Delivery Library в свой проект.

Запросить модуль по требованию

Когда вашему приложению необходимо использовать функциональный модуль, оно может запросить его, пока находится на переднем плане, через класс SplitInstallManager . При выполнении запроса вашему приложению необходимо указать имя модуля, как определено элементом split в манифесте целевого модуля. Когда вы создаете функциональный модуль с помощью Android Studio, система сборки использует указанное вами имя модуля для внедрения этого свойства в манифест модуля во время компиляции. Для получения дополнительной информации прочтите о манифестах функциональных модулей .

Например, рассмотрим приложение, которое имеет модуль по запросу для захвата и отправки графических сообщений с помощью камеры устройства, и этот модуль по запросу указывает split="pictureMessages" в своем манифесте. Следующий пример использует SplitInstallManager для запроса модуля pictureMessages (вместе с дополнительным модулем для некоторых рекламных фильтров):

Котлин

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Ява

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

Когда ваше приложение запрашивает модуль по требованию, библиотека доставки функций Play использует стратегию «запустил и забыл». То есть она отправляет запрос на загрузку модуля на платформу, но не отслеживает, была ли установка успешной. Чтобы продвинуть путь пользователя вперед после установки или изящно обработать ошибки, обязательно отслеживайте состояние запроса .

Примечание: можно запросить модуль функции, который уже установлен на устройстве. API мгновенно считает запрос выполненным, если обнаруживает, что модуль уже установлен. Кроме того, после установки модуля Google Play автоматически обновляет его. То есть, когда вы загружаете новую версию своего пакета приложений, платформа обновляет все установленные APK, которые принадлежат вашему приложению. Для получения дополнительной информации прочтите Управление обновлениями приложений .

Чтобы иметь доступ к коду и ресурсам модуля, ваше приложение должно включить SplitCompat . Обратите внимание, что SplitCompat не требуется для Android Instant Apps.

Отложить установку модулей по требованию

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

Вы можете указать модуль для загрузки позже с помощью метода deferredInstall() , как показано ниже. И, в отличие от SplitInstallManager.startInstall() , вашему приложению не нужно находиться на переднем плане, чтобы инициировать запрос на отложенную установку.

Котлин

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Ява

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

Запросы на отложенную установку выполняются по принципу «лучшее усилие», и вы не можете отслеживать их ход. Поэтому перед попыткой доступа к модулю, указанному для отложенной установки, следует проверить, установлен ли этот модуль . Если вам нужно, чтобы модуль был доступен немедленно, вместо этого используйте SplitInstallManager.startInstall() для его запроса, как показано в предыдущем разделе.

Мониторинг состояния запроса

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

Котлин

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Ява

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

Обработка ошибок запроса

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

С точки зрения кода вы должны обрабатывать сбои при загрузке или установке модуля с помощью addOnFailureListener() , как показано ниже:

Котлин

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Ява

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

В таблице ниже описаны состояния ошибок, которые может потребоваться обработать вашему приложению:

Код ошибки Описание Предлагаемые действия
ПРЕВЫШЕНИЕ_ЛИМИТА_АКТИВНЫХ_СЕАНСОВ Запрос отклонен, поскольку в данный момент выполняется загрузка как минимум одного существующего запроса. Проверьте, есть ли какие-либо запросы, которые все еще загружаются, как показано в примере выше.
МОДУЛЬ_НЕДОСТУПЕН Google Play не может найти запрошенный модуль на основе текущей установленной версии приложения, устройства и учетной записи Google Play пользователя. Если у пользователя нет доступа к модулю, сообщите ему об этом.
НЕДЕЙСТВИТЕЛЬНЫЙ_ЗАПРОС Google Play получил запрос, но он недействителен. Убедитесь, что информация, указанная в запросе, является полной и точной.
СЕАНС_НЕ_НАЙДЕН Сеанс для указанного идентификатора сеанса не найден. Если вы пытаетесь отслеживать состояние запроса по его идентификатору сеанса, убедитесь, что идентификатор сеанса указан правильно.
API_НЕ_ДОСТУПНО Библиотека Play Feature Delivery Library не поддерживается на текущем устройстве. То есть устройство не может загружать и устанавливать функции по требованию. Для устройств под управлением Android 4.4 (уровень API 20) или ниже следует включить функциональные модули во время установки с помощью свойства dist:fusing manifest. Чтобы узнать больше, прочтите о feature module manifest .
СЕТЕВАЯ_ОШИБКА Запрос не выполнен из-за сетевой ошибки. Предложите пользователю либо установить сетевое подключение, либо перейти на другую сеть.
ДОСТУП ЗАПРЕЩЕН Приложение не может зарегистрировать запрос из-за недостаточных разрешений. Обычно это происходит, когда приложение находится в фоновом режиме. Попытайтесь выполнить запрос, когда приложение вернется на передний план.
НЕСОВМЕСТИМОСТЬ_С_СУЩЕСТВУЮЩЕЙ_СЕССИЕЙ Запрос содержит один или несколько модулей, которые уже были запрошены, но еще не установлены. Либо создайте новый запрос, не включающий модули, которые ваше приложение уже запросило, либо дождитесь завершения установки всех запрошенных в данный момент модулей, прежде чем повторить запрос.

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

SERVICE_DIED Служба, ответственная за обработку запроса, прекратила работу. Повторите запрос.

Ваш SplitInstallStateUpdatedListener получает SplitInstallSessionState с этим кодом ошибки, статусом FAILED и идентификатором сеанса -1 .

НЕДОСТАТОЧНОЕ_ХРАНИЛИЩЕ На устройстве недостаточно свободного места для установки модуля функции. Уведомите пользователя о том, что у него недостаточно памяти для установки этой функции.
SPLITCOMPAT_VERIFICATION_ERROR, SPLITCOMPAT_EMULATION_ERROR, SPLITCOMPAT_COPY_ERROR SplitCompat не смог загрузить модуль функций. Эти ошибки должны автоматически устраниться после следующего перезапуска приложения.
PLAY_STORE_NOT_FOUND Приложение Play Store не установлено на устройстве. Сообщите пользователю, что для загрузки этой функции требуется приложение Play Store.
ПРИЛОЖЕНИЕ_НЕ_ВЛАДЕЕТ Приложение не установлено Google Play, и функция не может быть загружена. Эта ошибка может возникнуть только для отложенных установок. Если вы хотите, чтобы пользователь приобрел приложение в Google Play, используйте startInstall() , который может получить необходимое подтверждение пользователя .
ВНУТРЕННЯЯ_ОШИБКА В Play Store произошла внутренняя ошибка. Повторите запрос.

Если пользователь запрашивает загрузку модуля по запросу и возникает ошибка, рассмотрите возможность отображения диалогового окна, которое предоставляет пользователю два варианта: Повторить попытку (чтобы повторить запрос) и Отмена (чтобы отказаться от запроса). Для получения дополнительной поддержки вы также должны предоставить ссылку Help , которая направляет пользователей в Справочный центр Google Play .

Обработка обновлений состояния

После регистрации прослушивателя и записи идентификатора сеанса для вашего запроса используйте StateUpdatedListener.onStateUpdate() для обработки изменений состояния, как показано ниже.

Котлин

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Ява

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

Возможные состояния вашего запроса на установку описаны в таблице ниже.

Запросить состояние Описание Предлагаемые действия
В ОЖИДАНИИ Запрос принят, загрузка должна начаться в ближайшее время. Инициализируйте компоненты пользовательского интерфейса, такие как индикатор выполнения, чтобы предоставить пользователю обратную связь о ходе загрузки.
ТРЕБУЕТСЯ_ПОДТВЕРЖДЕНИЕ_ПОЛЬЗОВАТЕЛЯ Для загрузки требуется подтверждение пользователя. Чаще всего этот статус возникает, если приложение не было установлено через Google Play. Предложите пользователю подтвердить загрузку функции через Google Play. Чтобы узнать больше, перейдите в раздел о том, как получить подтверждение пользователя .
СКАЧИВАНИЕ Загрузка в процессе. Если вы предоставляете индикатор выполнения загрузки, используйте методы SplitInstallSessionState.bytesDownloaded() и SplitInstallSessionState.totalBytesToDownload() для обновления пользовательского интерфейса (см. пример кода над этой таблицей).
СКАЧАНО Устройство загрузило модуль, но установка еще не началась. Приложения должны разрешить SplitCompat иметь доступ к загруженным модулям и избегать просмотра этого состояния. Это необходимо для доступа к коду и ресурсам функционального модуля.
УСТАНОВКА В настоящее время устройство устанавливает модуль. Обновить полосу прогресса. Это состояние обычно короткое.
УСТАНОВЛЕНО Модуль установлен на устройстве. Получите доступ к коду и ресурсам в модуле для продолжения пути пользователя.

Если модуль предназначен для приложения Android Instant, работающего на Android 8.0 (уровень API 26) или выше, вам необходимо использовать splitInstallHelper для обновления компонентов приложения с помощью нового модуля .

НЕУСПЕШНЫЙ Запрос не был выполнен до установки модуля на устройство. Предложите пользователю повторить запрос или отменить его.
ОТМЕНА Устройство находится в процессе отмены запроса. Чтобы узнать больше, перейдите в раздел о том, как отменить запрос на установку .
ОТМЕНЕНО Запрос был отменен.

Получить подтверждение пользователя

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

Котлин

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Ява

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

Вы можете зарегистрировать средство запуска результатов активности, используя встроенный контракт ActivityResultContracts.StartIntentSenderForResult . См. API результатов активности .

Статус запроса обновляется в зависимости от ответа пользователя:

  • Если пользователь принимает подтверждение, статус запроса меняется на PENDING и загрузка продолжается.
  • Если пользователь отклоняет подтверждение, статус запроса меняется на CANCELED .
  • Если пользователь не сделает выбор до того, как диалог будет закрыт, статус запроса останется как REQUIRES_USER_CONFIRMATION . Ваше приложение может снова предложить пользователю выполнить запрос.

Чтобы получить обратный вызов с ответом пользователя, вы можете переопределить ActivityResultCallback, как показано ниже.

Котлин

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Ява

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

Отменить запрос на установку

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

Котлин

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Ява

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

Модули доступа

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

Однако следует отметить, что платформа в течение некоторого времени (в некоторых случаях в течение нескольких дней) после загрузки модуля имеет следующие ограничения на доступ к содержимому модуля:

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

Включить SplitCompat

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

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

Объявите SplitCompatApplication в манифесте

Самый простой способ включить SplitCompat — объявить SplitCompatApplication как подкласс Application в манифесте вашего приложения, как показано ниже:

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

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

Вызов SplitCompat во время выполнения

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

Если у вас есть пользовательский класс Application , вместо этого сделайте так, чтобы он расширял SplitCompatApplication , чтобы включить SplitCompat для вашего приложения, как показано ниже:

Котлин

class MyApplication : SplitCompatApplication() {
    ...
}

Ява

public class MyApplication extends SplitCompatApplication {
    ...
}

SplitCompatApplication просто переопределяет ContextWrapper.attachBaseContext() для включения SplitCompat.install(Context applicationContext) . Если вы не хотите, чтобы ваш класс Application расширял SplitCompatApplication , вы можете вручную переопределить метод attachBaseContext() следующим образом:

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

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

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

Включить SplitCompat для действий модуля

После включения SplitCompat для вашего базового приложения вам необходимо включить SplitCompat для каждой активности, которую ваше приложение загружает в модуле функций. Для этого используйте метод SplitCompat.installActivity() следующим образом:

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

Компоненты доступа, определенные в функциональных модулях

Запустить действие, определенное в функциональном модуле

Вы можете запускать действия, определенные в функциональных модулях, с помощью startActivity() после включения SplitCompat.

Котлин

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Ява

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

Первый параметр setClassName — это имя пакета приложения, а второй параметр — полное имя класса действия.

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

Запустить службу, определенную в функциональном модуле

Вы можете запускать службы, определенные в функциональных модулях, с помощью startService() после включения SplitCompat.

Котлин

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Ява

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

Экспорт компонента, определенного в функциональном модуле

Не следует включать экспортированные компоненты Android в дополнительные модули.

Система сборки объединяет записи манифеста для всех модулей в базовый модуль; если необязательный модуль содержит экспортированный компонент, он будет доступен еще до установки модуля и может вызвать сбой из-за отсутствия кода при вызове из другого приложения.

Это не является проблемой для внутренних компонентов; доступ к ним осуществляется только приложением, поэтому приложение может проверить, установлен ли модуль, прежде чем обращаться к компоненту.

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

Доступ к коду и ресурсам из установленных модулей

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

Код доступа из другого модуля

Доступ к базовому коду из модуля

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

Код модуля доступа из другого модуля

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

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

Для упрощения взаимодействия с объектом после создания экземпляра рекомендуется определить интерфейс в базовом модуле и его реализацию в модуле функций. Например:

Котлин

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Ява

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

Доступ к ресурсам и активам из другого модуля

После установки модуля вы можете получить доступ к ресурсам и активам внутри модуля стандартным способом, но с двумя оговорками:

  • Если вы обращаетесь к ресурсу из другого модуля, модуль не будет иметь доступа к идентификатору ресурса, хотя к ресурсу по-прежнему можно будет получить доступ по имени. Обратите внимание, что пакет, который следует использовать для ссылки на ресурс, — это пакет модуля, в котором определен ресурс.
  • Если вы хотите получить доступ к ресурсам или активам, которые существуют в недавно установленном модуле из другого установленного модуля вашего приложения, вы должны сделать это с помощью контекста приложения . Контекст компонента, который пытается получить доступ к ресурсам, еще не будет обновлен. В качестве альтернативы вы можете пересоздать этот компонент (например, вызвав Activity.recreate() ) или переустановить SplitCompat на нем после установки модуля функции.

Загрузите собственный код в приложение, используя доставку по требованию

Мы рекомендуем использовать ReLinker для загрузки всех собственных библиотек при использовании доставки функциональных модулей по требованию. ReLinker устраняет проблему с загрузкой собственных библиотек после установки функционального модуля. Подробнее о ReLinker можно узнать в разделе Android JNI Tips .

Загрузить собственный код из дополнительного модуля

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

Если вы используете System.loadLibrary() для загрузки собственного кода, а ваша собственная библиотека имеет зависимость от другой библиотеки в модуле, вы должны вручную сначала загрузить эту другую библиотеку. Если вы используете ReLinker, эквивалентная операция — Relinker.recursively().loadLibrary() .

Если вы используете dlopen() в нативном коде для загрузки библиотеки, определенной в необязательном модуле, это не будет работать с относительными путями библиотеки. Лучшим решением будет извлечь абсолютный путь библиотеки из кода Java через ClassLoader.findLibrary() , а затем использовать его в вызове dlopen() . Сделайте это до ввода нативного кода или используйте вызов JNI из нативного кода в Java.

Доступ к установленным приложениям Android Instant

После того, как модуль Android Instant App сообщает о состоянии INSTALLED , вы можете получить доступ к его коду и ресурсам с помощью обновленного приложения Context . Контекст, который ваше приложение создает перед установкой модуля (например, тот, который уже сохранен в переменной), не содержит содержимое нового модуля. Но новый контекст содержит — его можно получить, например, с помощью createPackageContext .

Котлин

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Ява

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

Приложения Android Instant на Android 8.0 и выше

При запросе модуля по требованию для Android Instant App на Android 8.0 (уровень API 26) и выше, после того как запрос на установку сообщается как INSTALLED , вам необходимо обновить приложение с контекстом нового модуля с помощью вызова SplitInstallHelper.updateAppInfo(Context context) . В противном случае приложение еще не знает о коде и ресурсах модуля. После обновления метаданных приложения вам следует загрузить содержимое модуля во время следующего события основного потока, вызвав новый Handler , как показано ниже:

Котлин

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Ява

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

Загрузить библиотеки C/C++

Если вы хотите загрузить библиотеки C/C++ из модуля, который устройство уже загрузило в Instant App, используйте SplitInstallHelper.loadLibrary(Context context, String libName) , как показано ниже:

Котлин

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib)
                ...
            }
        }
    }
}

Ява

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib);
                ...
        }
    }
}

Известные ограничения

  • Невозможно использовать Android WebView в активности, которая обращается к ресурсам или активам из необязательного модуля. Это связано с несовместимостью WebView и SplitCompat на уровне API Android 28 и ниже.
  • Вы не можете кэшировать объекты Android ApplicationInfo , их содержимое или объекты, которые содержат их в вашем приложении. Вы всегда должны извлекать эти объекты по мере необходимости из контекста приложения. Кэширование таких объектов может привести к сбою приложения при установке модуля функции.

Управление установленными модулями

Чтобы проверить, какие модули функций в данный момент установлены на устройстве, можно вызвать SplitInstallManager.getInstalledModules() , который возвращает Set<String> имен установленных модулей, как показано ниже.

Котлин

val installedModules: Set<String> = splitInstallManager.installedModules

Ява

Set<String> installedModules = splitInstallManager.getInstalledModules();

Удалить модули

Вы можете запросить у устройства удаление модулей, вызвав SplitInstallManager.deferredUninstall(List<String> moduleNames) , как показано ниже.

Котлин

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Ява

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

Удаление модулей не происходит немедленно. То есть устройство удаляет их в фоновом режиме по мере необходимости для экономии места на диске. Вы можете подтвердить, что устройство удалило модуль, вызвав SplitInstallManager.getInstalledModules() и проверив результат, как описано в предыдущем разделе.

Загрузите дополнительные языковые ресурсы

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

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

Котлин

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Ява

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

Запрос обрабатывается так же, как если бы это был запрос на модуль функций. То есть вы можете отслеживать состояние запроса , как обычно.

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

Котлин

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Ява

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Доступ к загруженным языковым ресурсам

Чтобы получить доступ к загруженным языковым ресурсам, вашему приложению необходимо запустить метод SplitCompat.installActivity() в методе attachBaseContext() каждого действия, которому требуется доступ к этим ресурсам, как показано ниже.

Котлин

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

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

Котлин

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

Чтобы эти изменения вступили в силу, вам придется заново создать вашу активность после установки нового языка и готовности к использованию. Вы можете использовать метод Activity#recreate() .

Котлин

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Ява

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

Удалить дополнительные языковые ресурсы

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

Котлин

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Ява

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

Затем вы можете решить, какие языки следует удалить, используя метод deferredLanguageUninstall() , как показано ниже.

Котлин

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Ява

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Локально тестировать установку модулей

Библиотека доставки функций Play позволяет вам локально протестировать способность вашего приложения выполнять следующие действия без подключения к Play Store:

На этой странице описывается, как развернуть разделенные APK-файлы вашего приложения на тестовом устройстве, чтобы Play Feature Delivery автоматически использовал эти APK-файлы для имитации запроса, загрузки и установки модулей из Play Store.

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

  • Загрузите и установите последнюю версию bundletool . bundletool вам понадобится для создания нового набора устанавливаемых APK из пакета вашего приложения.

Создайте набор APK

Если вы еще этого не сделали, создайте разделенные APK-файлы вашего приложения следующим образом:

  1. Создайте пакет приложений для своего приложения, используя один из следующих методов:
  2. Используйте bundletool для генерации набора APK для всех конфигураций устройств с помощью следующей команды:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

Флаг --local-testing включает метаданные в манифесты APK, которые позволяют библиотеке доставки функций Play использовать локальные разделенные APK для тестирования установки модулей функций без подключения к Play Store.

Разверните свое приложение на устройстве

После того, как вы создадите набор APK с помощью флага --local-testing , используйте bundletool для установки базовой версии вашего приложения и переноса дополнительных APK в локальное хранилище вашего устройства. Вы можете выполнить оба действия с помощью следующей команды:

bundletool install-apks --apks my_app.apks

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

Имитация сетевой ошибки

Для имитации установки модулей из Play Store библиотека Play Feature Delivery использует альтернативу SplitInstallManager , называемую FakeSplitInstallManager , для запроса модуля. Когда вы используете bundletool с флагом --local-testing для создания набора APK и развертывания их на вашем тестовом устройстве, он включает метаданные, которые предписывают библиотеке Play Feature Delivery автоматически переключать вызовы API вашего приложения для вызова FakeSplitInstallManager вместо SplitInstallManager .

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

Котлин

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Ява

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);
,

Модули функций позволяют вам отделить определенные функции и ресурсы от базового модуля вашего приложения и включить их в ваш пакет приложений. С помощью Play Feature Delivery пользователи могут, например, позже загрузить и установить эти компоненты по требованию после того, как они уже установили базовый APK вашего приложения.

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

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

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

Эта страница помогает добавить модуль функции в проект вашего приложения и настроить его для доставки спроса. Прежде чем начать, убедитесь, что вы используете Android Studio 3.5 или выше и Android Gradle Plugin 3.5.0 или выше.

Настройте новый модуль для доставки по требованию

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

Чтобы добавить модуль функции в проект вашего приложения с помощью Android Studio, выполните следующее:

  1. Если вы еще этого не сделали, откройте свой проект приложения в IDE.
  2. Выберите Файл> Новый> Новый модуль из строки меню.
  3. В диалоговом окне «Создать новый модуль» выберите динамический модуль функции и нажмите «Далее» .
  4. В разделе «Настроить свой новый модуль» выполните следующее:
    1. Выберите базовый модуль приложения для проекта вашего приложения в раскрывающемся меню.
    2. Укажите имя модуля . IDE использует это имя для идентификации модуля как подпроекта Gradle в вашем файле настроек Gradle . При создании пакета приложений Gradle использует последний элемент имени подпроекта, чтобы ввести атрибут <manifest split> в манифест модуля функции .
    3. Укажите имя пакета модуля. По умолчанию Android Studio предлагает имя пакета, которое объединяет имя корневого пакета базового модуля и имя модуля, которое вы указали на предыдущем шаге.
    4. Выберите минимальный уровень API, который вы хотите, чтобы модуль поддерживал. Это значение должно соответствовать значению базового модуля.
  5. Нажмите Далее .
  6. В разделе «Параметры загрузки модуля» выполните следующее:

    1. Укажите заголовок модуля , используя до 50 символов. Платформа использует этот заголовок, чтобы идентифицировать модуль для пользователей, когда, например, подтверждая, хочет ли пользователь загрузить модуль. По этой причине базовый модуль вашего приложения должно включать заголовок модуля как строковый ресурс , который вы можете перевести. При создании модуля с использованием Android Studio IDE добавляет ресурс String в базовый модуль для вас и вводит следующую запись в Manifest модуля функции:

      <dist:module
          ...
          dist:title="@string/feature_title">
      </dist:module>
      
    2. В раскрывающемся меню под включением времени установки выберите «Не включать модуль во время установки» . Android Studio вводит следующее в манифесте модуля, чтобы отразить ваш выбор:

      <dist:module ... >
        <dist:delivery>
            <dist:on-demand/>
        </dist:delivery>
      </dist:module>
      
    3. Установите флажок рядом с слиянием , если вы хотите, чтобы этот модуль был доступен для устройств, работающих на Android 4.4 (API-уровне 20) и более низком и включенном в мульти-APK. Это означает, что вы можете включить поведение по требованию для этого модуля и отключить слияние, чтобы опустить его с устройств, которые не поддерживают загрузку и установку разделенных APK. Android Studio вводит следующее в манифесте модуля, чтобы отразить ваш выбор:

      <dist:module ...>
          <dist:fusing dist:include="true | false" />
      </dist:module>
      
  7. Нажмите «Готово» .

После того, как Android Studio заканчивает создание вашего модуля, проверьте его содержимое на панели Project (выберите «Вид»> «Windows»> «Проект» из строки меню). Код, ресурсы и организация по умолчанию должны быть аналогичны стандартным модулю приложения.

Затем вам нужно будет реализовать функциональность установки по запросу, используя библиотеку доставки функций Play.

Включите библиотеку доставки функций Play в свой проект

Прежде чем вы сможете начать, вам нужно сначала добавить библиотеку доставки функций Play в свой проект.

Запросить модуль по требованию

Когда ваше приложение необходимо использовать модуль функций, оно может запросить один, пока оно находится на переднем плане через класс SplitInstallManager . При выполнении запроса ваше приложение должно указать имя модуля, как определено split элементом в манифесте целевого модуля. Когда вы создаете модуль функций с использованием Android Studio, система сборки использует имя модуля, которое вы предоставляете, чтобы ввести это свойство в манифест модуля во время компиляции. Для получения дополнительной информации прочитайте о манифестах модуля функции .

Например, рассмотрим приложение, которое имеет модуль по требованию для захвата и отправки сообщений с изображениями, используя камеру устройства, и этот модуль по требованию определяет split="pictureMessages" в его манифесте. В следующем образце используется SplitInstallManager для запроса модуля pictureMessages (вместе с дополнительным модулем для некоторых рекламных фильтров):

Котлин

// Creates an instance of SplitInstallManager.
val splitInstallManager = SplitInstallManagerFactory.create(context)

// Creates a request to install a module.
val request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build()

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener { sessionId -> ... }
    .addOnFailureListener { exception ->  ... }

Ява

// Creates an instance of SplitInstallManager.
SplitInstallManager splitInstallManager =
    SplitInstallManagerFactory.create(context);

// Creates a request to install a module.
SplitInstallRequest request =
    SplitInstallRequest
        .newBuilder()
        // You can download multiple on demand modules per
        // request by invoking the following method for each
        // module you want to install.
        .addModule("pictureMessages")
        .addModule("promotionalFilters")
        .build();

splitInstallManager
    // Submits the request to install the module through the
    // asynchronous startInstall() task. Your app needs to be
    // in the foreground to submit the request.
    .startInstall(request)
    // You should also be able to gracefully handle
    // request state changes and errors. To learn more, go to
    // the section about how to Monitor the request state.
    .addOnSuccessListener(sessionId -> { ... })
    .addOnFailureListener(exception -> { ... });

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

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

Чтобы иметь доступ к коду и ресурсам модуля, ваше приложение необходимо для включения SplitCompat . Обратите внимание, что SplitCompat не требуется для Android Instant Apps.

Отложить установку модулей по требованию

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

Вы можете указать модуль, который будет загружаться позже, используя метод deferredInstall() , как показано ниже. И, в отличие от SplitInstallManager.startInstall() , ваше приложение не должно быть на переднем плане, чтобы инициировать запрос на отсроченную установку.

Котлин

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(listOf("promotionalFilters"))

Ява

// Requests an on demand module to be downloaded when the app enters
// the background. You can specify more than one module at a time.
splitInstallManager.deferredInstall(Arrays.asList("promotionalFilters"));

Запросы на отсроченные установки наиболее эффективны, и вы не можете отслеживать их прогресс. Таким образом, прежде чем попытаться получить доступ к модулю, который вы указали для отложенной установки, вы должны проверить, что модуль был установлен . Если вам нужно немедленно доступен модуль, вместо этого используйте SplitInstallManager.startInstall() , чтобы запросить его, как показано в предыдущем разделе.

Следите за состоянием запроса

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

Котлин

// Initializes a variable to later track the session ID for a given request.
var mySessionId = 0

// Creates a listener for request status updates.
val listener = SplitInstallStateUpdatedListener { state ->
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
}

// Registers the listener.
splitInstallManager.registerListener(listener)

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener { sessionId -> mySessionId = sessionId }
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener { exception ->
        // Handle request errors.
    }

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener)

Ява

// Initializes a variable to later track the session ID for a given request.
int mySessionId = 0;

// Creates a listener for request status updates.
SplitInstallStateUpdatedListener listener = state -> {
    if (state.sessionId() == mySessionId) {
      // Read the status of the request to handle the state update.
    }
};

// Registers the listener.
splitInstallManager.registerListener(listener);

...

splitInstallManager
    .startInstall(request)
    // When the platform accepts your request to download
    // an on demand module, it binds it to the following session ID.
    // You use this ID to track further status updates for the request.
    .addOnSuccessListener(sessionId -> { mySessionId = sessionId; })
    // You should also add the following listener to handle any errors
    // processing the request.
    .addOnFailureListener(exception -> {
        // Handle request errors.
    });

// When your app no longer requires further updates, unregister the listener.
splitInstallManager.unregisterListener(listener);

Обработка ошибок запроса

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

По коду, вы должны обрабатывать сбои, загружающие или устанавливая модуль с использованием addOnFailureListener() , как показано ниже:

Котлин

splitInstallManager
    .startInstall(request)
    .addOnFailureListener { exception ->
        when ((exception as SplitInstallException).errorCode) {
            SplitInstallErrorCode.NETWORK_ERROR -> {
                // Display a message that requests the user to establish a
                // network connection.
            }
            SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED -> checkForActiveDownloads()
            ...
        }
    }

fun checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .sessionStates
        .addOnCompleteListener { task ->
            if (task.isSuccessful) {
                // Check for active sessions.
                for (state in task.result) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        }
}

Ява

splitInstallManager
    .startInstall(request)
    .addOnFailureListener(exception -> {
        switch (((SplitInstallException) exception).getErrorCode()) {
            case SplitInstallErrorCode.NETWORK_ERROR:
                // Display a message that requests the user to establish a
                // network connection.
                break;
            case SplitInstallErrorCode.ACTIVE_SESSIONS_LIMIT_EXCEEDED:
                checkForActiveDownloads();
            ...
    });

void checkForActiveDownloads() {
    splitInstallManager
        // Returns a SplitInstallSessionState object for each active session as a List.
        .getSessionStates()
        .addOnCompleteListener( task -> {
            if (task.isSuccessful()) {
                // Check for active sessions.
                for (SplitInstallSessionState state : task.getResult()) {
                    if (state.status() == SplitInstallSessionStatus.DOWNLOADING) {
                        // Cancel the request, or request a deferred installation.
                    }
                }
            }
        });
}

В таблице ниже описываются состояния ошибки, которое может потребоваться обработать ваше приложение:

Код ошибки Описание Предлагаемые действия
Active_sessions_limit_exeeded Запрос отклоняется, потому что существует как минимум один существующий запрос, который в настоящее время загружается. Проверьте, есть ли какие -либо запросы, которые все еще загружаются, как показано в примере выше.
Module_unavailable Google Play не может найти запрошенный модуль на основе текущей установленной версии приложения, устройства и учетной записи Google Play. Если пользователь не имеет доступа к модулю, уведомите его.
Invalid_Request Google Play получил запрос, но запрос недопустим. Убедитесь, что информация, включенная в запрос, является полной и точной.
СЕАНС_НЕ_НАЙДЕН Сессия для данного идентификатора сеанса не была найдена. Если вы пытаетесь отслеживать состояние запроса по идентификатору сеанса, убедитесь, что идентификатор сеанса является правильным.
Api_not_available Библиотека доставки функций Play не поддерживается на текущем устройстве. То есть устройство не может загружать и устанавливать функции по требованию. Для устройств, работающих на Android 4.4 (API -уровне 20) или ниже, вы должны включать модули функций во время установки с использованием свойства dist:fusing Manifest. Чтобы узнать больше, прочитайте о манифесте модуля функции .
Network_error Запрос не удался из -за сетевой ошибки. Предложите пользователю либо установить сетевое соединение, либо изменить в другую сеть.
ДОСТУП ЗАПРЕЩЕН Приложение не может зарегистрировать запрос из -за недостаточных разрешений. Обычно это происходит, когда приложение находится в фоновом режиме. Попробуйте запрос, когда приложение возвращается на передний план.
Несовместимый_with_existing_session Запрос содержит один или несколько модулей, которые уже были запрошены, но еще не были установлены. Либо создайте новый запрос, который не включает модули, которые ваше приложение уже запрашивало, либо дождитесь, пока все запрашиваемые в настоящее время модули завершат установку перед повторной попыткой запроса.

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

Service_died Служба, ответственная за обработку запроса, умерла. Повторите запрос.

Ваш SplitInstallStateUpdatedListener получает SplitInstallSessionState с этим кодом ошибки, FAILED состояния и идентификатором сеанса -1 .

Недостаточная_storage Устройство не имеет достаточно бесплатного хранения для установки модуля функции. Уведомите пользователя, что у него недостаточно хранилища для установки этой функции.
Splitcompat_verification_error, splitcompat_emulation_error, splitcompat_copy_error SplitCompat не может загрузить модуль функции. Эти ошибки должны автоматически разрешаться после перезапуска следующего приложения.
Play_store_not_found Приложение Play Store не установлено на устройстве. Пусть пользователь знает, что приложение Play Store необходимо для загрузки этой функции.
App_not_owned Приложение не было установлено Google Play, и функция не может быть загружена. Эта ошибка может возникнуть только для отложенных установок. Если вы хотите, чтобы пользователь получил приложение в Google Play, используйте startInstall() , который может получить необходимое подтверждение пользователя .
ВНУТРЕННЯЯ_ОШИБКА Внутренняя ошибка произошла в магазине Play. Повторите запрос.

Если пользователь запрашивает загрузку модуля по запросу и возникает ошибка, рассмотрите возможность отображения диалога, который предоставляет два варианта пользователя: попробуйте еще раз (который попытается снова сделать запрос) и отменить (что отказывается от запроса). Для получения дополнительной поддержки вы также должны предоставить справочную ссылку, которая направляет пользователей в справочный центр Google Play .

Обрабатывать государственные обновления

После регистрации слушателя и записывает идентификатор сеанса для вашего запроса, используйте StateUpdatedListener.onStateUpdate() для обработки изменений состояния, как показано ниже.

Котлин

override fun onStateUpdate(state : SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIED) {
       // Retry the request.
       return
    }
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.DOWNLOADING -> {
              val totalBytes = state.totalBytesToDownload()
              val progress = state.bytesDownloaded()
              // Update progress bar.
            }
            SplitInstallSessionStatus.INSTALLED -> {

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
            }
        }
    }
}

Ява

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.FAILED
        && state.errorCode() == SplitInstallErrorCode.SERVICE_DIES) {
       // Retry the request.
       return;
    }
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.DOWNLOADING:
              int totalBytes = state.totalBytesToDownload();
              int progress = state.bytesDownloaded();
              // Update progress bar.
              break;

            case SplitInstallSessionStatus.INSTALLED:

              // After a module is installed, you can start accessing its content or
              // fire an intent to start an activity in the installed module.
              // For other use cases, see access code and resources from installed modules.

              // If the request is an on demand module for an Android Instant App
              // running on Android 8.0 (API level 26) or higher, you need to
              // update the app context using the SplitInstallHelper API.
        }
    }
}

Возможные состояния для вашего запроса на установку описаны в таблице ниже.

Запросить состояние Описание Предлагаемые действия
В ОЖИДАНИИ Запрос был принят, и загрузка должна начаться в ближайшее время. Инициализируйте компоненты пользовательского интерфейса, такие как панель хода прогресса, чтобы предоставить пользователя отзывы о загрузке.
TERSH_USER_CONFIRMATION Загрузка требует подтверждения пользователя. Чаще всего этот статус происходит, если приложение не было установлено через Google Play. Предложите пользователю подтвердить загрузку функции через Google Play. Чтобы узнать больше, перейдите в раздел о том, как получить подтверждение пользователя .
СКАЧИВАНИЕ Скачать в процессе. Если вы предоставите строку прогресса для загрузки, используйте SplitInstallSessionState.bytesDownloaded() и SplitInstallSessionState.totalBytesToDownload() для обновления пользовательского интерфейса (см. Пример кода выше этой таблицы).
СКАЧАНО Устройство загрузило модуль, но установка еще не началась. Приложения должны позволить SplitCompat иметь доступ к загруженным модулям и избегать видеть это состояние. Это требуется для доступа к коду и ресурсам модуля функции.
УСТАНОВКА Устройство в настоящее время устанавливает модуль. Обновите планку прогресса. Это состояние обычно короткое.
УСТАНОВЛЕНО Модуль установлен на устройстве. Код доступа и ресурс в модуле , чтобы продолжить путешествие пользователя.

Если модуль предназначен для приложения Android Instant, работающего на Android 8.0 (API -уровне 26) или выше, вам необходимо использовать splitInstallHelper для обновления компонентов приложения с новым модулем .

НЕУСПЕШНЫЙ Запрос не удался до того, как модуль был установлен на устройстве. Предложите пользователю либо повторить запрос, либо отменить его.
ОТМЕНА Устройство находится в процессе отмены запроса. Чтобы узнать больше, перейдите в раздел о том, как отменить запрос на установку .
ОТМЕНЕНО Запрос был отменен.

Получите подтверждение пользователя

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

Котлин

override fun onSessionStateUpdate(state: SplitInstallSessionState) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher)
    }
    ...
 }

Ява

@Override void onSessionStateUpdate(SplitInstallSessionState state) {
    if (state.status() == SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION) {
        // Displays a confirmation for the user to confirm the request.
        splitInstallManager.startConfirmationDialogForResult(
          state,
          // an activity result launcher registered via registerForActivityResult
          activityResultLauncher);
    }
    ...
 }

Вы можете зарегистрировать запуск результата активности, используя строительный контракт с ActivityResultContracts.StartIntentSenderForResult . См. Результат активности API .

Статус запроса обновляется в зависимости от ответа пользователя:

  • Если пользователь принимает подтверждение, статус запроса изменяется в PENDING и доходы загрузки.
  • Если пользователь опровергает подтверждение, статус запроса изменяется в CANCELED .
  • Если пользователь не делает выбор до уничтожения диалога, статус запроса остается как REQUIRES_USER_CONFIRMATION . Ваше приложение может привлечь пользователя снова, чтобы заполнить запрос.

Чтобы получить обратный вызов с ответом пользователя, вы можете переопределить ActivityResultCallback, как показано ниже.

Котлин

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult -> {
        // Handle the user's decision. For example, if the user selects "Cancel",
        // you may want to disable certain functionality that depends on the module.
    }
}

Ява

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // Handle the user's decision. For example, if the user selects "Cancel",
            // you may want to disable certain functionality that depends on the module.
        }
    });

Отменить запрос на установку

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

Котлин

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId)

Ява

splitInstallManager
    // Cancels the request for the given session ID.
    .cancelInstall(mySessionId);

Доступ к модулям

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

Вы должны отметить, однако, что платформа испытывает следующие ограничения на доступ к содержимому модуля в течение некоторого времени (в некоторых случаях, в некоторых случаях) после загрузки модуля:

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

Включить сплиткомпат

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

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

Объявить SplatCompatApplication в манифесте

Самый простой способ включения SplitCompat - это объявить SplitCompatApplication как подкласс Application в манифесте вашего приложения, как показано ниже:

<application
    ...
    android:name="com.google.android.play.core.splitcompat.SplitCompatApplication">
</application>

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

Вызвать SplatCompat во время выполнения

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

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

Котлин

class MyApplication : SplitCompatApplication() {
    ...
}

Ява

public class MyApplication extends SplitCompatApplication {
    ...
}

SplitCompatApplication просто переопределяет ContextWrapper.attachBaseContext() чтобы включить SplitCompat.install(Context applicationContext) . Если вы не хотите, чтобы ваш класс Application расширил SplitCompatApplication , вы можете переопределить метод attachBaseContext() вручную, следующим образом:

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of future on demand modules using SplitCompat.
    SplitCompat.install(this);
}

Если ваш модуль по требованию совместим как с мгновенными приложениями, так и с установленными приложениями, вы можете вызвать условное вызов SplitCompat, следующим образом:

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this)
    }
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    if (!InstantApps.isInstantApp(this)) {
        SplitCompat.install(this);
    }
}

Включить SplitCompat для модульных действий

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

Котлин

override fun attachBaseContext(base: Context) {
    super.attachBaseContext(base)
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // Emulates installation of on demand modules using SplitCompat.
    SplitCompat.installActivity(this);
}

Компоненты доступа, определенные в модулях функций

Запустите действие, определенное в модуле функции

Вы можете запустить действия, определенные в модулях функций с использованием startActivity() после включения SplitCompat.

Котлин

startActivity(Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...))

Ява

startActivity(new Intent()
  .setClassName("com.package", "com.package.module.MyActivity")
  .setFlags(...));

Первым параметром для setClassName является имя пакета приложения, а второй параметр - это полное имя класса деятельности.

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

Запустите службу, определенную в модуле функции

Вы можете запустить службы, определенные в модулях функций, используя startService() после включения SplitCompat.

Котлин

startService(Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...))

Ява

startService(new Intent()
  .setClassName("com.package", "com.package.module.MyService")
  .setFlags(...));

Экспорт компонента, определенного в модуле функции

Вы не должны включать экспортируемые компоненты Android в дополнительные модули.

Система сборки объединяет ведущие записи для всех модулей в базовый модуль; Если дополнительный модуль содержал экспортированный компонент, он был бы доступен еще до того, как модуль будет установлен и может вызвать сбой из -за отсутствующего кода при вызове из другого приложения.

Это не проблема для внутренних компонентов; Доступ только к приложению, поэтому приложение может проверить, что модуль установлен перед доступом к компоненту.

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

Код доступа и ресурсы из установленных модулей

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

Код доступа из другого модуля

Доступ к базовому коду из модуля

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

Код модуля доступа из другого модуля

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

Вы должны опасаться, как часто это происходит из -за затрат на производительность размышлений. Для сложных вариантов использования используйте рамки впрыска зависимостей, такие как Dagger 2, чтобы гарантировать один вызов отражения на срок службы приложения.

Чтобы упростить взаимодействие с объектом после экземпляра, рекомендуется определить интерфейс в базовом модуле и его реализацию в модуле функции. Например:

Котлин

// In the base module
interface MyInterface {
  fun hello(): String
}

// In the feature module
object MyInterfaceImpl : MyInterface {
  override fun hello() = "Hello"
}

// In the base module, where we want to access the feature module code
val stringFromModule = (Class.forName("com.package.module.MyInterfaceImpl")
    .kotlin.objectInstance as MyInterface).hello();

Ява

// In the base module
public interface MyInterface {
  String hello();
}

// In the feature module
public class MyInterfaceImpl implements MyInterface {
  @Override
  public String hello() {
    return "Hello";
  }
}

// In the base module, where we want to access the feature module code
String stringFromModule =
   ((MyInterface) Class.forName("com.package.module.MyInterfaceImpl").getConstructor().newInstance()).hello();

Доступ к ресурсам и активам из другого модуля

После установки модуля вы можете получить доступ к ресурсам и активам в модуле стандартным способом, с двумя предостережениями:

  • Если вы получаете доступ к ресурсу из другого модуля, модуль не будет иметь доступа к идентификатору ресурса, хотя ресурс все еще можно получить по имени. Обратите внимание, что пакет для использования для ссылки на ресурс является пакетом модуля, где определяется ресурс.
  • Если вы хотите получить доступ к активам или ресурсам, которые существуют в недавно установленном модуле из другого установленного модуля вашего приложения, вы должны сделать это , используя контекст приложения . Контекст компонента, который пытается получить доступ к ресурсам, еще не будет обновлен. В качестве альтернативы, вы можете воссоздать этот компонент (например, вызов Activity.reecreate () ) или переустановить на нем SplatCompat после установки модуля функции.

Загрузите собственный код в приложение, используя доставку по требованию

Мы рекомендуем использовать Relinker для загрузки всех ваших собственных библиотек при использовании доставки модулей функций по требованию. Relinker устраняет проблему при загрузке собственных библиотек после установки модуля функции. Вы можете узнать больше о Selinker в советах Android JNI .

Загрузите собственный код из необязательного модуля

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

Если вы используете System.loadLibrary() для загрузки своего собственного кода, а ваша собственная библиотека зависит от другой библиотеки в модуле, вы должны сначала загрузить эту другую библиотеку. Если вы используете RELINKER, эквивалентная операция - Relinker.recursively().loadLibrary() .

Если вы используете dlopen() в собственном коде для загрузки библиотеки, определенной в необязательного модуля, он не будет работать с относительными путями библиотеки. Лучшее решение - получить абсолютный путь библиотеки из кода Java через ClassLoader.findLibrary() , а затем использовать его в вашем вызове dlopen() . Сделайте это перед входом на собственный код или используйте вызов JNI из вашего родного кода в Java.

Доступ к установленным приложениям Android Instrant

После того, как модуль Android Instant сообщает о INSTALLED , вы можете получить доступ к его коду и ресурсам, используя обновленный контекст приложения. Контекст, который создает ваше приложение перед установкой модуля (например, тот, который уже хранится в переменной), не содержит содержания нового модуля. Но новый контекст - это можно получить, например, с использованием createPackageContext .

Котлин

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                val newContext = context.createPackageContext(context.packageName, 0)
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                val am = newContext.assets
            }
        }
    }
}

Ява

// Generate a new context as soon as a request for a new module
// reports as INSTALLED.
@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // If you use AssetManager to access your app’s raw asset files, you’ll need
                // to generate a new AssetManager instance from the updated context.
                AssetManager am = newContext.getAssets();
        }
    }
}

Android Instrant Apps на Android 8.0 и выше

При запросе модуля по требованию для приложения Android Instant на Android 8.0 (API -уровне 26) и выше, после отчетов о запросах на INSTALLED , вам необходимо обновить приложение с помощью контекста нового модуля с помощью вызова для SplitInstallHelper.updateAppInfo(Context context) . В противном случае приложение еще не осведомлено о коде и ресурсах модуля. После обновления метаданных приложения вы должны загрузить содержимое модуля во время следующего события основного потока, вызывая новый Handler , как показано ниже:

Котлин

override fun onStateUpdate(state: SplitInstallSessionState ) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            ...
            SplitInstallSessionStatus.INSTALLED -> {
                // You need to perform the following only for Android Instant Apps
                // running on Android 8.0 (API level 26) and higher.
                if (BuildCompat.isAtLeastO()) {
                    // Updates the app’s context with the code and resources of the
                    // installed module.
                    SplitInstallHelper.updateAppInfo(context)
                    Handler().post {
                        // Loads contents from the module using AssetManager
                        val am = context.assets
                        ...
                    }
                }
            }
        }
    }
}

Ява

@Override
public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            ...
            case SplitInstallSessionStatus.INSTALLED:
            // You need to perform the following only for Android Instant Apps
            // running on Android 8.0 (API level 26) and higher.
            if (BuildCompat.isAtLeastO()) {
                // Updates the app’s context with the code and resources of the
                // installed module.
                SplitInstallHelper.updateAppInfo(context);
                new Handler().post(new Runnable() {
                    @Override public void run() {
                        // Loads contents from the module using AssetManager
                        AssetManager am = context.getAssets();
                        ...
                    }
                });
            }
        }
    }
}

Загрузить библиотеки C/C ++

Если вы хотите загрузить библиотеки C/C ++ из модуля, который устройство уже загрузило в мгновенное приложение, используйте SplitInstallHelper.loadLibrary(Context context, String libName) , как показано ниже:

Котлин

override fun onStateUpdate(state: SplitInstallSessionState) {
    if (state.sessionId() == mySessionId) {
        when (state.status()) {
            SplitInstallSessionStatus.INSTALLED -> {
                // Updates the app’s context as soon as a module is installed.
                val newContext = context.createPackageContext(context.packageName, 0)
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib)
                ...
            }
        }
    }
}

Ява

public void onStateUpdate(SplitInstallSessionState state) {
    if (state.sessionId() == mySessionId) {
        switch (state.status()) {
            case SplitInstallSessionStatus.INSTALLED:
                // Updates the app’s context as soon as a module is installed.
                Context newContext = context.createPackageContext(context.getPackageName(), 0);
                // To load C/C++ libraries from an installed module, use the following API
                // instead of System.load().
                SplitInstallHelper.loadLibrary(newContext, my-cpp-lib);
                ...
        }
    }
}

Известные ограничения

  • Невозможно использовать Android WebView в действии, которое обращается к ресурсам или активам из необязательного модуля. Это связано с несовместимостью между WebView и SplitCompat на уровне API API Android и ниже.
  • Вы не можете кэшировать объекты Android ApplicationInfo , их содержимое или объекты, которые содержат их в вашем приложении. Вы всегда должны получать эти объекты по мере необходимости из контекста приложения. Кэширование таких объектов может привести к сбою приложения при установке модуля функции.

Управление установленными модулями

Чтобы проверить, какие модули функций в настоящее время установлены на устройстве, вы можете вызвать SplitInstallManager.getInstalledModules() , который возвращает Set<String> из имен установленных модулей, как показано ниже.

Котлин

val installedModules: Set<String> = splitInstallManager.installedModules

Ява

Set<String> installedModules = splitInstallManager.getInstalledModules();

Удалить модули

Вы можете попросить устройство удалить модули, вызывая SplitInstallManager.deferredUninstall(List<String> moduleNames) , как показано ниже.

Котлин

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(listOf("pictureMessages", "promotionalFilters"))

Ява

// Specifies two feature modules for deferred uninstall.
splitInstallManager.deferredUninstall(Arrays.asList("pictureMessages", "promotionalFilters"));

Модуль Установки не происходит сразу. То есть устройство удаляет их в фоновом режиме по мере необходимости для сохранения места для хранения. Вы можете подтвердить, что устройство удалило модуль, вызывая SplitInstallManager.getInstalledModules() и осмотрив результат, как описано в предыдущем разделе.

Скачать дополнительные языковые ресурсы

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

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

Котлин

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply()
...

// Creates a request to download and install additional language resources.
val request = SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build()

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request)

Ява

// Captures the user’s preferred language and persists it
// through the app’s SharedPreferences.
sharedPrefs.edit().putString(LANGUAGE_SELECTION, "fr").apply();
...

// Creates a request to download and install additional language resources.
SplitInstallRequest request =
    SplitInstallRequest.newBuilder()
        // Uses the addLanguage() method to include French language resources in the request.
        // Note that country codes are ignored. That is, if your app
        // includes resources for “fr-FR” and “fr-CA”, resources for both
        // country codes are downloaded when requesting resources for "fr".
        .addLanguage(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
        .build();

// Submits the request to install the additional language resources.
splitInstallManager.startInstall(request);

Запрос обрабатывается так, как если бы это был запрос на модуль функций. То есть вы можете отслеживать состояние запроса , как вы обычно.

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

Котлин

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Ява

splitInstallManager.deferredLanguageInstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Доступ к загруженным языковым ресурсам

Чтобы получить доступ к загруженным языковым ресурсам, ваше приложение необходимо для запуска метода SplitCompat.installActivity() в методе attachBaseContext() каждого действия, который требует доступа к этим ресурсам, как показано ниже.

Котлин

override fun attachBaseContext(base: Context) {
  super.attachBaseContext(base)
  SplitCompat.installActivity(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
  super.attachBaseContext(base);
  SplitCompat.installActivity(this);
}

Для каждой деятельности, которое вы хотите использовать языковые ресурсы, загрузили ваше приложение, обновите базовый контекст и установите новый локал через его Configuration :

Котлин

override fun attachBaseContext(base: Context) {
  val configuration = Configuration()
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))
  val context = base.createConfigurationContext(configuration)
  super.attachBaseContext(context)
  SplitCompat.install(this)
}

Ява

@Override
protected void attachBaseContext(Context base) {
  Configuration configuration = new Configuration();
  configuration.setLocale(Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));
  Context context = base.createConfigurationContext(configuration);
  super.attachBaseContext(context);
  SplitCompat.install(this);
}

Чтобы эти изменения вступили в силу, вы должны воссоздать свою деятельность после установки нового языка и готового к использованию. Вы можете использовать метод Activity#recreate() .

Котлин

when (state.status()) {
  SplitInstallSessionStatus.INSTALLED -> {
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate()
  }
  ...
}

Ява

switch (state.status()) {
  case SplitInstallSessionStatus.INSTALLED:
      // Recreates the activity to load resources for the new language
      // preference.
      activity.recreate();
  ...
}

Удалить дополнительные языковые ресурсы

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

Котлин

val installedLanguages: Set<String> = splitInstallManager.installedLanguages

Ява

Set<String> installedLanguages = splitInstallManager.getInstalledLanguages();

Затем вы можете решить, какие языки удалять метод deferredLanguageUninstall() , как показано ниже.

Котлин

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)))

Ява

splitInstallManager.deferredLanguageUninstall(
    Locale.forLanguageTag(sharedPrefs.getString(LANGUAGE_SELECTION)));

Установка модуля локального тестирования

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

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

Хотя вам не нужно вносить какие -либо изменения в логику вашего приложения, вам нужно соответствовать следующим требованиям:

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

Построить набор APK

Если вы еще этого не сделали, создайте разделенные APK своего приложения, следующим образом:

  1. Создайте пакет приложения для вашего приложения, используя один из следующих методов:
  2. Используйте bundletool для генерации набора APK для всех конфигураций устройства со следующей командой:

    bundletool build-apks --local-testing
      --bundle my_app.aab
      --output my_app.apks
    

Флаг --local-testing включает в себя метадаты в манифестах ваших APK, которые позволяют библиотеке доставки функций Play для использования локальных APK для тестирования модулей установки, не подключаясь к магазину Play.

Развернуть свое приложение на устройство

После того, как вы создаете набор APK, используя флаг --local-testing , используйте bundletool для установки базовой версии вашего приложения и передачи дополнительных APK на локальное хранилище вашего устройства. Вы можете выполнить оба действия со следующей командой:

bundletool install-apks --apks my_app.apks

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

Моделировать сетевую ошибку

Для моделирования установки модуля из Play Store Библиотека доставки функций Play использует альтернативу SplitInstallManager , называемому FakeSplitInstallManager , для запроса модуля. Когда вы используете bundletool с флагом --local-testing для создания набора APK и развертывания их на вашем тестовом устройстве, он включает в себя метаданные, которые инструктируют библиотеку доставки функций Play, чтобы автоматически переключать вызовы API вашего приложения, чтобы вызвать FakeSplitInstallManager , вместо SplitInstallManager .

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

Котлин

// Creates an instance of FakeSplitInstallManager with the app's context.
val fakeSplitInstallManager = FakeSplitInstallManagerFactory.create(context)
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true)

Ява

// Creates an instance of FakeSplitInstallManager with the app's context.
FakeSplitInstallManager fakeSplitInstallManager =
    FakeSplitInstallManagerFactory.create(context);
// Tells Play Feature Delivery Library to force the next module request to
// result in a network error.
fakeSplitInstallManager.setShouldNetworkError(true);