2021 年 7 月までのGoogle Play Storeで100MB以上の容量のアプリを公開は、
Opaque Binary Blob (OBB)と呼ばれる仕組みを利用することで可能でした。

OBBは100MBのアプリ容量の制限を上回るアプリにおいて、
拡張ファイルを生成することでGooglePlayにアップロードできる機能です。

しかし、下記の発表により、OBBは利用不可となりました。

2021 年 8 月より、Google Play での新規アプリの公開は Android App Bundle で行う必要があります。150 MB を超える新しいアプリは、Play Feature Delivery または Play Asset Deliveryでサポートされるようになります。

【引用元】:APK 拡張ファイル

Unity製のAndroidアプリで容量が150MBを上回った場合の対処法として、
Android App BundlePlay Asset Deliveryについて調査しました。


Android App Bundle(AAB)

まず、Android App Bundle(AAB)について理解が必要です。
簡潔にまとめると、AABとは各ユーザーに適したAPKが生成される仕組みです。

AAB登場以前のAPKは、多様なデバイスや環境のためのリソースを全て一つのAPKに含んでいました。
AABは各ユーザーに適切なリソースをインストール可能にするソリューションです。

結果として、ユーザーごとの適切なリソース及び追加リソースの生成が可能となり、
アプリの容量削減に繋がります。

ただし、AABによって生成されたAPKの容量上限は150MBとなります。


Play Asset Delivery

次にPlay Asset Delivery(PAD)についてです。
公式ドキュメントには下記のように説明があります。

Play Asset Delivery(PAD)を使用することで、App Bundle のメリットをゲームでも活用できます。ゲームに必要なすべてのリソースを含む単一アーティファクトを Google Play で公開することで、150 MB を超えるゲームで以前の拡張ファイル(OBB)を置き換えることができます。

【引用元】:Play Asset Delivery

PADという仕組みにより、150MB以上の容量のアプリでもGooglePlay Storeに公開できるようになります。
先ほどのAABに記述した追加リソースの生成がPADで活かされます。

PADについて一言でまとめると、
AABから”ベースとなるAPK”と”追加リソース”を分割して生成し、追加リソースを任意のタイミングでインストールする一連の仕組みのことです。

PADを利用する大まかな流れは下記です。

①アセットパックを作成する
②アセットパックをダウンロードする処理を実装する
③アセットパックを内包したAABをビルドする
④AABをストアに内部テストとして公開する


アセットパックを作成する

まずアセットパックについて説明が必要です。
Unity以外の環境(Javaやネイティブ)においての使用も想定した抽象化された表現です。
Unityにおいてはアセットバンドルのことを指します。

今回、アセットバンドル作成にはUnity Asset Bundle Browser toolを利用します。
導入方法については下記公式ドキュメントに記載があるため割愛します。
【公式ドキュメント】:Unity Asset Bundle Browser tool

導入が完了したらWindow -> AssetBundle Browserの順に選択し、タブを開きます。


Configureにアセットバンドル化したいPrefabやSceneをドラッグアンドドロップします。

今回はテスト用に合計300MB越えの動画ファイルを内包したSceneをアセットバンドル化していきます。
下記のようにSceneをドラッグアンドドロップするだけで、内包しているアセット群を自動で解釈してくれます。

あとはBuildするだけです。
注意すべき設定項目はBuild TargetOutPut Pathの2つです。
Build TargetはAndroid、OutPut PathはAssets配下以外の場所に設定します。設定したらBuildを押下します。

ここまでの処理でアセットバンドルの作成が完了です。


アセットパックをダウンロードする処理を実装する

用意したアセットバンドルをダウンロードする処理が必要となります。
その処理はGoogleが用意したライブラリを使用することで実装可能となります。

下記のunitypackageを取得し、プロジェクトにインポートします。
com.google.android.appbundle-1.5.0.unitypackage
com.google.play.assetdelivery-1.5.0.unitypackage

下記から取得可能です。
【GitHub】: google / play-unity-plugins

早速ですが、コードです。
アセットバンドルの読み込み処理を行い、アセットバンドル内のScene情報を開き、遷移するサンプルです。
読み込みの処理のため、非同期処理が必要となります。

