When submitting to the Meta Horizon App Store, large apps should be split into an APK and one or more expansion files to improve both the developer and the user experience. The APK may be up to 1 GB in size. APKs can be uploaded through the Meta Quest Developer Hub, the Oculus Platform Command Line Utility or the integrations for the Platform tool in Unity or Unreal Engine.
There are two types of expansion files:
For more detailed information about APKs and expansion files, you can refer to the documentation from Meta.
When using the Split Application Binary feature in Unity, the engine automatically adds everything under StreamingAssets into the .obb expansion file. This could cause problems if your app is using the MxInkActions.asset
, which defines the stylus input actions. During the Unity build process the file RuntimeActionBindings.json
is created in the StreamingAssets
folder, containing all defined input actions. When the apk is split, the contents of the StreamingAssets
folder are placed inside the .obb expansion file and not the APK itself. When the APK is loaded, the input actions cannot be resolved at runtime, making it impossible to access MX Ink.
Instead of letting Unity perform the APK splitting (i.e. via the “Split Application Binary” checkbox under Project Settings), it is recommended to generate the expansion file manually to have control over what is included in the .obb. This Tech Note from Meta provides detailed information. In the remining part of this section we will provide a summary of the steps required to generate an expansion file.
We provide a sample application that is built in two parts: the main APK file which includes basic drawing capabilities using MX Ink, and an .obb file containing a 3D model that is not included in the main APK file and which can be loaded at runtime. This example illustrates how large files such as texture images, 3D models and other assets can be set to be included in an expansion file in order to keep the size of the main APK light enough for fast download and meet the requirements of the Meta Horizon App Store.
The key points to be considered are:
Models
folder to be included in the extraassets
bundle:This script should be placed in the Editor
folder within the Unity assets of the application. This script will create a new menu item in the Unity UI: Build/Asset Bundles
. When selected, the script will generate an asset bundle file corresponding to the asset label(s) defined above (extraassets
).
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);
}
}
The generated asset bundle has no extension, it should be renamed to match the expected formatting of the expansion file name:
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. It is best not to hard code the bundle path. The best approach is to write an Android plugin which returns the full path to the expansion file. The sample app contains a plugin (Assets/Logitech/AndroidPlugin/VersionHelperModule.aar
) which implements this functionality, feel free to reuse it in your own app.
For reference, this is the source code used to generate the VersionHelper
plugin using Android Studio:
package com.logitech.versionhelpermodule;
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";
}
}
See this Tech Note for additional information about how to create an Android plugin.
Back in Unity, the following script shows how the VersionHelper
plugin can be used to get the path to the expansion file. This .obb file is a Unity Asset Bundle containing a 3D model:
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.logitech.versionhelpermodule.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
}
}
}
public class LoadAssetBundles : MonoBehaviour
{
public AssetBundle assetBundle;
private string _assetBundleName;
void Start()
{
_assetBundleName = VersionHelper.ExpansionFileLocation;
Debug.Log("mx_ink_sample: Loading asset bundle: " + _assetBundleName);
StartCoroutine(LoadAssets());
}
IEnumerator LoadAssets()
{
var op = AssetBundle.LoadFromFileAsync(_assetBundleName);
yield return op;
assetBundle = op.assetBundle;
if (assetBundle == null)
{
Debug.LogError("mx_ink_sample: Failed to load AssetBundle: " + _assetBundleName);
}
}
}
In order to allow the app to read the external storage, where the expansion file will be located, add the following permission to the Android manifest:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
As an example, in the sample app, when pressing the stylus front button, a prefab including a 3D model will be extracted from the asset bundle and instantiated in the scene:
using UnityEngine;
public class LoadAssets : MonoBehaviour
{
public StylusHandler stylus;
private bool _frontButtonPressed = false;
private bool _modelInScene = false;
void Update()
{
if (stylus)
{
if (!_frontButtonPressed && stylus.CurrentState.cluster_front_value && !_modelInScene)
{
Debug.Log("mx_ink_sample: Instantiating 3d model");
AssetBundle assetBundle = FindFirstObjectByType<LoadAssetBundles>().assetBundle;
if (assetBundle != null)
{
Debug.Log("mx_ink_sample: loading model prefab");
var _modelPrefab = assetBundle.LoadAsset("Assets/Models/3dmodel.prefab");
Instantiate(_modelPrefab, new Vector3(0, 0.7f, 0), Quaternion.identity);
_modelInScene = true;
}
else
{
Debug.Log("mx_ink_sample: asset bundle not loaded");
}
_frontButtonPressed = stylus.CurrentState.cluster_front_value;
}
}
}
}
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.logitech.mx_ink_sample
adb shell rm /sdcard/Android/obb/main.1.com.logitech.mx_ink_sample.obb
# Install the APK file
adb install -g sample_app.apk
# Push the expansion file
adb push -p main.1.com.logitech.mx_ink_sample.obb /sdcard/Android/obb/
APKs can be uploaded to the App Store through the Meta Quest Developer Hub, the Oculus Platform Command Line Utility or the Platform tool in Unity. The expansion file will be automatically downloaded to the correct location whenever the app is installed. Each time a new apk is uploaded, the expansion file version must be increased.