整合資產提供功能 (Kotlin 和 Java)

請按照本指南中的步驟,透過 Java 程式碼存取應用程式的資產包。

專為 Kotlin 和 Java 建構

請按照下列步驟將 Play Asset Delivery 建構到專案的 Android App Bundle 中。執行時不必使用 Android Studio。

  1. 將專案 build.gradle 檔案中 Android Gradle 外掛程式的版本更新為 4.0.0 以上版本。

  2. 在專案的頂層目錄中,為資產包建立目錄。系統會使用這個目錄名稱做為資產包名稱。資產包名稱開頭須為英文字母,而且只能使用英文字母、數字和底線。

  3. 在資產包目錄中建立 build.gradle 檔案,並新增下列程式碼。請務必指定資產包的名稱,並且僅指定一種提供類型:

    Groovy

    // In the asset pack's build.gradle file: plugins {   id 'com.android.asset-pack' }  assetPack {     packName = "asset-pack-name" // Directory name for the asset pack     dynamicDelivery {         deliveryType = "[ install-time | fast-follow | on-demand ]"     } }

    Kotlin

    // In the asset pack's build.gradle.kts file: plugins {   id("com.android.asset-pack") }  assetPack {   packName.set("asset-pack-name") // Directory name for the asset pack   dynamicDelivery {     deliveryType.set("[ install-time | fast-follow | on-demand ]")   } }
  4. 在專案的應用程式 build.gradle 檔案中,新增專案中每個資產包的名稱,如下所示:

    Groovy

    // In the app build.gradle file: android {     ...     assetPacks = [":asset-pack-name", ":asset-pack2-name"] }

    Kotlin

    // In the app build.gradle.kts file: android {     ...     assetPacks += listOf(":asset-pack-name", ":asset-pack2-name") }
  5. 在專案的 settings.gradle 檔案中,納入專案中的所有資產包,如下所示:

    Groovy

    // In the settings.gradle file: include ':app' include ':asset-pack-name' include ':asset-pack2-name'

    Kotlin

    // In the settings.gradle.kts file: include(":app") include(":asset-pack-name") include(":asset-pack2-name")
  6. 在資產包目錄中建立以下子目錄:src/main/assets

  7. 將資產放在 src/main/assets 目錄中。您也可以在其中建立子目錄。目前應用程式的目錄結構應如下所示:

    • build.gradle
    • settings.gradle
    • app/
    • asset-pack-name/build.gradle
    • asset-pack-name/src/main/assets/your-asset-directories
  8. 使用 Gradle 建構 Android App Bundle。在產生的應用程式套件中,根層級目錄現在包含下列項目:

    • asset-pack-name/manifest/AndroidManifest.xml:設定資產包的 ID 和提供模式
    • asset-pack-name/assets/your-asset-directories:此目錄包含透過資產包提供的所有資產

    Gradle 為每個資產包產生資訊清單,也會替您輸出 assets/ 目錄。

  9. (選用) 如果您打算使用快速追蹤和隨選提供功能,請加入 Play Asset Delivery 程式庫

    Groovy

    implementation "com.google.android.play:asset-delivery:2.3.0" // For Kotlin use asset-delivery-ktx implementation "com.google.android.play:asset-delivery-ktx:2.3.0"

    Kotlin

    implementation("com.google.android.play:asset-delivery:2.3.0") // For Kotlin use core-ktx implementation("com.google.android.play:asset-delivery-ktx:2.3.0")

  10. (選用) 調整應用程式套件的設定,支援不同的紋理壓縮格式

與 Play Asset Delivery API 整合

Play Asset Delivery Java API 提供 AssetPackManager 類別,可用來要求資產包、管理下載內容以及存取資產。請務必先將 Play Asset Delivery 程式庫加入專案。

您可以根據想要存取的資產包提供類型實作此 API。步驟請參見下方流程圖。

Java 程式設計語言的資產包流程圖

