UnityでOBBを使うときの話

2019年6月29日Android, Unityobb

はじめに

AndroidのアプリをPlayStoreに上げるときにはAPK最大100MB制限があります。

ソシャゲなどは後からAssetBundleなどをDLする方式が多い印象ですが、頻繁に新規コンテンツの追加がない(アップデートする可能性が少ない)場合、OBBというAPKとは別枠で2GBx2ファイル配置できる機能があるので使ってみようというお話です。

OBBを使う方法は非常に簡単なのですが、変な所で思いっきり躓いて全治2日くらいかかったりしたので纏めておきます。

ソフトウェア バージョン
Unity 2018.3.11
Android SDK Platform Tools 28.0.1
Open JDK 1.8.0 152

Player Settings系の設定

OtherSettings系の設定は以下の通りです。

項目名 設定値
Minimum API Level Android 6.0
Target Architecture ARMv7
Scripting Runtime Version .NET 4.x
Api Compatibility Level .Net 4.x

上記設定はどれも必須なものでは無く、今回はこの様な設定でやったよー程度なのでこの数値が違っていてもOBBは問題無く出せると思います。

OBBの設定自体はPublishing SettingsのSplit Application Binaryを有効にするだけです。

難しいスクリプトや設定なんて何もいりません、チェックボックスを有効にするただそれだけです。後はUnityがよしなに処理してくれます。

スクリーンショット 2019-04-21 18.25.40

データ分割のされかた

どのようなデータがOBBに格納されるかというとUnityドキュメントでは下記の様に書かれています。

APK – 実行ファイル (Java とネイティブ)、プラグイン、スクリプト、最初のシーン (インデックス 0) のデータから構成されます。

OBB – 上記以外のすべて。残りのシーンすべて、リソース、ストリーミングアセットを含みます。

最初のシーンで参照しているAssetはAPKに入るという部分が注意が必要で、「最初のシーンで100MBのチュートリアル動画がある」と言う場合はOBBにしても100MB以上のAPKが誕生してしまいます。

シーン構造として、最初のシーンはCheck系のプログラムのみが動く黒画面(とかローディング画面)のシーンにし、画像や動画などは遷移先のシーンで描画するように構成する必要があります。

またプログラム部分はAPK以外には入れられないのでプログラム部分のみで100MB越えてるという場合はアーキテクチャ毎にAPKの出し分けなど対処が必要になります。

ResourcesStreaming AssetsのファイルはOBB側に含まれますので動的読込する際は注意が必要です。後述しますが希にAPKのみ端末に存在しOBBが非存在という状況が発生します、このときTOPシーンでResourcesから読込があるとそこで死んでしまいます。

Package Nameに注意!

OBBが正しく動くか、どのような挙動を示すのか実験用のプロジェクトを作られると思います。その時PackageNameを「jp.test.obb」とかにすると死にます。これで14時間くらい無駄にしました。はい。

末尾がobbで終わるPackageNameを使用すると下記の様なエラーがでます。

 

