Как добавить поддержку обновлений в приложении (нативные приложения)

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

Обзор нативных SDK

Нативный SDK Play Core входит в семейство SDK Play Core. В Native SDK входит заголовочный файл C app_update.h, который содержит обертку для AppUpdateManager из библиотеки Java Play In-App Update Library. Этот заголовочный файл позволяет приложению вызывать API для обновлений в приложении непосредственно из нативного кода.

Как настроить среду разработки

Скачать Play Core Native SDK

Перед загрузкой вы должны согласиться со следующими условиями.

Условия и положения

Last modified: September 24, 2020
  1. By using the Play Core Software Development Kit, you agree to these terms in addition to the Google APIs Terms of Service ("API ToS"). If these terms are ever in conflict, these terms will take precedence over the API ToS. Please read these terms and the API ToS carefully.
  2. For purposes of these terms, "APIs" means Google's APIs, other developer services, and associated software, including any Redistributable Code.
  3. “Redistributable Code” means Google-provided object code or header files that call the APIs.
  4. Subject to these terms and the terms of the API ToS, you may copy and distribute Redistributable Code solely for inclusion as part of your API Client. Google and its licensors own all right, title and interest, including any and all intellectual property and other proprietary rights, in and to Redistributable Code. You will not modify, translate, or create derivative works of Redistributable Code.
  5. Google may make changes to these terms at any time with notice and the opportunity to decline further use of the Play Core Software Development Kit. Google will post notice of modifications to the terms at https://developer.android.com/guide/playcore/license. Changes will not be retroactive.
Скачать Play Core Native SDK

play-core-native-sdk-1.16.0.zip

  1. Выполните одно из следующих действий:

    • Установите Android Studio версии 4.0 или более поздней. Используйте интерфейс SDK Manager, чтобы установить платформу Android SDK версии 10.0 (уровень API 29).
    • Установите инструменты командной строки Android SDK и с помощью sdkmanager установите платформу Android SDK версии 10.0 (уровень API 29).
  2. Подготовьте Android Studio к разработке нативных приложений, используя SDK Manager, чтобы установить последние версии CMake и Android Native Development Kit (NDK). Подробнее о том, как создавать и импортировать проекты нативных приложений, рассказывается в руководстве по началу работы с NDK.

  3. Скачайте ZIP-файл и извлеките его в папку с проектом.

    Ссылка для скачивания Размер Контрольная сумма SHA-256
    54,8 МиБ 008b8fedc6179a6dc6ccc21af75591afc7036f78f3d5559d844f1b923934fef0
  4. Обновите файл build.gradle приложения, как показано ниже:

    Яркий

        // App build.gradle
    
        plugins {
          id 'com.android.application'
        }
    
        // Define a path to the extracted Play Core SDK files.
        // If using a relative path, wrap it with file() since CMake requires absolute paths.
        def playcoreDir = file('../path/to/playcore-native-sdk')
    
        android {
            defaultConfig {
                ...
                externalNativeBuild {
                    cmake {
                        // Define the PLAYCORE_LOCATION directive.
                        arguments "-DANDROID_STL=c++_static",
                                  "-DPLAYCORE_LOCATION=$playcoreDir"
                    }
                }
                ndk {
                    // Skip deprecated ABIs. Only required when using NDK 16 or earlier.
                    abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                }
            }
            buildTypes {
                release {
                    // Include Play Core Library proguard config files to strip unused code while retaining the Java symbols needed for JNI.
                    proguardFile '$playcoreDir/proguard/common.pgcfg'
                    proguardFile '$playcoreDir/proguard/gms_task.pgcfg'
                    proguardFile '$playcoreDir/proguard/per-feature-proguard-files'
                    ...
                }
                debug {
                    ...
                }
            }
            externalNativeBuild {
                cmake {
                    path 'src/main/CMakeLists.txt'
                }
            }
        }
    
        dependencies {
            // Import these feature-specific AARs for each Google Play Core library.
            implementation 'com.google.android.play:app-update:2.1.0'
            implementation 'com.google.android.play:asset-delivery:2.3.0'
            implementation 'com.google.android.play:integrity:1.6.0'
            implementation 'com.google.android.play:review:2.0.2'
    
            // Import these common dependencies.
            implementation 'com.google.android.gms:play-services-tasks:18.0.2'
            implementation files("$playcoreDir/playcore-native-metadata.jar")
            ...
        }
        

    Kotlin

    // App build.gradle
    
    plugins {
        id("com.android.application")
    }
    
    // Define a path to the extracted Play Core SDK files.
    // If using a relative path, wrap it with file() since CMake requires absolute paths.
    val playcoreDir = file("../path/to/playcore-native-sdk")
    
    android {
        defaultConfig {
            ...
            externalNativeBuild {
                cmake {
                    // Define the PLAYCORE_LOCATION directive.
                    arguments += listOf("-DANDROID_STL=c++_static", "-DPLAYCORE_LOCATION=$playcoreDir")
                }
            }
            ndk {
                // Skip deprecated ABIs. Only required when using NDK 16 or earlier.
                abiFilters.clear()
                abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
            }
        }
        buildTypes {
            release {
                // Include Play Core Library proguard config files to strip unused code while retaining the Java symbols needed for JNI.
                proguardFile("$playcoreDir/proguard/common.pgcfg")
                proguardFile("$playcoreDir/proguard/gms_task.pgcfg")
                proguardFile("$playcoreDir/proguard/per-feature-proguard-files")
                ...
            }
            debug {
                ...
            }
        }
        externalNativeBuild {
            cmake {
                path = "src/main/CMakeLists.txt"
            }
        }
    }
    
    dependencies {
        // Import these feature-specific AARs for each Google Play Core library.
        implementation("com.google.android.play:app-update:2.1.0")
        implementation("com.google.android.play:asset-delivery:2.3.0")
        implementation("com.google.android.play:integrity:1.6.0")
        implementation("com.google.android.play:review:2.0.2")
    
        // Import these common dependencies.
        implementation("com.google.android.gms:play-services-tasks:18.0.2")
        implementation(files("$playcoreDir/playcore-native-metadata.jar"))
        ...
    }
  5. Обновите файлы CMakeLists.txt приложения, как показано ниже.

    cmake_minimum_required(VERSION 3.6)
    
    ...
    
    # Add a static library called “playcore” built with the c++_static STL.
    include(${PLAYCORE_LOCATION}/playcore.cmake)
    add_playcore_static_library()
    
    // In this example “main” is your native code library, i.e. libmain.so.
    add_library(main SHARED
            ...)
    
    target_include_directories(main PRIVATE
            ${PLAYCORE_LOCATION}/include
            ...)
    
    target_link_libraries(main
            android
            playcore
            ...)
    

