Tech Note: Expansion Files With Unity
Oculus Developer Blog
|
Posted by Gabor Szauer
|
May 18, 2018
|
Share

This tech note will walk you through building an asset bundle, and using the bundle as an expansion file. Oculus supports uploading 1 APK file up to 1GB in size, and 1 expansion file, up to 4GB in size. When using the Split Application Binary feature in Unity, the engine outputs two expansion files, up to 2GB each. Android apps built with Unity 2017 are 32-bit, and thus cannot files larger than 2GB. However, building your own asset bundles as described here has a lot of advantages over the built-in method, including significant improvement in load times.

The only thing special about expansion files is their name and location on disk. Any file that follows the appropriate naming convention and is placed in the correct location can be treated like an expansion file.

Place large assets in bundle

Assign all assets that should be included in the expansion file to an asset bundle. Scenes that are included in the asset bundle should NOT be included in build settings. All large assets (such as videos) should only be referenced from these scenes.

Build the asset bundle

Asset bundles are going to have to be built from code. The simplest build script will look something like this:

  using UnityEngine;
  using UnityEditor;
    
  public class BuildAssetBundles : MonoBehaviour {
      [MenuItem("Build/Asset Bundles")]
      public static void BuildBundles() {
          string path = Application.dataPath + "/../AssetBundles/";
         if (!System.IO.Directory.Exists(path)) {
              System.IO.Directory.CreateDirectory(path);
          }
   
	  BuildPipeline.BuildAssetBundles("AssetBundles", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);

      }
    }
  

Additional steps like automatically renaming the asset bundle can be added to the above code snippet. The above command will output several files, the only one you need is the actual asset bundle. The .manifest file is not needed.

Rename the bundle

The generated asset bundle has no extension, it's just a file name. Rename the asset bundle to match the expected formatting of the expansion file name. The expected naming format is:

main.<version>.<package>.obb

Where <version> and <package> are defined in the player settings. The bundle version will need to be increased each time a new build is uploaded.

Read Permission

The expansion file is probably going to be located on external storage. To allow Unity to read the file, add the READ_EXTERNAL_STORAGE permission to the Android manifest file.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Consider using a plugin to test if the app has permission to read from external storage. If it does not, the app can't load the levels found in the asset bundle.

Load the Asset Bundle

Before loading any scene that is located in the asset bundle, make sure that the actual bundle is loaded into memory. This call needs to be made only once, it's a good candidate to place somewhere in startup code that only executes when the game launches.

To Load the asset bundle, call AssetBundle.LoadFromFile with the obb file path as its argument.

AssetBundle.LoadFromFile("/sdcard/Android/obb/com.oculus.demo/main.1.com.oculus.demo.obb");

//

Because the bundle version changes with each build that is uploaded, it's best not to hard code the bundle path. Unity does not expose the bundle version to the Android runtime. One common approach to this is to generate a C# file every time a build is made as a pre-build step and write PlayerSettings.Android-bundleVersionCode to it as a static variable.

A more robust solution is to write a simple plugin which returns the expansion file path. The following Java code will return the name of the expansion file:

  package com.oculus.versionhelper;
  
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageInfo;
  import android.os.Environment;
  import java.io.File;
  
  public class VersionHelper {
      private Context mContext;
  
      public  VersionHelper(Context context) {
          mContext = context;
      }
  
      public String GetExpansionFilePath() {
          PackageManager pm = mContext.getPackageManager();
          String name = mContext.getPackageName();
          PackageInfo pi = null;
          try {
              pi = pm.getPackageInfo(name, 0);
          } catch (PackageManager.NameNotFoundException e) {
              e.printStackTrace();
              return  "";
          }
          int version = pi.versionCode;
  
          String storageDir = Environment.getExternalStorageDirectory().getAbsolutePath();
          String packageName = mContext.getPackageName();
          return  storageDir + File.separator + "Android" + File.separator + "obb" + File.separator + "main." + version + "." + packageName + ".obb";
      }
}

The C# script to call this helper function from within Unity:

  using UnityEngine;
  
  public static class VersionHelper {
      private static string ACTIVITY_NAME = "com.unity3d.player.UnityPlayer";
      private static string CONTEXT = "currentActivity";
      private static string VERSION_HELPER_CLASS = "com.oculus.versionhelper.VersionHelper";

      private static AndroidJavaObject versionHelper = null;

      public static string ExpansionFileLocation {
          get {
  #if UNITY_ANDROID
              if (versionHelper == null) {
                  AndroidJavaObject context = new AndroidJavaClass(ACTIVITY_NAME).GetStatic<AndroidJavaObject>(CONTEXT);
                  versionHelper = new AndroidJavaObject(VERSION_HELPER_CLASS , context);
              }
              return versionHelper.Call<string>("GetExpansionFilePath");
  #else
              return "";
  #endif
          }
      }
    }

With the plugin code above, loading the asset bundle becomes:

  AssetBundle.LoadFromFile(VersionHelper.ExpansionFileLocation);

Testing & Deploying

When testing locally, the obb file needs to be manually pushed to /sdcard/Android/obb/ after the main apk has been installed.

  # Remove previously installed version
  adb uninstall com.oculus.demo
  adb shell rm /sdcard/Android/obb/main.1.com.oculus.demo.obb
  
  # Install the APK file
  adb push -p bundles.apk /data/local/tmp
  adb shell pm install -g /data/local/tmp/bundles.apk
  adb shell rm /data/local/tmp/bundles.apk
  
  # Push the expansion file
  adb push -p main.1.com.oculus.demo.obb /sdcard/Android/obb/
  

Uploading to one of the store channels needs to be done with the cli tool. Details on how to upload with the cli tool can be found here. Once uploaded to one of the release channels, the expansion file will be automatically downloaded to the correct location whenever the app is installed. Remember, each time you upload a new apk, the expansion file version has to increase.