Meta Quest中Unity主应用启动独立APK应用的问题排查
背景
伦敦帝国理工学院团队正在搭建VR教育应用分发系统:
- 桌面端方案:学习应用打包为AssetBundle+DLL上传服务器,主启动应用可下载后直接导入运行,无需单独安装每个应用。
- 目标Quest端方案:因Android无法运行时加载DLL,计划将每个学习应用打包为独立APK,由主应用下载后启动,完全脱离Quest商店,仅需侧载主应用并开启未知来源/开发者模式,主应用编译时无需知晓待启动应用信息,仅通过包名调用。
APK的构建、上传、下载流程均正常,但主应用内启动APK的操作失败。
主应用启动代码
public void LaunchApp() { Debug.Log("Try launch app 1"); string packageName = "com.ImperialCollegeLondon.VE2Hub24"; //该包名可通过SideQuest或设备菜单正常启动 var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); var packageManager = currentActivity.Call<AndroidJavaObject>("getPackageManager"); var launchIntent = packageManager.Call<AndroidJavaObject>("getLaunchIntentForPackage", packageName); currentActivity.Call("startActivity", launchIntent); }
运行时错误日志
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Intent.migrateExtraStreamToClipData(android.content.Context)' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.content.Intent.migrateExtraStreamToClipData(android.content.Context)' on a null object reference
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1756)
at android.app.Activity.startActivityForResult(Activity.java:5411)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:728)
at android.app.Activity.startActivityForResult(Activity.java:5369)
at androidx.activity.ComponentActivity.startActivityForResult(ComponentActivity.java:709)
at android.app.Activity.startActivity(Activity.java:5755)
at android.app.Activity.startActivity(Activity.java:5708)
at UnityEngine.AndroidJNISafe.CheckException ()
待启动应用Manifest关键配置
主Activity为com.unity3d.player.UnityPlayerGameActivity,包含以下属性:
- 动作:
android.intent.action.MAIN - 类别:
android.intent.category.LAUNCHER、com.oculus.intent.category.VR exported=true
尝试修改后的错误
将代码中的var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");改为var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayerGameActivity");后,出现新错误:
java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "currentActivity" in class "Lcom/unity3d/player/UnityPlayerGameActivity;" or its superclasses
java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "currentActivity" in class "Lcom/unity3d/player/UnityPlayerGameActivity;" or its superclasses
at UnityEngine.AndroidJNISafe.CheckException () [0x00000] in <00000000000000000000000000000000>:0
at UnityEngine.AndroidJNISafe.GetStaticFieldID (System.IntPtr clazz, System.String name, System.String sig) [0x00000] in <00000000000000000000000000000000>:0
at UnityEngine._AndroidJNIHelper.GetFieldID (System.IntPtr jclass, System.String fieldName, System.String signature, System.Boolean isStatic) [0x00000] in <00000000000000000000000000000000>:0
at UnityEngine.AndroidJavaObject._GetStatic[FieldType] (System.String fieldName) [0x00000] in <00000000000000000000000000000000>:0
at WorldLauncher.LaunchApp2 () [0x00000] in < etc etc
问题分析与解决方案
核心问题
第一个错误的根源是getLaunchIntentForPackage返回了null,即使包名正确、应用已安装,也可能因为以下原因:
- Quest的VR应用需要明确指定VR启动类别,默认的launch intent可能无法正确识别。
- Unity打包的Quest应用,其启动Intent需要包含
com.oculus.intent.category.VR才能被正确唤起,而getLaunchIntentForPackage可能没有自动包含这个类别。
修复后的代码
直接构建包含VR类别的Intent,而非依赖系统返回的默认launch intent:
public void LaunchVRApp() { string packageName = "com.ImperialCollegeLondon.VE2Hub24"; var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); // 构建明确的VR启动Intent var intentClass = new AndroidJavaClass("android.content.Intent"); var intent = new AndroidJavaObject("android.content.Intent", intentClass.GetStatic<string>("ACTION_MAIN")); intent.Call<AndroidJavaObject>("addCategory", "android.intent.category.LAUNCHER"); intent.Call<AndroidJavaObject>("addCategory", "com.oculus.intent.category.VR"); intent.Call<AndroidJavaObject>("setPackage", packageName); // 确保Intent可解析 var packageManager = currentActivity.Call<AndroidJavaObject>("getPackageManager"); var resolveInfo = packageManager.Call<AndroidJavaObject>("resolveActivity", intent, 0); if(resolveInfo != null) { currentActivity.Call("startActivity", intent); Debug.Log("Successfully launched VR app"); } else { Debug.LogError("Cannot resolve intent for package: " + packageName); } }
额外注意事项
- 权限配置:主应用的AndroidManifest.xml需要添加
QUERY_ALL_PACKAGES权限(针对Android 11+),或者在<queries>标签中声明允许查询的包名(如果已知包名列表):<!-- 针对Android 11+,若允许查询所有包 --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <!-- 或者精确声明允许查询的包名 --> <queries> <package android:name="com.ImperialCollegeLondon.VE2Hub24" /> <!-- 添加其他学习应用包名 --> </queries> - Unity版本适配:确保主应用使用的Unity版本与待启动应用的Unity版本兼容,避免因Java层API差异导致的问题。
- Quest系统版本:部分新版本Quest系统对未知来源应用的启动有更严格的限制,需确保设备已开启开发者模式和允许未知来源应用。
关于Contractors等VR游戏的Mod实现
这类游戏的Mod并非通过加载DLL实现,而是采用以下方案:
- 将Mod内容打包为AssetBundle,主游戏在运行时下载并加载这些资源包。
- Mod的逻辑代码通过Lua、C#脚本的动态编译(利用Unity的
CSharpCodeProvider)或预定义的扩展接口实现,无需直接加载外部DLL。
需求可行性确认
该需求完全可实现:
- 侧载应用之间的启动是Android系统允许的行为,只要权限配置正确、Intent构建合理即可。
- 已有大量第三方Quest应用分发平台(如SideQuest)采用类似的应用间启动逻辑。
内容的提问来源于stack exchange,提问作者Fred T.A