Сбор данных

Нативный SDK Play Core может собирать данные о версиях, чтобы Google мог улучшать продукт. К таким данным относятся:

  • Название пакета приложения
  • Версия пакета приложения
  • Версия нативного SDK Play Core

Эти данные собираются, когда вы загружаете пакет приложения в Play Console. Чтобы отказаться от сбора данных, удалите импорт $playcoreDir/playcore-native-metadata.jar в файле build.gradle.

Обратите внимание, что сбор данных, связанных с использованием вами Play Core Native SDK, и использование этих данных Google – это отдельные процессы, не зависящие от сбора Google зависимостей библиотек, задекларированных в Gradle, при загрузке пакета приложения в Play Console.

После того как вы интегрируете нативный SDK Play Core в свой проект, добавьте следующую строку в файлы, содержащие вызовы API:

#include "play/app_update.h"

Инициализация In-App Update API

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

void android_main(android_app* app) {
  app->onInputEvent = HandleInputEvent;

  AppUpdateErrorCode error_code =
    AppUpdateManager_init(app->activity->vm, app->activity->clazz);
  if (error_code == APP_UPDATE_NO_ERROR) {
    // You can use the API.
  }
}

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

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

AppUpdateErrorCode error_code = AppUpdateManager_requestInfo()

if (error_code == APP_UPDATE_NO_ERROR) {
    // The request has successfully started, check the result using
    // AppUpdateManager_getInfo.
}

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

AppUpdateInfo* info;
GameUpdate() {

   // Keep calling this in every game loop until info != nullptr
   AppUpdateErrorCode error_code = AppUpdateManager_getInfo(&info);

   if (error_code == APP_UPDATE_NO_ERROR && info != nullptr) {
       // Successfully started, check the result in the following functions
   }
...
}

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

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

