Поддержка обновлений внутри приложения (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 Store. Это может помочь вам решить, следует ли инициировать гибкое обновление или немедленное обновление. Например, вы можете подождать несколько дней, прежде чем уведомить пользователя о гибком обновлении, и несколько дней после этого, прежде чем потребовать немедленное обновление.

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

Котлин

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

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

В следующем примере демонстрируется реализация снэк-бара Material Design , который запрашивает у пользователя подтверждение на перезапуск приложения:

Котлин

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 закрывается. Ваше приложение должно определить наилучший способ продолжения.

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

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

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

,

В этом руководстве описывается, как поддерживать обновления в приложении с помощью 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 Store. Это может помочь вам решить, следует ли инициировать гибкое обновление или немедленное обновление. Например, вы можете подождать несколько дней, прежде чем уведомить пользователя о гибком обновлении, и несколько дней после этого, прежде чем потребовать немедленное обновление.

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

Котлин

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

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

В следующем примере демонстрируется реализация снэк-бара Material Design , который запрашивает у пользователя подтверждение на перезапуск приложения:

Котлин

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 закрывается. Ваше приложение должно определить наилучший способ продолжения.

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

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

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