圖 1. 存取資產包的流程圖

安裝時提供

設定為 install-time 的資產包可在應用程式啟動時立即使用。使用 Java AssetManager API 存取在此模式下提供的資產:

Kotlin

import android.content.res.AssetManager ... val context: Context = createPackageContext("com.example.app", 0) val assetManager: AssetManager = context.assets val stream: InputStream = assetManager.open("asset-name")

Java

import android.content.res.AssetManager; ... Context context = createPackageContext("com.example.app", 0); AssetManager assetManager = context.getAssets(); InputStream is = assetManager.open("asset-name");

快速追蹤及隨選提供

以下各節將說明如何在下載前取得資產包的相關資訊、如何呼叫 API 以啟動下載作業,以及如何存取已下載的資產包。這部分的內容適用於 fast-followon-demand 資產包。

檢查狀態

每個資產包會儲存在應用程式內部儲存空間的獨立資料夾中。使用 getPackLocation() 方法來判定資產包的根資料夾。這個方法會回傳下列值:

傳回值 狀態
有效的 AssetPackLocation 物件 隨時可在 assetsPath() 立即存取資產包根資料夾
null 無法使用未知的資產包或資產

取得資產包的下載資訊

應用程式必須先揭露下載大小,才能擷取資產包。使用 requestPackStates()getPackStates() 方法來判定下載內容的大小,以及是否正在下載資產包。

Kotlin

suspend fun requestPackStates(packNames: List<String>): AssetPackStates

Java

Task<AssetPackStates> getPackStates(List<String> packNames)

requestPackStates() 是用來回傳 AssetPackStates 物件的暫停函式,getPackStates() 則是回傳 Task<AssetPackStates> 的非同步方法。AssetPackStates 物件的 packStates() 方法會回傳 Map<String, AssetPackState>。此對應包含每個要求資產包的狀態,並以名稱做為索引鍵:

Kotlin

AssetPackStates#packStates(): Map<String, AssetPackState>

Java

Map<String, AssetPackState> AssetPackStates#packStates()

最終要求如下所示:

Kotlin

const val assetPackName = "assetPackName" coroutineScope.launch {   try {     val assetPackStates: AssetPackStates =       manager.requestPackStates(listOf(assetPackName))     val assetPackState: AssetPackState =       assetPackStates.packStates()[assetPackName]   } catch (e: RuntimeExecutionException) {     Log.d("MainActivity", e.message)   } }

Java

