인앱 업데이트 지원(네이티브)

이 가이드에서는 네이티브 코드(C 또는 C++)를 사용하여 앱에서 인앱 업데이트를 지원하는 방법을 설명합니다. Kotlin 프로그래밍 언어 또는 Java 프로그래밍 언어를 사용하는 구현과 Unity를 사용하는 구현을 위한 별도의 가이드가 있습니다.

Native SDK 개요

Play Core Native SDK는 Play Core SDK 제품군의 일부입니다. Native SDK에는 Java Play In-App Update 라이브러리에서 AppUpdateManager를 래핑하는 C 헤더 파일 app_update.h가 포함되어 있습니다. 이 헤더 파일을 사용하면 앱이 네이티브 코드에서 직접 인앱 업데이트를 진행하기 위해 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.14.0.zip

  1. 다음 중 하나를 실행합니다.

    • Android 스튜디오 버전 4.0 이상을 설치합니다. SDK Manager UI를 사용하여 Android SDK 플랫폼 버전 10.0(API 수준 29)을 설치합니다.
    • Android SDK 명령줄 도구를 설치하고 sdkmanager를 사용하여 Android SDK 플랫폼 버전 10.0(API 수준 29)을 설치합니다.
  2. SDK Manager를 사용하여 최신 CMake 및 Android 네이티브 개발 키트(NDK)를 설치함으로써 네이티브 개발을 위해 Android 스튜디오를 준비합니다. 네이티브 프로젝트를 만들거나 가져오는 방법에 관한 자세한 내용은 NDK 시작하기를 참고하세요.

  3. ZIP 파일을 다운로드하여 프로젝트와 함께 압축을 풉니다.

    다운로드 링크 크기 SHA-256 체크섬
    36MiB 782a8522d937848c83a715c9a258b95a3ff2879a7cd71855d137b41c00786a5e
  4. 아래와 같이 앱의 build.gradle 파일을 업데이트합니다.

    Groovy

        // 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.0.0'
            implementation 'com.google.android.play:asset-delivery:2.0.0'
            implementation 'com.google.android.play:integrity:1.0.1'
            implementation 'com.google.android.play:review:2.0.0'
    
            // 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.0.0")
        implementation("com.google.android.play:asset-delivery:2.0.0")
        implementation("com.google.android.play:integrity:1.0.1")
        implementation("com.google.android.play:review:2.0.0")
    
        // 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
            ...)
    

데이터 수집

Play Core Native SDK는 Google에서 제품을 개선할 수 있도록 다음과 같은 버전 관련 데이터를 수집할 수 있습니다.

  • 앱의 패키지 이름
  • 앱의 패키지 버전
  • Play Core Native SDK 버전

이 데이터는 앱 패키지를 Play Console에 업로드할 때 수집됩니다. 이 데이터 수집 프로세스를 선택 해제하려면 build.gradle 파일에서 $playcoreDir/playcore-native-metadata.jar 가져오기를 삭제합니다.

Play Core Native SDK 사용 및 Google의 수집된 데이터 사용과 관련된 이 데이터 수집은 앱 패키지를 Play Console에 업로드할 때 Gradle에 선언된 라이브러리 종속 항목에 관한 Google의 수집과는 별개이며 관련이 없습니다.

Play Core Native SDK를 프로젝트에 통합한 후 API 호출이 포함된 파일에 다음 줄을 포함합니다.

#include "play/app_update.h"

인앱 업데이트 API 초기화

android_native_app_glue.h로 빌드된 다음 예에 나와 있는 것처럼 인앱 업데이트 API를 사용할 때마다 먼저 AppUpdateManager_init() 함수를 호출하여 이 API를 초기화합니다.

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와 관련해 null이 아닌 결과를 반환할 때까지 다음과 같이 모든 게임 루프에서 이 함수를 호출하는 것이 좋습니다.

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

업데이트 비활성화 확인

업데이트 사용 가능 여부를 확인하는 것 외에 Play 스토어를 통해 사용자에게 업데이트 알림이 전송된 후 얼마나 시간이 지났는지 확인하는 것도 좋습니다. 그렇게 하면 유연한 업데이트를 시작할지 아니면 즉시 업데이트를 시작할지 판단하는 데 도움이 될 수 있습니다. 예를 들어 며칠을 기다렸다가 사용자에게 유연한 업데이트를 알린 다음 며칠 후 즉시 업데이트를 요청할 수도 있습니다.

