libGDXでAndroidのAPK拡張に対応する
はじめに
AndroidアプリはAPKのサイズが100MBを超過すると、そのままでは開発者コンソールにアップロードできなくなります。
そこで必要となるのが、アセットファイルの一部または全部をAPKから拡張ファイルとして切り出し、APKそのもののサイズを大きく減らすAPK拡張という方法です。
APK拡張については、Android開発者の公式サイトをご覧ください。
本記事は、以下のサイトの内容を把握していることが前提です。
(英語かつ滅茶苦茶長いですが頑張りましょう……)
さて、本題に入りましょう。
libGDX利用時にどうやってAPK拡張に対応するのかが問題です。
困ったことに、libGDXの公式マニュアルにはAPK拡張についての項目はありません。ネット上にも情報がほぼないのが現状です。
libGDXのGitHubリポジトリWikiに、1ページだけAPK拡張についての記述がありますが、このページ通りに試しても上手くいきません。いくつか落とし穴があるので対処する必要があります。
APK Expansions support · libgdx/libgdx Wiki · GitHub
この記事ではlibGDXでのAPK拡張の手順をお教えします。
拡張ファイルの読込部の作成
以下に、拡張ファイルの読込部のコードを示します。 (本記事のコードは全てKotlinで記述されています)
//androidApplicationはAndroidLauncherを指しているものとする val packageManager = androidApplication.packageManager val packageName = androidApplication.packageName val applicationInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) val mainVersion = applicationInfo.metaData.getInt("main") val patchVersion = applicationInfo.metaData.getInt("patch") val androidFileResolver = Gdx.files as AndroidFiles androidFileResolver.setAPKExpansion(mainVersion, patchVersion)
6行目までは、拡張ファイルのバージョン番号取得部です。
コード内に拡張ファイルのバージョン番号情報を記載しておくのはよろしくないため、AndroidManifestにメタデータとして記載しておき、それを吸い出しています。
上記のコードはGameクラスを継承したクラスのインスタンスをもとにしてAndroidLauncher側でinitialize()を実行した後に呼び出さなければなりません。
なぜなら、initialize()実行前はGdx.filesがNULLなためです。
そのため、上記のコードはlibGDXのCore側から呼び出せるようにする必要があります。
Core側でアプリ拡張用SAMインターフェイスを実装し、AndroidLauncher側でAPK拡張用の実装として当該SAMインターフェイスを実装したクラスを定義し、Game継承クラスのコンストラクタで渡してしまうのが良いです。
拡張ファイルの作成
次に、拡張ファイルを準備します。
libGDXで使用する拡張ファイルは無圧縮のzipであり、標準的なobbではありません!
また、拡張ファイル内の構造には注意する必要があります。具体的には、以下のように拡張ファイルを作成してください。
1. 任意の名前でディレクトリを作成する。(例)expansion
2. 作成したディレクトリ内に、切り分けたいアセットを格納する。
もとのassetsフォルダ配下のファイルやディレクトリをそのまま移動すればOKです。
3. 作成したディレクトリを無圧縮のzipにする。
作成したzipを開発者コンソールに提出すると勝手に以下のようにリネームされます。
[main|patch].[バージョン番号].[パッケージ名].obb
拡張子はobbになりますが、中身は無圧縮zipのままになっています。
拡張ファイル内のアセットの参照
拡張ファイルからアセットを参照する際は、assets内からアセットを参照する際と同様にGdx.files.internal()を用いてアセットを参照できます。
ただし、ここで指定するパスが曲者です!
拡張ファイル内のアセットを示すパスは、拡張ファイル内のトップレベルディレクトリ名から記述しなければいけません!
つまり、本記事の例でいえば、expansions/img/character.pngのようにexpansionsから指定する必要があります。
assets内のアセットを示すパスには、assets/を含む必要がないため、知らないとドハマりします!
また、開発時はいちいち拡張ファイルを作成していると面倒なため、拡張ファイルの有無で場合分けしてアセットを読めるようにしておく必要があります。
例えば、以下のように実装して拡張ファイルの有無によるパスの差異に対応しています。
fun loadTexture(key :String, path :String){ if(textureMap.get(key) != null){ return } val actualPath = if(GameMain.isUseExpantion){ "expansion/" } else{ "" } + path assetManager.load(actualPath, Texture::class.java) textureMap.put(key, actualPath) }
拡張ファイル利用時にステップ実行可能なデバッグを実施する
内部テスト、αテスト、βテスト用のアプリをストアからダウンロードしても、デバッグ時にアプリを一時的にアンインストールすると拡張ファイルも同時に消去されてしまいます。
そのため、ステップ実行可能な状態で拡張ファイルのデバッグを実施する場合は、予め実機のSDカード内に拡張ファイルを手動で保存しておく必要があります。
拡張ファイルは、前述した、開発者コンソールによる命名規則に従って手動でリネームしておいてください。
拡張ファイルのSDカードへの格納には、AIrDroidなど、PCから端末に直接アクセス可能なサービスを利用すると楽です。
そのようなサービスが利用できない場合は、ADBで頑張りましょう。