こんにちは。このブログでははじめまして。クライアントエンジニアの下村です。
この記事は Akatsuki Advent Calendar 2018の19日目の記事です。
前回は kidach1 さんの ニューラルネットワークでStyle Transferを行う論文の紹介でした。
背景
この記事を開いた方にはご存知の事かと思いますが、Google Play Storeではアプリケーションパッケージのサイズに制限があり、100MBを超えるapkファイルをアップロードすることができません。 100MBを超える大きなアプリを提供したい場合は、APK拡張ファイルと呼ばれる別のパッケージにデータを分割する必要があります。
インターネット接続を前提とするソーシャルゲームでこの制限に引っかかることはそう多くありませんが、それでも急な対応でアプリサイズが大きくなってしまうことがたまにあります。
ちょうど最近、そういった不幸な出来事からこれを利用する機会があったので、備忘録を兼ねて知見を共有したいと思います。
UnityでAPK拡張ファイルを出力する
UnityでAPK拡張ファイルを出力する方法は非常に簡単です。
PlayerSettingsの Split Application Binary
オプションにチェックを入れてビルドすると、apkファイルと一緒にobbファイルが生成されます。
Unityは分割されたファイルから自動的にアセットを読み分けるため、Unityの実装上で分割を意識する必要はほとんどありません。
分割したアプリを手動でインストールする
Build And Run
を実行するとUnityは自動的にobbファイルをインストールしてくれます。
一方手動でアプリをインストールする場合は、 apkファイルに加えてobbファイルを特定の場所に配置する必要があります。
obbファイルの名前や置き場所は決まっていて、以下のようなパスに配置する必要があります。
外部ストレージのAndroidディレクトリは端末によってパスが異なる事がある点に注意してください。
/storage/emulated/0/Android/obb/<パッケージ名>/main.<ビルド番号>.<パッケージ名>.obb
ファイルの転送には adb push
コマンドを使うのが便利です。
$ adb install App.apk
$ adb push App.main.obb /storage/emulated/0/Android/obb/jp.aktsk.testapp/main.1.jp.aktsk.testapp.obb
これで分割されたアプリをインストールすることができました。
APK拡張ファイルのバリデーション
obbファイルは通常apkファイルと一緒にインストールされますが、実態は外部ストレージに配置されるいちファイルに過ぎません。 アプリ起動時にファイルが存在しないという状況が起こりえることから、Googleはアプリ起動時にAPK拡張ファイルのチェックや再ダウンロードを行うよう喚起しています。
Googleからはobbファイルをバックグラウンドでダウンロードするライブラリが公開されていますが、今回は急ぎで対応が必要だったことからこの高機能なダウンローダーを実装して詳しく検証している時間がありませんでした。 幸い分割されたデータはそう大きくなく、最近ではダウンロード漏れもかなり稀な発生率であるらしいことから、起動時に簡単なファイルチェックのみを行うことにしました。
APK拡張ファイルをチェックするアクティビティを挟む
今回のアプリではUnityの起動直後にいくつものアセットを読み込んでいたため、UnityPlayerActivityが起動する前にobbファイルの存在チェックをする必要がありました。 そこでonStartでobbファイルの存在をチェックをするActivityを新しく作成します。
publicclass ExpansionFileCheckActivity extends Activity { @Overrideprotectedvoid onStart() { super.onStart(); if (getMainObbFile().canRead()) { // UnityPlayerActivityを起動するreturn; } // エラーを表示してアプリを終了する } private File getMainObbFile() { String fileName = "main." + getVersionCode() + "." + getPackageName() + ".obb"; returnnew File(getObbDir(), fileName); } privatelong getVersionCode() { try { PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0); return PackageInfoCompat.getLongVersionCode(packageInfo); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return0; } } // (省略) }
getObbDir()
でobbファイルの配置されるディレクトリを取得できますが、ファイル名は自分で生成する必要があります。
Activityを追加したら、AndroidManifestを修正して最初に起動するActivityを置き換えます。
<activity android:name="jp.aktsk.testapp.ExpansionFileCheckActivity"android:label="@string/app_name"android:launchMode="singleTask"android:theme="@style/Theme.AppCompat"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name="com.unity3d.player.UnityPlayerActivity"android:label="@string/app_name"android:launchMode="singleTask"><meta-data android:name="unityplayer.UnityActivity"android:value="true" /></activity>
これで起動時に問題があれば適切なエラーを表示することができるようになりました。
必要なときだけアクセス権限を要求する
さらに一部の端末ではインストールされたobbファイルがユーザ所有にならず、アプリからアクセスできなくなることがあるようです。
Unityはこの問題を回避するため、 Split Application Binary
オプションを有効にしてビルドすると READ_EXTERNAL_STORAGE
権限要求をAndroidManifestに自動で付与します。
しかしながら、ごく一部の端末のためにすべてのユーザーにこの強力な権限を要求するのはナンセンスです。ここはobbファイルが読み込めなかった場合にのみ権限を要求するようActivityを拡張しましょう。
publicclass ExpansionFileCheckActivity extends Activity { privatestaticfinalint RC_PERMISSION_REQUEST = 1; @Overrideprotectedvoid onStart() { super.onStart(); checkApkExtension(); } @Overridepublicvoid onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNullint[] grantResults) { if (requestCode != RC_PERMISSION_REQUEST) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); return; } if (grantResults.length == 0&& !ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) { // アプリの設定画面を開くreturn; } checkApkExtension(); } privatevoid checkApkExtension() { if (getMainObbFile().canRead()) { // UnityPlayerActivityを起動するreturn; } if (PermissionChecker.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE }, RC_PERMISSION_REQUEST); return; } // エラーを表示してアプリを終了する } // (省略) }
ファイルチェックに失敗した場合は READ_EXTERNAL_STORAGE
権限をチェックし、なければリクエストしてから再度チェックを行います。
このときユーザーが「今後表示しない」をチェックしていると shouldShowRequestPermissionRationale
が false
を返すので、一言添えてアプリ設定画面に誘導するとよいでしょう。
最後にUnity側の権限リクエストを無効化する設定をAndroidManifestに追加すれば完了です。
<meta-data android:name="unityplayer.SkipPermissionsDialog"android:value="true" />
これでなんとかAPK拡張ファイルを利用したアプリをリリースする最低限の準備ができました。 あとはお好みでユーザーへの説明や操作指示などを肉付けしましょう。
その他の注意点
バイナリの配布方法
利用しているアプリケーション配信サービスによってはobbファイルを取り扱えないことがあります。ストアの内部テストだけでは大抵不足なので、ビルドの用途に応じてAPK拡張ファイルの利用するか切り替えられるような仕組みを準備しておくべきでしょう。
StreamingAssetsへのアクセス
UnityでAPK拡張ファイルを利用するとStreamingAssetsはobbファイルの側に格納されます。AndroidにおいてStreamingAssets以下のファイルへのアクセスはどちらにあってもそう変わりません。
しかし、一部のサードパーティライブラリではobbファイル内のStreamingAssetsに正常にアクセスできないものがありました。
StreamingAssets以下にあるアセットが正しく読み込まれているか検証を忘れないようにしましょう。
ビルド番号
Google Play StoreではAPK拡張ファイルのビルド番号はアプリ本体のものと別に扱われるようですが、Unityは必ずアプリのビルド番号を用いてobbファイルを開こうとするようです。 Google Play Storeで新しいリリースを生成するとき、新しいobbファイルをアップロードせずに過去のファイルを選択すると、Unityからアクセスできなくなってしまいます。
まとめ
UnityでAPK拡張ファイルを扱うのは簡単ですが、その仕組みには落とし穴がいくつもありました。 アプリサイズが逼迫したときはまずより簡単な手順(例えば、アーキテクチャ毎にapkをビルドするなど)を講じて、なお足りない場合に採用を検討すべきでしょう。
その上でどうしても必要になったときは、Unityによるサポートがあるからと軽視せず早めに実装と検証を行うことが大事ですね。