Play 스토어를 통해 업데이트가 제공된 후 경과한 일수를 확인하려면 AppUpdateInfo_getClientVersionStalenessDays()를 사용합니다.

int32_t staleness_days = AppUpdateInfo_getClientVersionStalenessDays(info);

업데이트 우선순위 확인

Google Play Developer API를 사용하여 각 업데이트의 우선순위를 설정할 수 있습니다. 이렇게 하면 앱에서 사용자에게 업데이트를 얼마나 강력하게 권장할지 결정할 수 있습니다. 업데이트 우선순위를 설정하는 다음 전략을 예로 들어 보겠습니다.

  • 사소한 UI 개선: 낮은 우선순위 업데이트. 유연한 업데이트와 즉시 업데이트 중 어떤 것도 요청하지 않습니다. 사용자가 앱과 상호작용하지 않을 때만 업데이트합니다.
  • 성능 개선: 중간 우선순위 업데이트. 유연한 업데이트를 요청합니다.
  • 중요 보안 업데이트: 높은 우선순위 업데이트. 즉시 업데이트를 요청합니다.

우선순위를 결정하기 위해 Google Play는 0에서 5 사이의 정숫값을 사용합니다. 0은 기본값, 5는 가장 높은 우선순위입니다. 업데이트 우선순위를 설정하려면 Google Play Developer API의 Edits.tracks.releases 아래에 있는 inAppUpdatePriority 필드를 사용하세요. 출시에 새로 추가된 모든 버전은 출시와 동일한 우선순위로 간주됩니다. 우선순위는 새 버전을 출시할 때만 설정할 수 있으며 나중에 변경할 수 없습니다.

Play Developer API 문서에 설명된 대로 Google Play Developer API를 사용하여 우선순위를 설정합니다. Edit.tracks: update 메서드에 전달된 Edit.tracks 리소스에서 인앱 업데이트 우선순위를 지정합니다. 다음 예는 버전 코드가 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()를 호출하여 비동기식으로 업데이트 흐름을 요청하고 마지막 매개변수에 Android 활동 jobject를 전달합니다.

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

리소스를 확보하려면 AppUpdateInfo_destroy()AppUpdateOptions_destroy()를 호출하여 더 이상 필요하지 않은 AppUpdateInfoAppUpdateOptions의 인스턴스를 각각 해제합니다.

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

앱에서 API 사용을 마치면 AppUpdateManager_destroy() 함수를 호출하여 리소스를 해제합니다.

오류 처리

이 섹션에서는 특정 AppUpdateErrorCode 값으로 표시된 일반적인 오류에 대한 해결책을 설명합니다.

  • -110, APP_UPDATE_INITIALIZATION_NEEDED의 오류 코드는 API가 제대로 초기화되지 않았음을 나타냅니다. AppUpdateManager_init()를 호출하여 API를 초기화합니다.
  • -4, APP_UPDATE_INVALID_REQUEST의 오류 코드는 업데이트 흐름 요청의 일부 매개변수 형식이 잘못되었음을 나타냅니다. AppUpdateInfoAppUpdateOptions 객체가 null이 아니며 올바른 형식인지 확인합니다.
  • -5, APP_UPDATE_UNAVAILABLE의 오류 코드는 적용 가능한 업데이트가 없음을 나타냅니다. 타겟 버전의 패키지 이름애플리케이션 ID, 서명 키가 동일한지 확인합니다. 사용 가능한 업데이트가 있으면 앱의 캐시를 지우고 AppUpdateManager_requestAppUpdateInfo()를 다시 호출하여 AppUpdateInfo를 새로 고칩니다.
  • -6, APP_UPDATE_NOT_ALLOWED의 오류 코드는 AppUpdateOption 객체로 표시된 업데이트 유형이 허용되지 않음을 나타냅니다. 업데이트 흐름을 시작하기 전에 업데이트 유형이 허용되는 것으로 AppUpdateInfo 객체에 나타나는지 확인합니다.

다음 단계

통합이 제대로 작동하는지 확인하기 위해 앱의 인앱 업데이트를 테스트합니다.