Unity与Android原生交互:告别手写JNI,使用AndroidJavaProxy实现双向通信

📅 发布时间:2026/7/30 6:48:23
Unity与Android原生交互:告别手写JNI,使用AndroidJavaProxy实现双向通信 1. 项目概述为什么我们需要告别JNI在Unity与安卓原生代码交互的领域JNIJava Native Interface长期以来是绕不开的技术。无论是调用一个系统API还是集成一个第三方SDK你大概率都写过类似AndroidJavaClass和AndroidJavaObject的代码或者在更底层的C/C#层与Java层之间手动进行繁琐的类型转换和内存管理。这个过程对于开发者来说体验并不友好代码冗长、容易出错、调试困难而且一旦通信需求变得复杂尤其是需要从Java回调到C#时整个架构会变得异常脆弱和难以维护。我经历过太多这样的项目一个简单的获取设备ID的调用因为JNI的某个签名写错导致在特定机型上崩溃一个复杂的支付回调因为线程问题消息丢失在茫茫的虚拟机栈中。直到我开始系统性地使用AndroidJavaProxy整个开发体验才发生了质的变化。这个项目标题“告别JNI”并不是说完全不用JNI底层通信依然基于它而是指告别那种手写、裸露、难以维护的JNI胶水代码转而采用一种更优雅、更安全、更符合C#开发者直觉的面向接口的通信方式。简单来说AndroidJavaProxy允许你在C#侧定义一个接口这个接口直接对应Java侧的一个接口或一个类的特定方法。然后你可以创建一个该接口的代理实例并将这个实例传递给Java端。当Java端调用这个接口的方法时调用会被自动、安全地转发到C#侧你实现的对应方法中。这就实现了从Java到C#的“反向”或“回调”通信。结合Unity原有的从C#调用Java的能力一套完整的、双向的、类型安全的通信桥梁就搭建起来了。它解决了什么问题代码清晰与安全用C#接口和强类型方法调用替代了字符串方法名、AndroidJavaObject数组传参等易错操作。简化回调机制实现Java到C#的回调变得和定义C#事件一样简单。提升开发效率减少胶水代码将精力集中在业务逻辑而非通信细节上。增强可维护性通信协议以接口形式定义双方职责清晰易于迭代和调试。这篇文章我将从一个完整的实战案例出发手把手带你实现一个功能Unity向安卓端发送一条消息安卓端处理后在特定时机例如一个按钮点击事件回调通知Unity并传回处理结果。我们将使用AndroidJavaProxy作为核心工具并提供可直接复制使用的C#和Java代码。无论你是Unity移动端开发的新手还是正在被JNI回调问题困扰的老手这篇内容都将为你提供一个可靠、高效的解决方案。2. 核心原理与架构设计拆解在深入代码之前我们必须理解AndroidJavaProxy是如何工作的以及我们整个双向通信架构的设计思路。这能帮助你在遇到问题时知道该从哪里排查。2.1 AndroidJavaProxy 的本质一个动态生成的JNI桥接器当你创建一个AndroidJavaProxy的实例时你实际上是在C#侧创建了一个对象这个对象知道如何与Java虚拟机JVM交互。它的核心工作原理可以概括为接口映射你提供一个C#接口例如IAndroidCallback。这个接口的方法签名方法名、参数类型、返回类型必须与你希望回调的Java接口方法严格匹配。代理生成Unity或者说底层的Mono/IL2CPP会为这个C#接口动态生成一个Java类的实现。这个生成的Java类内部包含了必要的JNI代码。对象传递当你将这个代理实例通过AndroidJavaObject.Call等方法传递给Java端时传递过去的实际上是一个指向这个动态生成的Java对象的引用一个AndroidJavaObject或AndroidJavaProxy对象本身在JNI层面是一个jobject。回调触发Java端拿到这个对象后可以将其强制转换为对应的Java接口类型然后调用其方法。这个调用会通过JNI层最终路由回C#侧你实现的接口方法中。关键在于所有的JNI查找方法ID、转换参数类型、调用方法、处理异常和返回值等复杂操作都被AndroidJavaProxy隐藏和自动化了。你只需要关心C#接口的定义和实现。2.2 双向通信的架构设计一个稳健的双向通信架构需要明确数据流向和职责边界。我们的设计如下[Unity C# 侧] --(1. C#调用Java)-- [Android Java 侧] ^ | | | (2. Java持有C#代理) | v [Unity C# 接口实现] --(3. Java回调C#)-- [Java 接口调用]流程解析C# 主动调用 Java (正向通信)这是Unity的传统强项。使用AndroidJavaClass和AndroidJavaObject我们可以轻松调用任何Java静态方法或实例方法。在这个项目中我们利用这个能力来“初始化”通信将我们的C#回调代理实例传递给Java端。Java 持有 C# 代理在Java端我们需要定义一个接口例如UnityCallback这个接口是C#接口在Java侧的镜像。C#侧通过AndroidJavaProxy创建一个实现了该Java接口的代理对象并将其作为参数传递给Java端的某个初始化方法。Java端接收这个对象并将其保存为一个成员变量例如private UnityCallback unityCallback;。至此Java端就持有了一个可以“呼叫”Unity的“对讲机”。Java 回调 C# (反向通信)当Java端某个事件发生时如网络请求完成、传感器数据更新、按钮被点击它就可以调用持有的unityCallback对象上的方法。这个调用会穿透JNI自动触发C#侧AndroidJavaProxy背后对应的接口方法实现。C#侧的方法执行后还可以通过返回值将数据传回Java端如果需要。设计要点与考量接口先行先定义好通信的“协议”即Java和C#两边完全匹配的接口。这是保证通信成功的基础。生命周期管理要特别注意对象的生命周期。Unity场景销毁时如果Java端还持有C#代理的引用可能导致内存泄漏或无效回调。通常需要在C#的OnDestroy方法中通知Java端清理引用。线程安全Java端的回调可能发生在非Unity主线程例如网络回调线程。而Unity的API如GameObject.Find,Debug.Log在Editor下大部分必须在主线程调用。因此在C#接口的实现中我们通常需要使用UnityEngine.Dispatcher如UnityMainThreadDispatcher插件或MainThreadUtil等机制将回调逻辑派发到主线程执行。错误处理在接口方法中添加try-catch并将异常信息通过Debug.LogError输出对于调试至关重要。3. 完整实现从接口定义到双向调用接下来我们实现一个具体场景Unity发送一个“请求”给安卓安卓端模拟一个耗时操作比如网络请求然后在操作完成后将结果“响应”回调给Unity。3.1 第一步定义通信协议接口这是最关键的一步两边的接口必须像镜子一样完全对应。C# 侧接口定义 (IAndroidCallback.cs)这个文件放在Unity项目的Assets/Scripts/AndroidBridge/目录下。// IAndroidCallback.cs // 这个接口定义了Java端可以回调C#的所有方法。 // 方法签名必须与Java端的接口完全一致。 public interface IAndroidCallback { // 定义一个简单的回调方法从Java接收一个字符串消息。 // 方法名onMessageReceived // 参数一个string类型的消息 // 返回类型void void OnMessageReceived(string messageFromAndroid); // 定义一个带复杂数据的回调方法。 // 例如回调一个操作结果包含成功状态和详细数据。 // 方法名onOperationComplete // 参数bool success, string data // 返回类型void void OnOperationComplete(bool isSuccess, string resultData); // 注意参数类型必须使用Java对应的基本类型或完全限定类名。 // string - java.lang.String // bool - boolean (在C#中就是bool) // int - int // float - float // 如果传递自定义对象需要使用完全限定名且两边需要有相同的类结构通常用JSON字符串替代更简单。 }Java 侧接口定义 (UnityCallback.java)这个文件放在Android Studio项目的app/src/main/java/com/yourcompany/yourgame/目录下。// UnityCallback.java package com.yourcompany.yourgame; // 这个接口是给C#的AndroidJavaProxy实现的。 // 它必须与C#侧的IAndroidCallback接口方法一一对应。 public interface UnityCallback { // 对应 C#: void OnMessageReceived(string message) void onMessageReceived(String message); // 对应 C#: void OnOperationComplete(bool isSuccess, string resultData) void onOperationComplete(boolean isSuccess, String resultData); }重要提示方法名的大小写在Java中习惯使用camelCase在C#中习惯使用PascalCase。但为了JNI能正确匹配两边必须使用完全相同的方法名。通常我们遵循Java的命名规范在C#接口中也使用camelCase或者确保两边一致。本例中为了清晰C#侧用了PascalCase但实际匹配时JNI是按名称字符串匹配的所以必须完全一致。这里我们统一使用onMessageReceived这种格式。下文的C#实现会修正这一点。3.2 第二步实现C#侧通信管理类这是Unity端的核心控制器负责初始化通信、发送消息、以及实现回调接口。C# 通信管理类 (AndroidBridgeManager.cs)// AndroidBridgeManager.cs using UnityEngine; using System; // 使用Action进行主线程派发 public class AndroidBridgeManager : MonoBehaviour, IAndroidCallback // 实现我们定义的接口 { // 单例模式方便全局访问 private static AndroidBridgeManager _instance; public static AndroidBridgeManager Instance { get { if (_instance null) { GameObject go new GameObject(AndroidBridgeManager); _instance go.AddComponentAndroidBridgeManager(); DontDestroyOnLoad(go); // 常驻避免场景切换后回调丢失 } return _instance; } } // 指向Java端工具类的引用 private AndroidJavaObject _androidJavaPlugin; // AndroidJavaProxy实例它是我们实现接口的代理 private AndroidJavaProxy _callbackProxy; void Awake() { if (_instance ! null _instance ! this) { Destroy(this.gameObject); return; } _instance this; InitializeAndroidBridge(); } /// summary /// 初始化与Android的通信桥梁 /// /summary private void InitializeAndroidBridge() { try { // 1. 创建AndroidJavaProxy传入我们实现的接口类型。 // Unity会基于这个接口自动生成一个实现了对应Java接口的代理对象。 _callbackProxy new AndroidJavaProxy(com.yourcompany.yourgame.UnityCallback) { // 这里使用C#的匿名方法或Lambda表达式来实现接口方法 // 注意方法名必须与Java接口中的方法名完全一致 onMessageReceived (message) { // 重要这个回调可能不在Unity主线程 // 我们需要将处理逻辑派发到主线程。 ExecuteOnMainThread(() { Debug.Log($[Unity] 收到来自Android的消息: {message}); // 在这里处理消息例如更新UI、触发游戏事件等。 OnAndroidMessageReceived?.Invoke(message); }); }, onOperationComplete (isSuccess, resultData) { ExecuteOnMainThread(() { string status isSuccess ? 成功 : 失败; Debug.Log($[Unity] 操作完成状态: {status}, 数据: {resultData}); OnOperationComplete?.Invoke(isSuccess, resultData); }); } }; // 2. 获取Java端的工具类实例 // 假设我们有一个Android的类com.yourcompany.yourgame.UnityBridge using (AndroidJavaClass pluginClass new AndroidJavaClass(com.yourcompany.yourgame.UnityBridge)) { // 调用其静态方法getInstance来获取单例或者直接构造。 // 这里假设UnityBridge有一个初始化方法需要传入我们的回调代理。 _androidJavaPlugin pluginClass.CallStaticAndroidJavaObject(getInstance); if (_androidJavaPlugin ! null) { // 将回调代理传递给Android端完成“对讲机”的交付。 _androidJavaPlugin.Call(setUnityCallback, _callbackProxy); Debug.Log([Unity] Android通信桥初始化成功回调已设置。); } else { Debug.LogError([Unity] 获取Android插件实例失败); } } } catch (System.Exception e) { Debug.LogError($[Unity] 初始化Android桥时发生异常: {e.Message}\n{e.StackTrace}); } } /// summary /// 从Unity发送消息到Android /// /summary /// param namemessage要发送的消息/param public void SendMessageToAndroid(string message) { if (_androidJavaPlugin ! null) { try { _androidJavaPlugin.Call(receiveMessageFromUnity, message); Debug.Log($[Unity] 已向Android发送消息: {message}); } catch (System.Exception e) { Debug.LogError($[Unity] 调用Android方法失败: {e.Message}); } } else { Debug.LogWarning([Unity] Android插件未初始化无法发送消息。); } } /// summary /// 请求Android端执行一个模拟的耗时操作 /// /summary public void RequestAndroidOperation(string operationData) { if (_androidJavaPlugin ! null) { try { _androidJavaPlugin.Call(performOperation, operationData); Debug.Log($[Unity] 已请求Android执行操作数据: {operationData}); } catch (System.Exception e) { Debug.LogError($[Unity] 请求Android操作失败: {e.Message}); } } } // 以下两个事件方便其他C#脚本订阅回调实现解耦。 public event Actionstring OnAndroidMessageReceived; public event Actionbool, string OnOperationComplete; // 一个简单的主线程派发器适用于简单项目复杂项目建议使用专用插件 private void ExecuteOnMainThread(Action action) { // 在Unity中可以从任何线程通过Queue或委托到主线程执行。 // 这里使用一个简单的静态方法模拟。实际项目中你可能需要更健壮的方案。 // 例如使用UnityMainThreadDispatcher插件。 if (action ! null) { // 这里假设我们在主线程调用Call但回调可能来自其他线程。 // 一个简单的检查如果当前是主线程直接执行否则缓存起来在Update中执行。 // 为了简化示例我们假设回调总是在主线程实际并非如此。 // 这是一个需要你根据项目实际情况完善的关键点 action.Invoke(); // Debug.LogWarning(回调执行请确保在主线程。实际开发请实现线程安全派发。); } } void OnDestroy() { // 清理时通知Android端移除回调引用避免内存泄漏。 if (_androidJavaPlugin ! null) { try { _androidJavaPlugin.Call(clearUnityCallback); } catch (System.Exception e) { Debug.LogWarning($[Unity] 清理Android回调时发生异常: {e.Message}); } _androidJavaPlugin.Dispose(); _androidJavaPlugin null; } _callbackProxy null; Debug.Log([Unity] AndroidBridgeManager 已销毁。); } }3.3 第三步实现Java侧通信管理类这是Android端的核心负责接收Unity的调用并在适当时候发起回调。Java 通信管理类 (UnityBridge.java)// UnityBridge.java package com.yourcompany.yourgame; import android.util.Log; import com.unity3d.player.UnityPlayer; // 用于直接调用Unity方法备用方案 public class UnityBridge { private static final String TAG UnityBridge; private static UnityBridge _instance; // 持有Unity回调接口的引用 private UnityCallback _unityCallback; // 私有构造 private UnityBridge() {} public static synchronized UnityBridge getInstance() { if (_instance null) { _instance new UnityBridge(); } return _instance; } /** * 由Unity调用设置回调代理。 * param callback 从Unity传递过来的AndroidJavaProxy对象在Java层表现为一个实现了UnityCallback接口的对象 */ public void setUnityCallback(UnityCallback callback) { Log.i(TAG, 设置Unity回调); this._unityCallback callback; // 可以立即测试一下回调是否畅通 if (this._unityCallback ! null) { new android.os.Handler(android.os.Looper.getMainLooper()).postDelayed(new Runnable() { Override public void run() { sendMessageToUnity([Android] 回调连接测试成功); } }, 500); } } /** * 由Unity调用清除回调引用。 */ public void clearUnityCallback() { Log.i(TAG, 清除Unity回调); this._unityCallback null; } /** * 供Unity调用的方法接收消息。 * param message 来自Unity的消息 */ public void receiveMessageFromUnity(String message) { Log.i(TAG, 收到Unity消息: message); // 在这里处理来自Unity的指令或数据 // 例如可以显示一个Toast或者开始一个服务等。 android.widget.Toast.makeText(com.unity3d.player.UnityPlayer.currentActivity, 收到Unity消息: message, android.widget.Toast.LENGTH_SHORT).show(); // 模拟处理后的回调 sendMessageToUnity([Android] 已处理你的消息: message); } /** * 供Unity调用的方法执行一个操作。 * param data 操作数据 */ public void performOperation(final String data) { Log.i(TAG, 开始执行操作数据: data); // 模拟一个耗时操作例如网络请求 new Thread(new Runnable() { Override public void run() { try { Thread.sleep(2000); // 模拟2秒耗时 boolean success true; // 假设操作成功 String result 操作 data 已完成耗时2秒。; // 操作完成回调Unity onOperationComplete(success, result); } catch (InterruptedException e) { Log.e(TAG, 操作被中断, e); onOperationComplete(false, 操作被中断); } } }).start(); } /** * 内部方法发送普通消息到Unity通过回调接口 * param message 要发送的消息 */ private void sendMessageToUnity(String message) { if (_unityCallback ! null) { try { // 这里回调会通过JNI触发C#侧AndroidJavaProxy的实现 _unityCallback.onMessageReceived(message); } catch (Exception e) { Log.e(TAG, 回调Unity失败 (onMessageReceived): , e); } } else { Log.w(TAG, Unity回调接口未设置无法发送消息。); } } /** * 内部方法通知Unity操作完成通过回调接口 * param isSuccess 是否成功 * param resultData 结果数据 */ private void onOperationComplete(boolean isSuccess, String resultData) { if (_unityCallback ! null) { try { _unityCallback.onOperationComplete(isSuccess, resultData); } catch (Exception e) { Log.e(TAG, 回调Unity失败 (onOperationComplete): , e); } } else { Log.w(TAG, Unity回调接口未设置无法通知操作完成。); } } // 备用方案使用UnityPlayer.UnitySendMessage直接向特定GameObject发送消息。 // 这种方式不需要AndroidJavaProxy但耦合度高且只能传简单参数。 public void sendMessageViaUnityPlayer(String gameObjectName, String methodName, String message) { UnityPlayer.UnitySendMessage(gameObjectName, methodName, message); } }3.4 第四步在Unity中测试将AndroidBridgeManager脚本挂载到一个GameObject上或者通过其单例访问。创建一个简单的测试UI如两个按钮。编写测试脚本// TestAndroidBridge.cs using UnityEngine; using UnityEngine.UI; public class TestAndroidBridge : MonoBehaviour { public Button sendMessageBtn; public Button requestOperationBtn; public Text logText; void Start() { // 确保管理器已初始化 var bridge AndroidBridgeManager.Instance; // 订阅回调事件 bridge.OnAndroidMessageReceived HandleAndroidMessage; bridge.OnOperationComplete HandleOperationComplete; sendMessageBtn.onClick.AddListener(() { bridge.SendMessageToAndroid(Hello from Unity UI!); AddLog(点击了【发送消息】按钮); }); requestOperationBtn.onClick.AddListener(() { bridge.RequestAndroidOperation(模拟数据123); AddLog(点击了【请求操作】按钮); }); } void HandleAndroidMessage(string msg) { AddLog($[事件] 收到Android消息: {msg}); } void HandleOperationComplete(bool success, string data) { string status success ? 成功 : 失败; AddLog($[事件] 操作完成: {status}, 数据: {data}); } void AddLog(string log) { if (logText ! null) { logText.text log \n logText.text; } Debug.Log(log); } void OnDestroy() { // 取消订阅防止内存泄漏 var bridge AndroidBridgeManager.Instance; if (bridge ! null) { bridge.OnAndroidMessageReceived - HandleAndroidMessage; bridge.OnOperationComplete - HandleOperationComplete; } } }构建APK在真机或模拟器上运行。点击按钮你将在LogCat和Unity编辑器的Console中看到双向通信的日志。4. 关键细节、避坑指南与性能优化实现基本功能后我们来看看那些决定成败的细节和常见的大坑。4.1 线程安全回调的“致命陷阱”这是使用AndroidJavaProxy时最容易导致崩溃的问题。Java端的回调很可能发生在非Unity主线程如网络线程、计时器线程、传感器线程。问题现象在回调函数中直接操作Unity对象如GameObject.Find、修改UI Text、实例化Prefab时游戏在真机上随机崩溃而在Editor中可能正常。错误信息可能很模糊如NullReferenceException或JNI相关错误。解决方案使用主线程派发器这是最推荐的做法。在C#回调接口的实现中绝不直接处理Unity对象逻辑而是将逻辑包装成一个Action放入一个主线程任务队列。在Unity的Update或LateUpdate中执行这些任务。推荐插件UnityMainThreadDispatcher。它是一个单例提供了Enqueue方法可以安全地从任何线程将任务提交到主线程执行。手动实现可以自己创建一个静态的ConcurrentQueueAction在Update中出队执行。修正后的回调示例使用假设的MainThreadDispatcher_callbackProxy new AndroidJavaProxy(com.yourcompany.yourgame.UnityCallback) { onMessageReceived (message) { // 立即将工作派发到主线程 MainThreadDispatcher.Instance.Enqueue(() { Debug.Log($[Unity主线程] 收到消息: {message}); // 现在可以安全地操作任何Unity对象了 if (OnAndroidMessageReceived ! null) OnAndroidMessageReceived(message); }); } };在Java端切换到主线程再回调如果你能控制Java端回调的触发点可以在回调前使用runOnUiThread或Handler切换到Android的主线程UI线程。但这并不能保证回到Unity的主线程因为从Android UI线程到Unity渲染线程还有一次切换。不过这通常能避免一些基础的JNI线程冲突。Unity的AndroidJavaProxy机制本身会处理一部分线程间通信但涉及Unity API时仍不安全。4.2 接口匹配魔鬼在细节里JNI根据方法名和参数签名进行匹配。任何不匹配都会导致回调失败方法不会被调用且可能无任何错误日志。检查清单方法名必须完全相同包括大小写。建议统一使用camelCase。参数类型string(C#) ↔java.lang.String(Java)bool(C#) ↔boolean(Java)int(C#) ↔int(Java)float(C#) ↔float(Java)double(C#) ↔double(Java)数组int[]↔int[]。对于对象数组如string[]在Java端是String[]但传递过程可能更复杂通常用JSON字符串传递复杂数据。返回类型必须完全相同。void对void。包名与类名在创建AndroidJavaProxy时传入的Java接口完整类名不能有错别字。调试技巧如果回调不执行首先在Java端回调处加Log确认Java方法是否被调用。如果被调用再在C#接口方法的第一行加Debug.Log。如果Java有Log而C#没有基本就是接口不匹配。4.3 生命周期管理防止内存泄漏和空指针初始化时机最好在Unity的Awake或游戏启动早期初始化AndroidBridgeManager并设为DontDestroyOnLoad确保在整个游戏生命周期内存在。清理引用在Unity的OnDestroy中务必调用Java端的清理方法如clearUnityCallback将Java端对C#代理的引用置空。否则即使Unity场景销毁Java对象仍持有旧代理的引用可能导致后续回调出错或内存无法回收。空引用检查在Java端每次回调前检查_unityCallback是否为null。在C#端调用Java方法前检查_androidJavaPlugin是否为null。4.4 性能优化建议减少通信频率避免每帧进行高频的跨语言调用。如果需要频繁传递数据如传感器信息考虑在Java端缓存以较低频率如每秒10次批量发送或使用更高效的通信方式如Unity的PlayerPrefs配合文件监视、或使用MemoryMappedFile进行共享内存通信但这更复杂。简化数据类型优先使用基本类型int,float,bool,string。避免传递复杂的嵌套对象。复杂结构使用JSON字符串序列化/反序列化是通用且可靠的做法。使用using语句对于AndroidJavaClass和AndroidJavaObject的局部变量使用using语句包裹以确保及时释放JNI引用避免局部引用表溢出。using (AndroidJavaClass jc new AndroidJavaClass(java.lang.System)) { string version jc.CallStaticstring(getProperty, java.version); }5. 进阶应用与场景扩展掌握了基础的双向通信后我们可以将其应用到更复杂的场景中。5.1 场景一集成第三方安卓SDK这是最常见的需求。很多SDK登录、支付、广告、推送都需要初始化并在事件发生后回调给游戏。模式初始化在AndroidBridgeManager中调用Java方法初始化SDK并传入AndroidJavaProxy作为回调监听器。Java封装在UnityBridge.java中初始化第三方SDK并将其回调监听器设置为UnityBridge本身或一个内部类。事件转发当SDK回调触发时在Java监听器方法中将事件参数转换为简单类型再通过持有的_unityCallback调用对应方法转发给Unity。示例简化支付回调// 在UnityBridge.java中 public class UnityBridge implements ThirdPartyPaySDK.PaymentListener { private UnityCallback _unityCallback; private ThirdPartyPaySDK _paySdk; public void initializePaySDK(String appId) { _paySdk ThirdPartyPaySDK.getInstance(context); _paySdk.setPaymentListener(this); _paySdk.initialize(appId); } // 实现第三方SDK的接口 Override public void onPaymentSuccess(String orderId, double amount) { // 将SDK回调转发给Unity if (_unityCallback ! null) { _unityCallback.onPaymentEvent(success, orderId | amount); } } Override public void onPaymentFailed(String orderId, String errorCode) { if (_unityCallback ! null) { _unityCallback.onPaymentEvent(failed, orderId | errorCode); } } }在C#侧定义onPaymentEvent方法解析参数触发游戏内的支付成功逻辑。5.2 场景二从Unity控制安卓原生UI有时需要在游戏上方显示一个原生Android对话框或Activity。模式启动ActivityUnity调用Java方法传递参数如对话框标题、内容。Java显示UIJava端启动一个Dialog或新的Activity。结果回调用户操作点击确定、取消后在该UI组件内调用_unityCallback的方法将选择结果回传。关键点显示UI和接收回调必须在Android的主线程UI线程进行。在Java端启动Activity或显示Dialog的代码需要放在runOnUiThread中。5.3 场景三持续性的数据流如传感器对于陀螺仪、GPS等需要持续数据的传感器。模式启动监听Unity调用Java方法开启传感器监听。Java端监听Java端注册传感器监听器在onSensorChanged中获取数据。节流回调在Java端不要每次传感器数据变化都立即回调Unity。可以设置一个时间戳或计数器比如每100毫秒或者数据变化超过一定阈值时才通过_unityCallback将最新的数据打包发送一次。这能极大降低通信开销。6. 常见问题排查与解决方案实录即使按照步骤操作也可能会遇到问题。这里记录了一些我踩过的坑和解决方法。问题1C#回调方法从未被调用Java端日志显示回调接口为null。可能原因1AndroidJavaProxy创建时传入的Java接口类名错误。排查检查new AndroidJavaProxy(com.yourcompany.yourgame.UnityCallback)中的包名和类名是否与Java文件完全一致。可能原因2setUnityCallback方法没有被成功调用或者调用时_callbackProxy对象无效。排查在Unity的InitializeAndroidBridge方法中在Call(setUnityCallback, _callbackProxy)前后加Debug.Log确认调用执行。检查Java端setUnityCallback方法的Log是否打印。可能原因3Proguard混淆了接口方法名。解决在Android项目的proguard-rules.pro文件中添加规则保留你的回调接口和类。-keep class com.yourcompany.yourgame.UnityCallback { *; } -keep class com.yourcompany.yourgame.UnityBridge { *; }问题2游戏在真机上运行一段时间后收到安卓回调时随机崩溃。可能原因线程安全问题。回调在非Unity主线程中操作了Unity对象。解决严格按照4.1章节实施主线程派发机制。这是生产环境必须做的。问题3回调可以执行但参数值不对如字符串乱码、数字错误。可能原因参数类型不匹配或编码问题。排查确保C#和Java的方法签名严格一致。对于字符串确保是UTF-8编码。避免传递中文字符时出问题通常Unity和Android默认都是UTF-8但需留意。问题4在Editor模式下运行正常打APK后回调失效。可能原因1代码没有正确包含在构建中。Unity在构建时可能会剥离未使用的代码。解决确保调用Android桥的代码在场景中某个始终激活的GameObject上。或者创建一个空的、始终被调用的方法强制Unity包含该库。可能原因2AndroidManifest.xml 或 Gradle配置缺少权限或特性。排查检查Java代码中是否用到了需要权限的功能如网络、传感器并在Manifest中声明。可能原因3IL2CPP Stripping 导致接口被剥离。解决在Project Settings - Player - Other Settings - Stripping中尝试降低 stripping level如从 High 调到 Low或者在link.xml文件中保留你的接口和桥接类。!-- Assets/link.xml -- linker assembly fullnameAssembly-CSharp preserveall/ !-- 或者更精确地保留你的类 -- assembly fullnameAssembly-CSharp type fullnameAndroidBridgeManager preserveall/ type fullnameIAndroidCallback preserveall/ /assembly /linker问题5调用Java方法时抛出AndroidJavaException: java.lang.NoSuchMethodError。可能原因方法名或签名错误。排查使用javap -s命令查看Java类的实际方法签名与C#调用处的签名对比。注意重载方法。示例对于public void showToast(String msg, int duration)其签名可能是(Ljava/lang/String;I)V。在C#中调用时参数顺序和类型必须匹配.Call(showToast, hello, 1)。最后调试是解决问题的关键。充分利用Debug.Log和 Android的Logcat在通信的每一步C#调用Java、Java收到调用、Java发起回调、C#收到回调都打上日志可以快速定位问题发生在哪个环节。将AndroidBridgeManager和UnityBridge的日志级别设为Verbose在开发阶段输出尽可能多的信息。