Addressables × Play Asset Deliveryの調査メモ

# Play Asset Deliveryとは

参考: [Google公式ドキュメント - Play Asset Delivery](https://developer.android.com/guide/app-bundle/asset-delivery?hl=ja)

旧来はGoogle Playにアップロードするapkには100MBという制限があって、収まりきらないリソースを別途アップロードする Opaque Binary Blob (OBB) という仕組みが存在した。
2021年8月からは Android App Bundle (ABB) という新しい仕組みが必須対応となり、この仕組みにおけるOBBに相当する機能が Play Asset Delivery。

Play Asset Deliveryでは**アセットパック**という単位でリソースを扱う。アセットパックには以下の配信モードを紐付けることができる。
| 配信モード名 | ダウンロードタイミング | サイズ制限 | ストアのアプリサイズ | 削除可能性 | 注意事項 |
| ------------- | ------------------------ | ----------- | --- | --- | --- |
| install-time | アプリインストール時 |  1GB | 含まれる | AABの機能によって1つのapkとしてインストールされるので削除されない |
| fast-follow | インストール直後 | 512MB | 含まれる | アプリの内部ストレージ領域に展開され、削除の可能性がある | アプリ起動時にDLが完了していない可能性がある | 
| on-demand | リクエスト時 | 512MB | 含まれない | アプリの内部ストレージ領域に展開され、削除の可能性がある | |

# Android App Bundle (AAB) とは

参考: [Google公式ドキュメント - Android App Bundle](https://developer.android.com/guide/app-bundle?hl=ja)

従来のapkは1つのファイルの中にx64やarm64といったCPUアーキテクチャ別のバイナリや、hdpiやxhdpiといった画面密度別のリソースなどが全て含まれていた。
これだと不要なデータまで含まれてしまって無駄なので、細かく分割して動的にapkを構成してインストールする仕組みが生み出された。その仕組みに対応したファイル形式がAndroid App Bundle。
イメージとしては[Android Developers Blog](https://android-developers.googleblog.com/2018/05/io-2018-everything-new-in-google-play.html)のgifが分かりやすい。

# UnityにおけるAAB、Play Asset Delivery対応

参考: [Unity公式ドキュメント - Play Asset Delivery](https://docs.unity3d.com/ja/2021.2/Manual/play-asset-delivery.html)

UnityでもAABおよびPlay Asset Deliveryを利用する仕組みは提供されている。[Unity設定](#unity設定)に示す項目を設定してビルドするだけでPlay Asset Delivery対応のAABが成果物として出力される。

Unityが内部でどのようなことをしているかは[Unityの「Split Application Binary」を用いて、GooglePlay Storeで150MBより大きいアプリケーションを配信する](https://blog.yucchiy.com/2023/12/unity-split-application-binary-and-pad/)という記事で詳しく調査されています。

## 仕様

- 対象アセットのサイズが1GB未満の場合、全て1つのアセットパックにまとめて、`install-time`配信モードになる
    - この場合、追加実装は**不要**。ストリーミングアセットにアクセスする際の`Application.streamingAssetsPath`で取得できるパスがアセットパックを考慮したものになっている
- 対象アセットのサイズが1GB以上の場合、ストリーミングアセットで1つ、その他で1つのアセットパックにまとめて、大きい方が`install-time`、小さい方が`fast-follow`配信モードになる。
    - この場合、追加実装が**必要**。`fast-follow`配信モードはインストール後にDLが開始するけれど、必要なタイミングまでにDLが完了しているとは限らないのでハンドリング処理が必要になる

**カスタムアセットパック**という仕組みを使うことで任意のアセット、任意の配信モードのアセットパックを作ることもできる。

## Unity設定

### ① 出力をapkからaabにする
1. File > Build Settings から Build Settingsウィンドウを開く
2. PlatformのAndroid設定において、**Build App Bundle (Google Play)** を有効にする
  ※ **Export Project** が有効の場合、**Export for App Bundle** という項目名に変化している

### ② ベースモジュールとアセットパックを分割する
1. Edit > Project Settings から Project Settingsウィンドウを開き、Playerカテゴリを選択する
2. Android タブを開き、Publishing Settings セクションを選択する
3. **Split Application Binary**  を有効にする

## 成果物(aab)に含まれるもの

- ベースモジュール
    - プラグイン、スクリプト、アセット、ビルドインデックスが0のシーンの実行ファイル
- アセットパック
    - 残りのシーン、リソース、ストリーミングアセットなど

## 実機上のパス

- `install-time`
    - apk領域
    - jar:file:///data/app/[アプリのID]-[ハッシュ値]/split_[アセットパック名].apk!/assets/[アセットのパス]
- `fast-follow`, `on-demand`
    - 内部ストレージ領域 (Javaの`getFilesDir()`で取得できるパス)
    - /data/data/[アプリのID]/files/assetpacks/[アセットパック名]/[Bundle Version Code]/[Bundle Version Code]/assets/[アセットパス]

## 動作確認について

参考: [Google公式ドキュメント - アセット配信をテストする](https://developer.android.com/guide/playcore/asset-delivery/test?hl=ja)

UnityエディタにAndroid実機を繋いで**Build And Run**すれば動作確認ができる。
`install-time`はアプリのインストールと一緒にインストールされる。
`fast-follow`は`on-demand`と同様の動作となる (明示的にリクエストしないとDLされない)。

注意事項は下記
- Google Playからではなく外部ストレージからパックを取得するのでネットワークエラーのテストはできない
- Wi-Fi接続を待つテストはできない
- アセットパックのアップデートはサポートされない。事前にアンインストールしたりデータ消去する必要がある

多少手間はかかるけれど、Google Playの[内部テスト](https://support.google.com/googleplay/android-developer/answer/9844679?hl=ja)で動作確認するほうがトータルの手間は少ないかもしれない。

## Addressable Asset Systemの場合

配置先がローカル (= ストリーミングアセット) になっているグループのAssetBundleが全てアセットパックに含まれる。
ファイルサイズが1GB未満で`install-time`だけでいいなら特に追加実装は不要。
ファイルサイズが1GB以上になると前述のようにアセットパックが分割されて小さいほうが`fast-follow`配信モードになるので追加実装が必要。
※ 追加の実装 = `fast-follow`や`on-demand`のアセットパックの場合、必要時にDLされていなければDLする処理

追加実装は[Addressables公式サンプル](https://github.com/Unity-Technologies/Addressables-Sample)から必要なファイルを持ってきて、適宜改変するのが楽。

> **Warning**
> Addressables 1.21.1で`GenerateLocationListsTask.cs`に入った変更によって、カタログに記載されるInternalIdのセパレータが`/`から`\`に変わったため、公式サンプルのコードのままでは正常に動作しない。
> `PlayAssetDeliveryAssetBundleProvider.cs`と`PlayAssetDeliveryInitialization.cs`のInternalIdに関する操作のところで、セパレータの置換をしてやれば動作するようになる。

### 作業手順

[Unity設定](#unity設定)も必要

1. [Addressables公式サンプル](https://github.com/Unity-Technologies/Addressables-Sample)をクローンする
2. `Addressables-Sample/Advanced/Play Asset Delivery/Assets/PlayAssetDelivery`フォルダを`Assets`配下にコピーする
3. `Assets/AddressableAssetsData/AddressableAssetSettings.asset`のインスペクタを開き、**Build and Play Mode Script**に`Assets/PlayAssetDelivery/Data/BuildScriptPlayAssetDelivery.asset`を追加する
4. 同インスペクタの**Initialization Objects**に`Assets/PlayAssetDelivery/Data/PlayAssetDeliveryInitializationSettings.asset`を追加する
5. Addressables Groupsウィンドウにて、Play Asset Deliveryに含めたいグループのインスペクタを開き、Add Schemaボタンを押下して、**Play Asset Delivery**を選択して追加する。配信モードを選択できるので任意の設定を行う
6. 同インスペクタの**Asset Bundle Provider**の項目を**Play Asset Delivery Provider**に変更する
7. **Build & LoadPaths**の項目を**Local**に変更する
8. アセットのビルドは**Default Build Script**ではなく**Play Asset Delivery**を用いる

### 実装調査

#### ビルドスクリプト

**Play Asset Delivery**の実体は`BuildScriptPlayAssetDelivery.cs`で、**Default Build Script**の実体である`BuildScriptPackedMode.cs`を継承している。内部の処理も継承元であるBuildScriptPackedModeのビルド処理を実行後にPlay Asset Delivery用の追加処理を実行しているだけなので、プラットフォーム問わず導入後はこちらに切り替えて構わない。

#### リソースプロバイダ

PlayAssetDeliveryProviderはアセットバンドル用のプロバイダであるAssetBundleProviderを継承している。
Android実機以外のプラットフォームでは親のAssetBundleProviderとして振る舞う。
Android実機ではアセットパックの状況を確認し、必要ならDLしてから親のAssetBundleProviderとして振る舞う。

#### グループスキーマ

`CustomAssetPackSettings.asset`のインスペクタでアセットパックの定義をする。
カスタムアセットパックを定義した場合、必要な`[アセットパック名].androidpack`フォルダや`build.gradle`を自動で生成する。
Play Asset Delivery用のグループスキーマを追加することで、グループごとにどのアセットパックに含めるか指定できる。

### Unityの仕様変更

アセットパックの配置仕様がパッチバージョンで変わっているので注意が必要。 
リリースノートでは `UUM-15109` というIssueIDで表記されている。
2021では3.15から、2022では2.9から、2023では1.0から変更されている。

- 変更前
  - [アセットパック名].androidpack/XXX.bundle
- 変更後
  - [アセットパック名].androidpack/src/main/assets/XXX.bundle

# AABを調べてみる

適当なサンプルプロジェクトを`ARMv7`と`ARM64`アーキテクチャ向けにビルドした`Test.aab`を調べてみる。

![ 2023-01-19 17.17.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/0d9c7a28-2fa1-bc6c-ed45-393b46660e75.png)

## aabを展開する

Google公式で[bundletool](https://github.com/google/bundletool)というツールが提供されている。これを使うことでAABファイルを展開できる。
Homebrewでもインストール可能 ([Homebrew Formulae - bundletool](https://formulae.brew.sh/formula/bundletool)) なのでMacOSやLinuxの場合はそちらを使うほうが楽かもしれない。

### aabからapkセットを生成する

bundletoolのbuild-apksコマンドでaabからapkセットファイルを生成できる。
ビルドした`Test.aab`から`Test.apks`を生成するコマンドは以下のようになる。

```bash
bundletool build-apks --bundle="./Test.aab" --output="./Test.apks" \
--ks="<keystoreFilePath>" \
--ks-pass="pass:<storePassword>" \
--ks-key-alias="<keyAlias>" \
--key-pass="pass:<keyPassword>"
```

![ 2023-01-19 15.38.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/e78432eb-b361-a57e-00b3-66ea21b19928.png)


### apkセットを展開する

生成されたapkセット (`.apks`) ファイルはただのzipなので拡張子を変えれば展開できる。
splits配下に格納されているのが**ベースモジュール**、asset-slices配下に格納されているのが**アセットパック**。
ここから端末に合わせて動的に統合されたapkがインストールされる。

![ 2023-01-19 15.41.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/e2431fe9-48e8-568b-e2f9-bb6fd210a6cc.png)

### apkも展開してみる

[apktool](https://ibotpeaches.github.io/Apktool/)を使うことでapkファイルの展開ができる。
Homebrewでもインストール可能 ([Homebrew Formulae - apktool](https://formulae.brew.sh/formula/apktool)) なのでMacOSやLinuxの場合はそちらを使うほうが楽かもしれない。
apkを展開するコマンドは以下のようになる。

```bash
apktool d <apkFilePath>
```

#### `base-master.apk`の展開結果

- Unityの基本的なアセットやglobal-metadata.dataがassets配下に格納されている
- original配下にはapktoolによってデコードされる前の生のAndroidManifest.xmlが格納されている
- res配下には画面密度別のアプリアイコンやstrings.xmlが格納されている
- smali配下にはapktoolによってJava実行ファイルのclasses.dexをデコードしたsmaliファイルが格納されている

※ `.smali`はAndroid Dalvik仮想マシンで使用されるアセンブリ言語であるSmaliで記述されたファイル

![ 2023-01-19 17.52.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/9be6a99d-ee10-4484-0652-2bef626f2724.png)

#### `base-arm64_v8a.apk`と`base-armeabi_v7a.apk`の展開結果

- lib配下にはそれぞれarm64-v8a向けとarmeabi-v7a向けのネイティブバイナリが格納されている

![ 2023-01-19 17.49.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/d1479425-6c55-2c7b-69c9-1650e36d6bc5.png)　![ 2023-01-19 17.50.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/d1030789-48f6-3a1e-1aea-2b873784788d.png)

#### `UnityDataAssetPack-master.apk`の展開結果

- assets配下にAddressablesにてローカル設定にしたアセットバンドルとカタログや設定ファイルが格納されている

![ 2023-01-19 17.57.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/e1f46265-ba55-8eb9-d11e-682b6c51c96c.png)

## aabをGoogle Play Consoleにアップロードしてみる

Google Play Consoleにaabをアップロードすると [App Bundle エクスプローラ](https://play.google.com/intl/ja/console/about/app-bundle-explorer/)でaabの詳細を確認することができる。

### デバイス別のapkセットのDL

デバイス一覧からデバイスごとのapkセットをzip形式でDLすることができる。
試しに`10.or E`と`3BBTV TBBTV01`のapkセットをDLしてみると、`base.apk`と`UnityDataAssetPack.apk`は共通でCPUアーキテクチャ別のapkだけが異なるのが確認できた。

![ 2023-01-19 18.01.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/d0c48104-40f6-97d3-12ae-3ef8c2090104.png)

#### `10.or E`のapkセット

![ 2023-01-19 16.16.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/1f2cac06-4a08-ec0b-5c3a-1db0e57f5727.png)

#### `3BBTV TBBTV01`のapkセット

![ 2023-01-19 16.16.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/047bc963-9f1c-3e4d-032e-49d418a1e6f6.png)

### 署名済みのユニバーサルAPKのDL

AABが登場する以前の**1つのapkに全端末用のデータが入ったapkファイル**形式でDLできる。
aabファイルをbundletoolとApktoolで展開した際に個々のapkに含まれていたファイルが全て1つのapkに格納されているのが確認できた。
Unityは1GB未満ならば`install-time`になるのが仕様で、それもここで確認できた。

![ 2023-01-19 18.15.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/e50bfbb2-2b78-c9e9-101e-e3f4f6305a3b.png)

### モジュールとアセットパックの確認

配信タブでは機能モジュールとアセットパックが確認できる。

機能モジュールには`base`という名前のモジュールが1つあり、マスター1つとABI2種類の3つから構成されることが確認できた。
アセットパックには`UnityDataAssetPack`という名前のアセットパックが1つあり、Unityの仕様通りに`install-time`配信モードであることが確認できた。

![ 2023-01-19 18.17.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/a8c16c68-2c2d-359c-2f8a-a78961264cdf.png)


![ 2023-01-19 18.17.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/ef9b2545-a1a3-ae1a-8cb4-85d5fbd45c79.png)
![ 2023-01-19 18.17.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/daf1c2e0-b09c-aebd-1fac-f0cc2866e2e9.png)

### アセットパックを複数作った場合

[Addressables公式サンプル](https://github.com/Unity-Technologies/Addressables-Sample)のPlay Asset Deliveryのサンプルを利用して`install-time`, `fast-follow`, `on-demand`それぞれのアセットパックを生成してみた。

aabを展開するとasset-slices配下に3つのアセットパックが格納されているのが確認できた。
また[App Bundle エクスプローラ](https://play.google.com/intl/ja/console/about/app-bundle-explorer/)にてそれぞれのアセットパックに配信モードが適切に設定されているのが確認できた。

![ 2023-01-20 13.08.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/86171268-019f-af45-d221-9f61dbb12013.png)

![ 2023-01-20 13.07.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/216890/55fffb90-93d0-58f4-3921-55236ee752a5.png)