using System.Threading;
using Cysharp.Threading.Tasks;
using Google.Play.AssetDelivery;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

public class BundleLoader : MonoBehaviour
{
    /// <summary>
    /// 進捗率のテキスト
    /// </summary>
    [SerializeField] private Text _progressText;
    
    /// <summary>
    /// シーン遷移ボタン
    /// </summary>
    [SerializeField] private Button _sceneChangeButton;
    
    /// <summary>
    /// 読み込んだアセットバンドル内のシーンのパス群用変数
    /// </summary>
    private string[] _scenePaths;
    
    /// <summary>
    /// 読み込んだアセットバンドル用変数
    /// </summary>
    private AssetBundle _assetBundle;

    private void Start()
    {
        _sceneChangeButton.gameObject.SetActive(false);
        _sceneChangeButton.onClick.AddListener(onClick);
        LoadAssetBundleAsync("movie",this.GetCancellationTokenOnDestroy()).Forget();
    }

    private void OnDestroy()
    {
        _sceneChangeButton.onClick.RemoveListener(onClick);
    }

    /// <summary>
    /// アセットバンドルをロードする
    /// </summary>
    /// <param name="assetBundleName">アセットバンドルの名前</param>
    /// <param name="ct">キャンセルトークン</param>
    /// <returns></returns>
    private async UniTask LoadAssetBundleAsync(string assetBundleName,CancellationToken ct)
    {
        var bundleRequest = PlayAssetDelivery.RetrieveAssetBundleAsync(assetBundleName);

        //Wi-Fiの接続をチェック
        await checkWifiAsync(bundleRequest, ct);
        
        while (!bundleRequest.IsDone)
        {
            //Wi-Fiの接続をチェック
            await checkWifiAsync(bundleRequest, ct);

            //進捗率
            _progressText.text = bundleRequest.DownloadProgress * 100f + "%";

            await UniTask.Yield(ct);
        }

        if (bundleRequest.Error != AssetDeliveryErrorCode.NoError)
        {
            //何かしらのエラーはここで拾う
            await UniTask.Yield(ct);
        }
        
        //ボタン表示
        _sceneChangeButton.gameObject.SetActive(true);

        //DLに成功したらアセットバンドルの中身を参照する
        _assetBundle = bundleRequest.AssetBundle;
        _scenePaths = _assetBundle.GetAllScenePaths();
    }

    /// <summary>
    /// Wi-Fiの接続をチェックする
    /// </summary>
    /// <param name="playAssetBundleRequest">リクエストしたアセットバンドル</param>
    /// <param name="ct">キャンセルトークン</param>
    private async UniTask checkWifiAsync(PlayAssetBundleRequest playAssetBundleRequest,CancellationToken ct)
    {
        //アセットバンドルが150MB以上の場合、Wi-Fiの接続が前提となる 接続が無い場合はユーザーにWi-Fi無しでDLしても良いか確認する。
        //150MB超えているかどうかは自動で判定
        if (playAssetBundleRequest.Status == AssetDeliveryStatus.WaitingForWifi)
        {
            //Wi-Fi無しでDLしても良いか確認
            var userConfirmationOperation = PlayAssetDelivery.ShowCellularDataConfirmation();

            //ユーザーの入力を待つ
            await userConfirmationOperation.ToUniTask(cancellationToken: ct).AttachExternalCancellation(ct);

            if ((userConfirmationOperation.Error != AssetDeliveryErrorCode.NoError) ||
                (userConfirmationOperation.GetResult() != ConfirmationDialogResult.Accepted))
            {
                // ユーザーが拒否した時の処理
            }

            // Wi-Fiに接続された、もしくは"未接続でも可"が承認されるのを待つ
            await UniTask.WaitWhile(() => playAssetBundleRequest.Status != AssetDeliveryStatus.WaitingForWifi, cancellationToken: ct);
        }
    }

    ///ボタン押下時の処理
    private void onClick()
    {
        SceneManager.LoadScene(_scenePaths[0]);
    }
}

アセットバンドルの容量が150MBを超えた際にはWi-Fiでのみ自動DLされる仕組みになっています。
そのため、150MBを超えた際には下記のようにダイアログでユーザーに確認する必要があります。