04-15 16:03:41.925 26610 26628 E mono    : The assembly mscorlib.dll was not found or could not be loaded.
 
 04-15 16:03:41.925 26610 26628 E mono    : It should have been installed in the `/storage/emulated/0/Android/obb/jp.test.obb/main.1.jp.test.obb.obb/assets/bin/Data/Managed/mono/4.5/mscorlib.dll' directory.

このエラーログを読んで、どうしてこの子はOBBにスクリプトがあると思っているのだろうかと考え続けましたが、.obb.obbとこうなっちゃうのがダメな感じっぽいです。冷静に考えたら、そりゃあまりよろしくない。

本番環境でPackageNameにobbなんて入れる事はないと思いますが、実験の時は気をつけましょう。

 Buildチェック

簡単なProjectを作ってOBBで分割されているか確認します。

「Title」Sceneと「Movie」Sceneを作成します。

TitleにはMovieに遷移するボタンをコードを、MovieにはUnity標準のVideo Playerで動画を再生させます。

そしてSplit Application Binaryを有効にしてBuildを行うと、Build結果として普通のAPKとOBBファイルが出力されます。

OBB側に200MBある動画ファイルが配置されているので意図したとおりの分割になりました。

UnityでBuild And Runを行えばOBBはAndroid端末の然るべき場所に自動で配置されます。

OBBファイルの置き場所と名前

OBBファイルをAndroid上の下記ディレクトリ、名前で配置します。


/storage/emulated/0/Android/obb/(Package Name)/main.(Bundle Version Code).(Package Name).obb

ディレクトリはAndroidの外部ストレージになるので端末によって差異がありますので注意してください。

ファイル名の変数部分はProject Settingsで設定するパラメータに依存しているので、Project Settingsを確認して入力してください。

OBBファイルの存在確認

ここまででOBBファイルの分割とアプリ実行ができたと思います。基本的にはこれで問題ないのですが、希にAPKはインストールされているがOBBファイルが端末にDLされてないという状況が発生するようです。

OBBファイルが存在しない状況でアプリを起動した場合、初期Sceneから遷移できない、Resourcesからファイルが読めないなどの問題が発生するのでユーザー側にアプリを再度DLするように促す必要があります。

そのため、OBBファイルチェック機能が必要になるのですが、Androidネイティブでないと確認することができないのでJavaと戦う必要があります。

既にAndroid用のネイティブプラグインを作っているならそこに足せばいいですが、OBBチェックのためだけにネイティブプラグインを作るのも面倒くさいので今回はUnityのC#でJavaを叩ける機能を使ってOBBファイルチェック機能を作って行きます。

OBBファイルチェックコード

OBBがあるか確認するにはFile.Existsで調べれば良いので、用意するものはOBBファイルのフルパスになります。

まず、OBBのフォルダパスを取得します。

 
        /// <summary>
        /// Activityを取得
        /// </summary>
        /// <returns>Activity</returns>
        private static AndroidJavaObject GetActivity()
        {
            return GetJavaStaticField<AndroidJavaObject>("com.unity3d.player.UnityPlayer", "currentActivity");
        }

        /// <summary>
        /// Contextのインスタンス取得
        /// </summary>
        /// <returns>Contextのインスタンス</returns>
        private static AndroidJavaObject GetConetxt()
        {
            return GetActivity().Call<AndroidJavaObject>("getApplicationContext");
        }

        /// <summary>
        /// OBBファイルを配置するフォルダのフルパスを取得
        /// </summary>
        /// <returns>OBBフォルダのフルパス</returns>
        public static string GetObbFolderPath()
        {
            string result = null;
            using (var context = GetConetxt())
            {
                result = context.Call<AndroidJavaObject>("getObbDir").Call<string>("getCanonicalPath");
            }
            return result;
        }

以上のコードでGetObbFolderPathを叩くとフルパスでOBBフォルダが取得できます。

続いてOBBのファイル名を取得します。

        /// <summary>
        /// アプリのPackage Nameを取得
        /// </summary>
        /// <returns>Package Name</returns>
        public static string GetPackageName()
        {
            string result = null;
            using (var context = GetConetxt())
            {
                result = context.Call<string>("getPackageName");
            }

            return result;
        }

        /// <summary>
        /// アプリのBundle Version Codeを取得
        /// </summary>
        /// <returns>Bundle Version Code</returns>
        public static string GetApplicationBuildVersion()
        {
            string result = null;
            using (var context = GetConetxt())
            {
               AndroidJavaObject packageManager = context.Call<AndroidJavaObject>("getPackageManager");
               AndroidJavaObject packageInfo = packageManager.Call<AndroidJavaObject>("getPackageInfo", GetPackageName(), 0);
               result = packageInfo.Get<int>("versionCode").ToString();
            }

            return result;
        }

        /// <summary>
        /// OBBファイル名を取得
        /// </summary>
        /// <returns>OBBファイル名</returns>
        public static string GetObbFileName()
        {
            return  "main." + GetApplicationBuildVersion() + "." + GetPackageName() + ".obb";   
        }

Package NameとBundle Version Codeがわかればファイル名が出せるので、両方を取得してファイル名にしてます。

ここ見逃してるだけでgetObbDir的なメソッドで一発取得できそうな気がしてます……

以上のコードでOBBのフルパスが取得できたのであとはFile.Existsで存在チェックを行います。

存在チェック後、実際に読込可能かまで調べるとより良いと思います。

OBBファイルを開くためのパーミッション

AndroidでOBBファイルを読み込む場合に希な環境でREAD_EXTERNAL_STORAGEのパーミッションがないと読込ができない場合があるそうです。

そのため、UnityではSplit Application Binaryを有効にするとManifestにREAD_EXTERNAL_STORAGE権限を自動で追記します。

今回、ここ2年程度に販売したXperia、Galaxy、Pixel、HUAWEIなどで確認したところREAD_EXTERNAL_STORAGE権限が無くても問題無く読み込めたのでREAD_EXTERNAL_STORAGEはManifestから削除しました。

本来はOBBにファイルアクセスを行い読み込みができなければREAD_EXTERNAL_STORAGEを要求するという実装を組むのが最善だと思いますが、READ_EXTERNAL_STORAGE無しでエラーが起きる実機が手元にないので問題再現ができないので対応しない方向で見捨てます。

まとめ

以上でOBBファイルを無事に扱えるようになりました、これで100MBの壁を気にすること無くリソースを入れられます。

本記事で声を大にしていいたいのが「jp.test.obb」とかいう頭の悪いPackage Nameをつけるんじゃない、死ぬぞという事です。

参考文献