final String assetPackName = "myasset";  assetPackManager     .getPackStates(Collections.singletonList(assetPackName))     .addOnCompleteListener(new OnCompleteListener<AssetPackStates>() {         @Override         public void onComplete(Task<AssetPackStates> task) {             AssetPackStates assetPackStates;             try {                 assetPackStates = task.getResult();                 AssetPackState assetPackState =                     assetPackStates.packStates().get(assetPackName);             } catch (RuntimeExecutionException e) {                 Log.d("MainActivity", e.getMessage());                 return;             })

下列 AssetPackState 方法提供資產包的大小、目前為止的下載數量 (如有要求),以及已轉移到應用程式的數量:

如要取得資產包的狀態,請使用 status() 方法傳回整數形式的狀態,而此整數會對應至 AssetPackStatus 類別中的特定常數欄位。尚未安裝的資產包處於 AssetPackStatus.NOT_INSTALLED 狀態。

如果要求失敗,請使用 errorCode() 方法,其傳回值會對應至 AssetPackErrorCode 類別中的特定常數欄位。

安裝

若是第一次下載資產包,或若要呼叫以完成資產包更新作業,請使用 requestFetch()fetch() 方法:

Kotlin

suspend fun AssetPackManager.requestFetch(packs: List<String>): AssetPackStates

Java

Task<AssetPackStates> fetch(List<String> packNames)

這個方法會回傳 AssetPackStates 物件,其中包含資產包清單,以及其初始下載狀態和大小。如果正在下載透過 requestFetch()fetch() 要求的資產包,系統會回傳下載狀態,且不會開始其他下載作業。

監控下載狀態

您應實作 AssetPackStateUpdatedListener 以追蹤資產包的安裝進度。系統會按照每個資產包細分狀態更新內容,支援個別資產包的狀態追蹤功能。您可以開始使用可用的資產包,不必等到要求的所有其他下載作業完成。

Kotlin

fun registerListener(listener: AssetPackStateUpdatedListener) fun unregisterListener(listener: AssetPackStateUpdatedListener)

Java

void registerListener(AssetPackStateUpdatedListener listener) void unregisterListener(AssetPackStateUpdatedListener listener)

大型下載內容

如果下載檔案大小超過 200 MB,且使用者未連上 Wi-Fi 網路,則只有在使用者明確同意使用行動數據連線進行下載時,下載作業才會開始。同樣,如果下載內容較大,且使用者未連接 Wi-Fi 網路,則系統會暫停下載並明確取得同意,才能透過行動數據連線進行下載。已暫停的資產包處於 WAITING_FOR_WIFI 狀態。如要觸發使用者介面流程來提示使用者同意聲明,請使用 showConfirmationDialog() 方法。

請注意,如果應用程式未呼叫這個方法,下載作業就會暫停,且只在使用者再次連上 Wi-Fi 網路時,才會自動繼續下載。

需要使用者確認

如果套件的狀態為 REQUIRES_USER_CONFIRMATION,使用者必須接受隨 showConfirmationDialog() 顯示的對話方塊,下載作業才會繼續。如果 Play 無法辨識應用程式 (例如應用程式是側載),就會出現這個狀態。請注意,在這種情況下呼叫 showConfirmationDialog() 會導致應用程式更新。更新後,你必須再次要求資產。

以下是事件監聽器的實作範例:

Kotlin

private val activityResultLauncher = registerForActivityResult(     ActivityResultContracts.StartIntentSenderForResult() ) { result ->     if (result.resultCode == RESULT_OK) {         Log.d(TAG, "Confirmation dialog has been accepted.")     } else if (result.resultCode == RESULT_CANCELED) {         Log.d(TAG, "Confirmation dialog has been denied by the user.")     } }  assetPackManager.registerListener { assetPackState ->   when(assetPackState.status()) {     AssetPackStatus.PENDING -> {       Log.i(TAG, "Pending")     }     AssetPackStatus.DOWNLOADING -> {       val downloaded = assetPackState.bytesDownloaded()       val totalSize = assetPackState.totalBytesToDownload()       val percent = 100.0 * downloaded / totalSize        Log.i(TAG, "PercentDone=" + String.format("%.2f", percent))     }     AssetPackStatus.TRANSFERRING -> {       // 100% downloaded and assets are being transferred.       // Notify user to wait until transfer is complete.     }     AssetPackStatus.COMPLETED -> {       // Asset pack is ready to use. Start the game.     }     AssetPackStatus.FAILED -> {       // Request failed. Notify user.       Log.e(TAG, assetPackState.errorCode())     }     AssetPackStatus.CANCELED -> {       // Request canceled. Notify user.     }     AssetPackStatus.WAITING_FOR_WIFI,     AssetPackStatus.REQUIRES_USER_CONFIRMATION -> {       if (!confirmationDialogShown) {         assetPackManager.showConfirmationDialog(activityResultLauncher);         confirmationDialogShown = true       }     }     AssetPackStatus.NOT_INSTALLED -> {       // Asset pack is not downloaded yet.     }     AssetPackStatus.UNKNOWN -> {       Log.wtf(TAG, "Asset pack status unknown")     }   } }

Java

assetPackStateUpdateListener = new AssetPackStateUpdateListener() {     private final ActivityResultLauncher<IntentSenderRequest> activityResultLauncher =       registerForActivityResult(           new ActivityResultContracts.StartIntentSenderForResult(),           new ActivityResultCallback<ActivityResult>() {             @Override             public void onActivityResult(ActivityResult result) {               if (result.getResultCode() == RESULT_OK) {                 Log.d(TAG, "Confirmation dialog has been accepted.");               } else if (result.getResultCode() == RESULT_CANCELED) {                 Log.d(TAG, "Confirmation dialog has been denied by the user.");               }             }           });      @Override     public void onStateUpdate(AssetPackState assetPackState) {       switch (assetPackState.status()) {         case AssetPackStatus.PENDING:           Log.i(TAG, "Pending");           break;          case AssetPackStatus.DOWNLOADING:           long downloaded = assetPackState.bytesDownloaded();           long totalSize = assetPackState.totalBytesToDownload();           double percent = 100.0 * downloaded / totalSize;            Log.i(TAG, "PercentDone=" + String.format("%.2f", percent));           break;          case AssetPackStatus.TRANSFERRING:           // 100% downloaded and assets are being transferred.           // Notify user to wait until transfer is complete.           break;          case AssetPackStatus.COMPLETED:           // Asset pack is ready to use. Start the game.           break;          case AssetPackStatus.FAILED:           // Request failed. Notify user.           Log.e(TAG, assetPackState.errorCode());           break;          case AssetPackStatus.CANCELED:           // Request canceled. Notify user.           break;          case AssetPackStatus.WAITING_FOR_WIFI:         case AssetPackStatus.REQUIRES_USER_CONFIRMATION:           if (!confirmationDialogShown) {             assetPackManager.showConfirmationDialog(activityResultLauncher);             confirmationDialogShown = true;           }           break;          case AssetPackStatus.NOT_INSTALLED:           // Asset pack is not downloaded yet.           break;         case AssetPackStatus.UNKNOWN:           Log.wtf(TAG, "Asset pack status unknown")           break;       }     } }

您也可以使用 getPackStates() 方法取得目前下載作業的狀態。AssetPackStates 包含下載進度、下載狀態和任何失敗錯誤代碼。

存取資產包

在下載要求達到 COMPLETED 狀態後,您可以使用檔案系統呼叫來存取資產包。使用 getPackLocation() 方法取得資產包的根資料夾。

資產會儲存在資產包根目錄的 assets 目錄中。您可以使用便利的方法 assetsPath(),取得 assets 目錄的路徑。請使用以下方法取得特定資產的路徑:

Kotlin

private fun getAbsoluteAssetPath(assetPack: String, relativeAssetPath: String): String? {     val assetPackPath: AssetPackLocation =       assetPackManager.getPackLocation(assetPack)       // asset pack is not ready       ?: return null      val assetsFolderPath = assetPackPath.assetsPath()     // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets")     return FilenameUtils.concat(assetsFolderPath, relativeAssetPath) }

Java

private String getAbsoluteAssetPath(String assetPack, String relativeAssetPath) {     AssetPackLocation assetPackPath = assetPackManager.getPackLocation(assetPack);      if (assetPackPath == null) {         // asset pack is not ready         return null;     }      String assetsFolderPath = assetPackPath.assetsPath();     // equivalent to: FilenameUtils.concat(assetPackPath.path(), "assets");     String assetPath = FilenameUtils.concat(assetsFolderPath, relativeAssetPath);     return assetPath; }

其他 Play Asset Delivery API 方法

以下列出一些您可能想在應用程式中使用的其他 API 方法。

取消要求

使用 cancel() 取消啟用的資產包要求。請注意,我們會盡可能履行這項要求。

移除資產包

使用 requestRemovePack()removePack() 安排資產包的移除作業。

取得多個資產包的位置

使用 getPackLocations() 查詢多個資產包的狀態,進而回傳資產包及其位置的對應圖。getPackLocations() 回傳的對應圖包含每個已下載且處於最新狀態的資產包的輸入項。

下一步

在本機和 Google Play 中測試 Play Asset Delivery