専用の関数が用意されており、コード内でWi-Fi接続が切れたタイミングで呼び出しています。


③アセットパックを内包したAABをビルドする

先ほどインポートしたGoogleが用意したライブラリが正常に動作している場合、
メニューバーにGoogleのタブが出現しています。
下記画像を参考にGoogle -> Android App Bundle -> Asset Delivery Settingsを選択します。

下記画像のAdd Folder(①)を選択して先ほど作成したアセットバンドルを設定します。
Delivery Mode(②)はいつDLするかのタイミングを選べます。

ここまでの設定が完了したら、Google -> Build Android App Bundle を選択することで、ABBをビルドできます。


Delivery Mode

Delivery Modeを切り替えることで、アセットバンドルのインストールされるタイミングを設定可能です。

install-time Asset Pack は、アプリのインストール時に、分割 APK(APK セットの一部)として配信されます。この Asset Pack は「Upfront」Asset Pack と呼ばれることもあり、アプリを起動するとすぐに使用できます。また、Google Play ストアに表示されるアプリのサイズには、これらの Asset Pack が含まれます。これらの Asset Pack をユーザーが変更または削除することはできません。

fast-follow Asset Pack は、アプリがインストールされるとすぐに自動的にダウンロードされます。そのため、fast-follow Asset Pack のダウンロードを開始するためにユーザーがアプリを開く必要はありません。この自動ダウンロードによってアプリを開けなくなることはありません。また、Google Play ストアに表示されるアプリのサイズには、これらの Asset Pack が含まれます。

on-demand アセットパックは、アプリの実行中にダウンロードされます。

【引用元】:Play Asset Delivery

簡潔にまとめると下記となります。

モードタイミング
install-timeインストール時
fast-followインストール直後
on-demandアプリの起動中

install-time、fast-followの場合、アプリ容量にアセットバンドルの容量も含まれるため、
AABの総容量は150MBを超え、ストアページにおいても同様に150MBを超えた容量が表記されます。
AABの容量上限が150MBだったはずなのになぜ150MB越えのAABをストアにアップロードできるのか、
解釈が少し難しいですが、AABのアプリ容量上限はあくまでアセットバンドルを除外した容量の話です。
ですので、インストール時に150MBを超えた数値が表記されていてもそれはあくまでアプリの総容量を指します。

関連して、150MB超えのアプリのDL時にWi-Fi接続されていない場合、
下記のようなDLを継続するかどうかのダイアログが出ます。


on-demandの場合のみ、アプリ容量が初回DL時にアセットバンドルを除外した値で表記されています。
下記画像は実際の数値です。
アップロードしたAABは350MB程ですが、
バンドル部分は後程ダウンロードされるため、37MB程になっています。


④AABをストアに内部テストとして公開する

ストアのページから内部テストを実施することができます。
内部テストとは、開発者が設定した任意のユーザーに向けて本番環境を想定したテストを行う機能です。
ストアからのダウンロード~実際にアプリを体験 まで実施できます。

【参考リンク】:内部アプリ共有を使用したテスト

以下、手順です。
Google Play Consoleにログインし、下記画像赤枠の内部テストを選択します。

あとは先ほど作成したAABをアップロードして、コンソール上の内部テスト公開の手順に従うだけです。

テストユーザーの追加は内部テストのテスター数というタブから可能です。
テスターのメールアドレスはGoogleアカウントの所有者のGmailのみ登録可能です。
共有リンクもテスター数というタブの下部から発行可能です。


リンクを共有されたテスターは、下記のようなテストページから実際のストアを模したページに遷移し、
アプリをインストールできます。


デモ

アプリ起動からWi-Fi接続のダイアログの挙動のデモです。
Wi-Fi接続が切れたことを検知して警告ダイアログが表示されました。
その後の接続復帰後の処理も問題ありません。


ダウンロード完了~Scene遷移までです。
ちゃんと300MB超えのSceneを読み込むことができました。


おわりに

PADはしっかりと理解して使用すれば、非常に便利な機能だと感じました。
一方でエラーハンドリング等をしっかりと行う必要もあるので引き続き調査していこうと思います。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる