Поддержка обновлений внутри приложения (Kotlin или Java)

В этом руководстве описывается, как реализовать поддержку внутренних обновлений в вашем приложении, используя Kotlin или Java. Существуют отдельные руководства для случаев, когда ваша реализация использует нативный код (C/C++), и для случаев, когда ваша реализация использует Unity или Unreal Engine .

Настройте среду разработки

Библиотека обновлений Play In-App является частью библиотеки Google Play Core . Включите следующую зависимость Gradle, чтобы интегрировать библиотеку обновлений Play In-App.

Круто

// In your app's build.gradle file:
...
dependencies {
    // This dependency is downloaded from the Google's Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation 'com.google.android.play:app-update:2.1.0'

    // For Kotlin users also add the Kotlin extensions library for Play In-App Update:
    implementation 'com.google.android.play:app-update-ktx:2.1.0'
    ...
}

Котлин

// In your app's build.gradle.kts file:
...
dependencies {
    // This dependency is downloaded from the Google's Maven repository.
    // So, make sure you also include that repository in your project's build.gradle file.
    implementation("com.google.android.play:app-update:2.1.0")

    // For Kotlin users also import the Kotlin extensions library for Play In-App Update:
    implementation("com.google.android.play:app-update-ktx:2.1.0")
    ...
}

Проверить наличие обновлений

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

Котлин

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
        // This example applies an immediate update. To apply a flexible update
        // instead, pass in AppUpdateType.FLEXIBLE
        && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)
    ) {
        // Request the update.
    }
}

Ява

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks that the platform will allow the specified type of update.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          // This example applies an immediate update. To apply a flexible update
          // instead, pass in AppUpdateType.FLEXIBLE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request the update.
    }
});

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

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

Проверить актуальность обновлений

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

Используйте clientVersionStalenessDays() чтобы проверить количество дней с момента обновления в магазине Play:

Котлин

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && (appUpdateInfo.clientVersionStalenessDays() ?: -1) >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
}

Ява

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and current version staleness.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.clientVersionStalenessDays() != null
          && appUpdateInfo.clientVersionStalenessDays() >= DAYS_FOR_FLEXIBLE_UPDATE
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)) {
              // Request the update.
    }
});

Проверить приоритет обновления

API разработчика Google Play позволяет вам устанавливать приоритет каждого обновления. Это позволяет вашему приложению самостоятельно определять, насколько настоятельно рекомендовать обновление пользователю. Например, рассмотрим следующую стратегию установки приоритета обновления:

  • Незначительные улучшения пользовательского интерфейса: обновление с низким приоритетом ; Запросите ни гибкое обновление, ни немедленное обновление. Обновление только тогда, когда пользователь не взаимодействует с вашим приложением.
  • Улучшение производительности: обновление среднего приоритета ; Запросите гибкое обновление.
  • Критическое обновление безопасности: обновление высокого приоритета ; Запросите немедленное обновление.

Для определения приоритета Google Play использует целое число от 0 до 5, где 0 — значение по умолчанию, а 5 — наивысший приоритет. Чтобы задать приоритет обновления, используйте поле inAppUpdatePriority в разделе Edits.tracks.releases в API разработчика Google Play. Все новые версии в релизе считаются имеющими тот же приоритет, что и сам релиз. Приоритет можно задать только при выпуске нового релиза и нельзя изменить позже.

Установите приоритет с помощью API Google Play Developer, как описано в документации API Play Developer . Приоритет обновления внутри приложения должен быть указан в ресурсе Edit.tracks , передаваемом в методе Edit.tracks: update :. В следующем примере демонстрируется выпуск приложения с кодом версии 88 и inAppUpdatePriority 5:

{
  "releases": [{
      "versionCodes": ["88"],
      "inAppUpdatePriority": 5,
      "status": "completed"
  }]
}

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

  • Вы выпускаете версию 1 на производственный трек без приоритета.
  • Вы выпускаете версию 2 во внутреннюю тестовую дорожку с приоритетом 5.
  • Вы выпускаете версию 3 на производственный трек без приоритета.

Когда пользователи производства обновляются от версии 1 до версии 3, они получат приоритет 5, хотя версия 2 была опубликована на другом треке.

Котлин

val appUpdateManager = AppUpdateManagerFactory.create(context)

// Returns an intent object that you use to check for an update.
val appUpdateInfoTask = appUpdateManager.appUpdateInfo

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener { appUpdateInfo ->
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
}

Ява

AppUpdateManager appUpdateManager = AppUpdateManagerFactory.create(context);

// Returns an intent object that you use to check for an update.
Task<AppUpdateInfo> appUpdateInfoTask = appUpdateManager.getAppUpdateInfo();

// Checks whether the platform allows the specified type of update,
// and checks the update priority.
appUpdateInfoTask.addOnSuccessListener(appUpdateInfo -> {
    if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
          && appUpdateInfo.updatePriority() >= 4 /* high priority */
          && appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {
              // Request an immediate update.
    }
});

Начать обновление

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

Котлин

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())

Ява

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());

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

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

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

Настройте обновление с помощью AppUpdateOptions

AppUpdateOptions содержит поле AllowAssetPackDeletion , которое определяет, разрешено ли обновлению удалять пакеты ресурсов при ограниченном объёме памяти устройства. По умолчанию это поле имеет значение false , но вы можете использовать метод setAllowAssetPackDeletion() , чтобы установить его в true :

Котлин

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build())

Ява

appUpdateManager.startUpdateFlowForResult(
    // Pass the intent that is returned by 'getAppUpdateInfo()'.
    appUpdateInfo,
    // an activity result launcher registered via registerForActivityResult
    activityResultLauncher,
    // Or pass 'AppUpdateType.FLEXIBLE' to newBuilder() for
    // flexible updates.
    AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE)
        .setAllowAssetPackDeletion(true)
        .build());

Получите обратный звонок для обновления статуса

После начала обновления зарегистрированный обратный вызов запуска запуска

Котлин

registerForActivityResult(StartIntentSenderForResult()) { result: ActivityResult ->
    // handle callback
    if (result.resultCode != RESULT_OK) {
        log("Update flow failed! Result code: " + result.resultCode);
        // If the update is canceled or fails,
        // you can request to start the update again.
    }
}

Ява

registerForActivityResult(
    new ActivityResultContracts.StartIntentSenderForResult(),
    new ActivityResultCallback<ActivityResult>() {
        @Override
        public void onActivityResult(ActivityResult result) {
            // handle callback
            if (result.getResultCode() != RESULT_OK) {
                log("Update flow failed! Result code: " + result.getResultCode());
                // If the update is canceled or fails,
                // you can request to start the update again.
            }
        }
    });

Есть несколько значений, которые вы могли бы получить от обратного вызова onActivityResult() :

  • RESULT_OK : Пользователь принял обновление. В случае немедленных обновлений этот обратный вызов может не быть получен, поскольку обновление должно быть уже завершено к моменту возврата управления приложению.
  • RESULT_CANCELED : пользователь отрицал или отменил обновление.
  • ActivityResult.RESULT_IN_APP_UPDATE_FAILED : какая -то другая ошибка не позволила пользователю предоставить согласие, либо обновление не было выполнено.

Обеспечить гибкое обновление

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

Мониторинг состояния гибкого обновления

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

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

Котлин

// Create a listener to track request state updates.
val listener = InstallStateUpdatedListener { state ->
    // (Optional) Provide a download progress bar.
    if (state.installStatus() == InstallStatus.DOWNLOADING) {
      val bytesDownloaded = state.bytesDownloaded()
      val totalBytesToDownload = state.totalBytesToDownload()
      // Show update progress bar.
    }
    // Log state or install the update.
}

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener)

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener)

