支援應用程式內更新 (原生)

本指南說明如何使用原生程式碼 (C 或 C++) 在您的應用程式中支援 應用程式內更新。我們針對您的導入使用 Kotlin 程式設計語言或者 Java 程式設計語言 以及 Unity 的情況分別提供單獨指南。

原生 SDK 總覽

Play Core 原生 SDK 屬於 Play Core SDK 系列的一部分。原生 SDK 包含 C 標頭檔案 app_update.h,可納入 Java Play 應用程式內更新程式庫中的 AppUpdateManager。應用程式可利用這個標頭檔案,直接從原生程式碼呼叫 API,以進行應用程式內更新。

設定開發環境

Download Play Core Native SDK

Before downloading, you must agree to the following terms and conditions.

Terms and Conditions

上次修改時間:2020 年 9 月 24 日
  1. 使用 Play Core 軟體開發套件,即表示您同意遵守本條款,以及《Google API 服務條款》(以下簡稱「API 服務條款」)。如果本條款與 API 服務條款相牴觸,應以本條款為準。請詳閱本條款和 API 服務條款。
  2. 就本條款而言,「API」是指 Google 的 API、其他開發人員服務和相關軟體,包括任何可轉散發程式碼。
  3. 「可轉散發程式碼」是指 Google 提供的物件程式碼或標頭檔案,用於呼叫 API。
  4. 根據本條款和 API 服務條款的規定,您可以複製並發布可轉散發程式碼,但只能納入為 API 用戶端的一部分。Google 及其授權人對「可轉散發的程式碼」擁有所有權利、所有權和利益,包括任何和所有智慧財產權及其他財產權。您不得對可轉散發程式碼進行修改、翻譯或製造衍生作品。
  5. Google 可隨時變更本條款。條款有所異動時,Google 會發出通知並提供選項,讓開發人員可以拒絕繼續使用 Play Core 軟體開發套件。Google 會在 https://developer.android.com/guide/playcore/license 發布條款修訂通知。變更將不溯及既往。
Download Play Core Native SDK

play-core-native-sdk-1.12.1.zip

  1. 執行下列其中一項操作:

  2. 使用 SDK Manager 安裝最新的 CMake 和 Android Native Development Kit (NDK),讓 Android Studio 準備進行原生開發。如要進一步瞭解如何建立或匯入原生專案,請參閱 NDK 入門指南

  3. 下載 ZIP 檔案,然後將其連同專案一起解壓縮。

    下載連結 大小 SHA-256 總和檢查碼
    70.9 MiB 84c9e9579f05d6e29bbbd9c9cde2fde8210947f2007866b0045f4c40fabb7368
  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 原生 SDK 可收集版本相關資料,讓 Google 能夠改善產品,這包括:

  • 應用程式的套件名稱
  • 應用程式的套件版本
  • Play Core 原生 SDK 版本

當您將應用程式套件上傳至 Play 管理中心時,系統就會收集這類資料。如要選擇退出此資料收集程序,請移除 build.gradle 檔案中的 $playcoreDir/playcore-native-metadata.jar 匯入項目。

請注意,上述與使用 Play Core 原生 SDK 相關的資料收集行為,以及 Google 使用所收集資料的行為,與您在上傳應用程式套件至 Play 管理中心時,Google 收集 Gradle 中宣告的程式庫依附元件無關。

將 Play Core 原生 SDK 整合到專案後,請在包含 API 呼叫的檔案中加入下列程式碼:

#include "play/app_update.h"

初始化應用程式內更新 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
   }
...
}

檢查更新是否過時

除了檢查是否有可用的更新,建議您也同時確認自從使用者上次收到 Play 商店的更新通知後已過了多久。這可以幫助您判斷是否要啟動彈性更新還是立即更新。例如,您可以等待幾天再通知使用者進行彈性更新,接著過幾天後提出需要立即更新。

您可以利用 AppUpdateInfo_getClientVersionStalenessDays() 查看透過 Play 商店推出更新的天數。

int32_t staleness_days = AppUpdateInfo_getClientVersionStalenessDays(info);

檢查更新的優先順序

您可以透過 Google Play Developer API 設定每項更新的優先順序。應用程式可藉此判斷向使用者建議更新的強烈程度。舉例來說,您可以運用下列策略設定更新的優先順序:

  • 使用者介面微幅調整:低度優先等級更新;不需要提出彈性更新和立即更新的要求。僅在使用者未與應用程式互動時更新。
  • 效能提升:中度優先等級更新;要求彈性更新。
  • 重大安全性更新:高度優先等級更新;要求立即更新。

為了判定優先等級,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 物件並非空值且格式正確。
  • 錯誤代碼為 -5, APP_UPDATE_UNAVAILABLE,表示沒有適用的更新項目。確認目標版本使用相同的 套件名稱應用程式 ID簽署金鑰。如果有可用的更新,請清除應用程式的快取,並再次呼叫 AppUpdateManager_requestAppUpdateInfo(),以重新整理 AppUpdateInfo
  • 錯誤代碼為 -6, APP_UPDATE_NOT_ALLOWED,表示系統不允許 AppUpdateOption 物件指定的更新類型。啟動更新流程之前,請檢查 AppUpdateInfo 物件是否指示允許該更新類型。

後續步驟

測試應用程式的應用程式內更新,以驗證整合是否正常運作。