整合資產提供功能 (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.2.2"
    // For Kotlin use asset-delivery-ktx
    implementation "com.google.android.play:asset-delivery-ktx:2.2.2"

    Kotlin

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

  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