Чтобы узнать, сколько дней прошло с момента, когда обновление стало доступно в Google Play, используйте AppUpdateInfo_getClientVersionStalenessDays():

int32_t staleness_days = AppUpdateInfo_getClientVersionStalenessDays(info);

Как проверить приоритет обновления

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

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

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

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

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

В коде приложения можно проверить уровень приоритета для определенного обновления с помощью AppUpdateInfo_getPriority():

int32_t priority = AppUpdateInfo_getPriority(info);

Как начать обновление

Если обновление доступно, вы можете запросить его с помощью команды AppUpdateManager_requestStartUpdate(). Прежде чем запрашивать обновление, получите актуальный объект AppUpdateInfo и создайте объект AppUpdateOptions, чтобы настроить процесс обновления. Объект AppUpdateOptions определяет параметры процесса обновления в приложении, в том числе тип обновления (гибкое или немедленное).

В примере ниже показано, как создать объект AppUpdateOptions для гибкого обновления:

// Creates an AppUpdateOptions configuring a flexible in-app update flow.
AppUpdateOptions* options;
AppUpdateErrorCode error_code = AppUpdateOptions_createOptions(APP_UPDATE_TYPE_FLEXIBLE, &options);

В примере ниже показано, как создать объект AppUpdateOptions для немедленного обновления:

// Creates an AppUpdateOptions configuring an immediate in-app update flow.
AppUpdateOptions* options;
AppUpdateErrorCode error_code = AppUpdateOptions_createOptions(APP_UPDATE_TYPE_IMMEDIATE, &options);

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

bool allow = true;
AppUpdateErrorCode error_code = AppUpdateOptions_setAssetPackDeletionAllowed(options, allow);

После того как вы получите актуальный объект AppUpdateInfo и правильно настроите объект AppUpdateOptions, вызовите AppUpdateManager_requestStartUpdate(), чтобы асинхронно запросить процесс обновления, передав в качестве последнего параметра объект Activity Android jobject.

AppUpdateErrorCode request_error_code =
AppUpdateManager_requestStartUpdate(info, options, app->activity->clazz);

Чтобы освободить ресурсы, удалите ненужные экземпляры AppUpdateInfo и AppUpdateOptions, вызвав AppUpdateInfo_destroy() и AppUpdateOptions_destroy() соответственно.

AppUpdateInfo_destroy(info);
AppUpdateOptions_destroy(options);

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

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

AppUpdateStatus status = AppUpdateInfo_getStatus(info);
if (status == APP_UPDATE_DOWNLOADED) {
    AppUpdateErrorCode error_code = AppUpdateManager_requestCompleteUpdate();
    if (error_code != APP_UPDATE_NO_ERROR)
    {
      // There was an error while completing the update flow.
    }
}

Освободите ресурсы, вызвав функцию AppUpdateManager_destroy() после того, как приложение закончит использовать API.

Обработка ошибок

В этом разделе описаны решения распространенных ошибок, которые обозначаются определенными значениями AppUpdateErrorCode:

  • Код ошибки -110, APP_UPDATE_INITIALIZATION_NEEDED указывает на то, что инициализация API не была выполнена успешно. Вызовите AppUpdateManager_init(), чтобы инициализировать API.
  • Код ошибки -4, APP_UPDATE_INVALID_REQUEST указывает на то, что некоторые параметры запроса на обновление процесса сформированы неправильно. Убедитесь, что объекты AppUpdateInfo и AppUpdateOptions не имеют значения null и правильно отформатированы.
  • Код ошибки -5, APP_UPDATE_UNAVAILABLE означает, что доступных обновлений нет. Убедитесь, что у целевой версии те же название пакета, идентификатор приложения и ключ подписи. Если обновление доступно, очистите кеш приложения и снова позвоните на номер AppUpdateManager_requestAppUpdateInfo(), чтобы обновить AppUpdateInfo.
  • Код ошибки -6, APP_UPDATE_NOT_ALLOWED означает, что тип обновления, указанный в объекте AppUpdateOption, недопустим. Перед началом процесса обновления проверьте, разрешен ли тип обновления в объекте AppUpdateInfo.

Дальнейшие действия

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