Asset Delivery を統合する(Kotlin と Java)

Java コードからアプリのアセットパックにアクセスするには、このガイドの手順を実施します。

Kotlin と Java 用にビルドする

プロジェクトの Android App Bundle に Play Asset Delivery を組み込む手順は次のとおりです。この手順で Android Studio を使用する必要はありません。

  1. プロジェクトの build.gradle ファイル内の Android Gradle プラグインのバージョンを 4.0.0 以降に更新します。

  2. プロジェクトの最上位ディレクトリに、アセットパックのディレクトリを作成します。このディレクトリ名はアセットパック名として使用されます。アセットパック名の先頭は英字にしてください。その後には、英字、数字、アンダースコアのみ使用できます。

  3. アセットパックのディレクトリに build.gradle ファイルを作成し、下記のコードを入力します。アセットパックの名前と、配信タイプを 1 つだけ指定します。

    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 をビルドします。生成された App Bundle のルートレベルのディレクトリには、以下が含まれるようになりました。

    • asset-pack-name/manifest/AndroidManifest.xml: アセットパックの識別子と配信モードを設定します
    • asset-pack-name/assets/your-asset-directories: アセットパックの一部として配信されるすべてのアセットを含むディレクトリです

    Gradle は各アセットパックのマニフェストを生成し、assets/ ディレクトリを出力します。

  9. (省略可)fast-follow 配信や on-demand 配信を使用する場合は、Play Asset Delivery Library を含めます。

    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. (省略可)さまざまなテクスチャ圧縮形式をサポートするように App Bundle を構成します。

Play Asset Delivery API と統合する

Play Asset Delivery Java API には、アセットパックのリクエスト、ダウンロードの管理、アセットへのアクセスを行うための AssetPackManager クラスが用意されています。まず、プロジェクトに Play Asset Delivery Library を追加します。

アクセスするアセットパックの配信タイプに応じて、この 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");

fast-follow 配信と on-demand 配信

以下のセクションでは、アセットパックに関する情報をダウンロード前に取得する方法、API を呼び出してダウンロードを開始する方法、ダウンロードしたパックにアクセスする方法について説明します。以下のセクションは、fast-follow アセットパックと on-demand アセットパックに適用されます。

ステータスを確認する

各アセットパックは、アプリの内部ストレージ内の個別のフォルダに保存されます。アセットパックのルートフォルダを確認するには、getPackLocation() メソッドを使用します。このメソッドは以下の値を返します。

戻り値 ステータス
有効な AssetPackLocation オブジェクト アセットパックのルートフォルダは assetsPath() ですぐにアクセスできます
null 不明なアセットパックまたはアセットは利用できません

アセットパックのダウンロード情報を取得する

アプリは、アセットパックを取得する前に、ダウンロード サイズを開示する必要があります。ダウンロードのサイズを確認し、パックがすでにダウンロード中かどうかを判別するには、requestPackStates() または getPackStates() メソッドを使用します。

Kotlin

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

Java

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

requestPackStates()AssetPackStates オブジェクトを返す suspend 関数ですが、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 の状態になります。ユーザーに同意を求める UI フローをトリガーするには、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 をテストします。