Ява

// Create a listener to track request state updates.
InstallStateUpdatedListener listener = state -> {
  // (Optional) Provide a download progress bar.
  if (state.installStatus() == InstallStatus.DOWNLOADING) {
      long bytesDownloaded = state.bytesDownloaded();
      long totalBytesToDownload = state.totalBytesToDownload();
      // Implement progress bar.
  }
  // Log state or install the update.
};

// Before starting an update, register a listener for updates.
appUpdateManager.registerListener(listener);

// Start an update.

// When status updates are no longer needed, unregister the listener.
appUpdateManager.unregisterListener(listener);

Установить гибкое обновление

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

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

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

Следующий пример демонстрирует реализацию закусочной дизайна материала , которая запрашивает подтверждение от пользователя для перезапуска приложения:

Котлин

val listener = { state ->
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate()
    }
    ...
}

// Displays the snackbar notification and call to action.
fun popupSnackbarForCompleteUpdate() {
    Snackbar.make(
        findViewById(R.id.activity_main_layout),
        "An update has just been downloaded.",
        Snackbar.LENGTH_INDEFINITE
    ).apply {
        setAction("RESTART") { appUpdateManager.completeUpdate() }
        setActionTextColor(resources.getColor(R.color.snackbar_action_text_color))
        show()
    }
}

Ява

InstallStateUpdatedListener listener = state -> {
    if (state.installStatus() == InstallStatus.DOWNLOADED) {
        // After the update is downloaded, show a notification
        // and request user confirmation to restart the app.
        popupSnackbarForCompleteUpdate();
    }
    ...
};

// Displays the snackbar notification and call to action.
private void popupSnackbarForCompleteUpdate() {
  Snackbar snackbar =
      Snackbar.make(
          findViewById(R.id.activity_main_layout),
          "An update has just been downloaded.",
          Snackbar.LENGTH_INDEFINITE);
  snackbar.setAction("RESTART", view -> appUpdateManager.completeUpdate());
  snackbar.setActionTextColor(
      getResources().getColor(R.color.snackbar_action_text_color));
  snackbar.show();
}

При вызове appUpdateManager.completeUpdate() на переднем плане платформа отображает полноэкранный пользовательский интерфейс, который перезапускает приложение в фоновом режиме. После установки обновления платформа перезапускает приложение в свою основную активность.

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

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

Котлин

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            // If the update is downloaded but not installed,
            // notify the user to complete the update.
            if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                popupSnackbarForCompleteUpdate()
            }
        }
}

Ява

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all app entry points.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(appUpdateInfo -> {
              ...
              // If the update is downloaded but not installed,
              // notify the user to complete the update.
              if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED) {
                  popupSnackbarForCompleteUpdate();
              }
          });
}

Обрабатывать немедленное обновление

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

Однако, когда приложение возвращается на передний план, необходимо убедиться, что обновление не остановлено в состоянии UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS . Если обновление остановлено в этом состоянии, возобновите его:

Котлин

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
override fun onResume() {
    super.onResume()

    appUpdateManager
        .appUpdateInfo
        .addOnSuccessListener { appUpdateInfo ->
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS
            ) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build())
            }
        }
}

Ява

// Checks that the update is not stalled during 'onResume()'.
// However, you should execute this check at all entry points into the app.
@Override
protected void onResume() {
  super.onResume();

  appUpdateManager
      .getAppUpdateInfo()
      .addOnSuccessListener(
          appUpdateInfo -> {
            ...
            if (appUpdateInfo.updateAvailability()
                == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) {
                // If an in-app update is already running, resume the update.
                appUpdateManager.startUpdateFlowForResult(
                  appUpdateInfo,
                  activityResultLauncher,
                  AppUpdateOptions.newBuilder(AppUpdateType.IMMEDIATE).build());
            }
          });
}

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

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

Следующие шаги

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