在Unity与Android原生代码交互的场景中,AndroidJavaProxy是一个强大但容易被忽视的桥梁类。它允许Unity端的C#代码直接响应来自Android原生Java代码的回调,这种双向通信机制在插件开发、SDK集成和混合应用架构中极为常见。我在多个商业项目中实际使用过这种交互方式,比如处理支付回调、获取传感器数据、接收推送通知等场景。
与常规的Unity调用Android方式(如AndroidJavaClass和AndroidJavaObject)不同,AndroidJavaProxy的核心价值在于"反向调用"——让Java端能主动触发Unity中的逻辑。这种设计模式在事件驱动型架构中尤为重要,比如当我们需要监听Android系统的广播事件、处理第三方SDK的异步回调时,如果没有AndroidJavaProxy,就只能通过轮询或文件监听等低效方式实现。
AndroidJavaProxy的本质是一个C#端的Java接口实现。当我们在Unity中创建一个继承自AndroidJavaProxy的类时,实际上是在C#侧创建了一个Java接口的代理实现。这个代理对象会被传递到Java端,Java代码可以像调用普通接口实现一样调用它,而实际执行的是我们在C#中定义的方法。
从JNI(Java Native Interface)层面看,整个过程经历了以下步骤:
在实际项目中,AndroidJavaProxy最常见的三种使用场景:
事件监听回调:比如处理Android系统的蓝牙状态变化、网络连接状态变更等广播事件。我们可以创建一个BroadcastReceiver的接口代理,在C#中直接处理这些系统事件。
异步操作回调:当集成第三方SDK时,很多操作(如支付验证、广告加载)都是异步的。使用AndroidJavaProxy可以直接在C#中接收这些回调,避免复杂的线程同步问题。
自定义通信协议:当Unity和Android需要频繁交互时,可以定义一套接口协议,两边分别实现对应部分。这种方式比通过UnitySendMessage更高效,也更容易维护。
首先需要在Android Studio中定义一个Java接口,这是整个通信机制的基础。例如我们创建一个简单的回调接口:
java复制// 文件路径:android/src/main/java/com/example/UnityCallback.java
package com.example;
public interface UnityCallback {
void onMessageReceived(String message);
int onDataRequested(String params);
}
然后在Unity项目中创建对应的C#代理类:
csharp复制// Unity项目中的C#脚本
public class UnityCallbackProxy : AndroidJavaProxy {
public UnityCallbackProxy() : base("com.example.UnityCallback") {}
public void onMessageReceived(string message) {
Debug.Log($"Java端发来消息: {message}");
// 在这里处理来自Java端的消息
}
public int onDataRequested(string params) {
Debug.Log($"Java端请求数据,参数: {params}");
// 返回Unity端的数据
return 42;
}
}
在Android端,我们需要接收并持有这个代理对象。通常会在Unity调用的Java方法中将代理对象保存起来:
java复制// Android端Java代码
public class UnityBridge {
private static UnityCallback callback;
public static void registerCallback(UnityCallback callback) {
this.callback = callback;
}
public static void triggerCallback() {
if(callback != null) {
callback.onMessageReceived("Hello from Java!");
int result = callback.onDataRequested("some params");
Log.d("UnityBridge", "Got result from Unity: " + result);
}
}
}
在Unity端,通过AndroidJavaClass调用注册方法:
csharp复制// Unity端C#代码
void Start() {
AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
// 创建代理实例并注册
UnityCallbackProxy proxy = new UnityCallbackProxy();
AndroidJavaClass bridgeClass = new AndroidJavaClass("com.example.UnityBridge");
bridgeClass.CallStatic("registerCallback", proxy);
// 测试触发Java端回调
bridgeClass.CallStatic("triggerCallback");
}
一个关键但容易被忽视的问题是线程同步。Android的回调通常发生在非Unity主线程,而Unity的API必须在主线程调用。我们需要使用Unity的Dispatcher来处理这种情况:
csharp复制public class UnityCallbackProxy : AndroidJavaProxy {
public UnityCallbackProxy() : base("com.example.UnityCallback") {}
public void onMessageReceived(string message) {
// 使用Unity的主线程调度器
UnityMainThreadDispatcher.Instance().Enqueue(() => {
Debug.Log($"主线程处理消息: {message}");
// 这里可以安全调用Unity API
});
}
}
提示:UnityMainThreadDispatcher是一个常用的开源工具类,需要单独实现或导入。它本质上利用了Unity的[ExecuteInEditMode]和Update机制来在主线程执行委托。
有时我们需要实现多个Java接口,但C#不支持多重继承。这时可以采用接口组合的方式:
csharp复制public class MultiCallbackProxy : AndroidJavaProxy {
public interface IMessageHandler {
void onMessageReceived(string message);
}
public interface IDataProvider {
int onDataRequested(string params);
}
private IMessageHandler messageHandler;
private IDataProvider dataProvider;
public MultiCallbackProxy(IMessageHandler msgHandler, IDataProvider dataProvider)
: base("com.example.UnityCallback") {
this.messageHandler = msgHandler;
this.dataProvider = dataProvider;
}
public void onMessageReceived(string message) {
messageHandler?.onMessageReceived(message);
}
public int onDataRequested(string params) {
return dataProvider?.onDataRequested(params) ?? -1;
}
}
AndroidJavaProxy对象在JNI层持有对C#对象的引用,如果不妥善管理会导致内存泄漏。最佳实践是:
csharp复制public class UnityBridgeManager : MonoBehaviour {
private UnityCallbackProxy proxy;
void Start() {
proxy = new UnityCallbackProxy();
// 注册到Java端...
}
void OnDestroy() {
// 通知Java端注销回调
new AndroidJavaClass("com.example.UnityBridge")
.CallStatic("unregisterCallback");
proxy = null;
}
}
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 回调没有被触发 | Java端没有正确持有代理对象 | 检查注册流程,确保代理对象被正确传递和保存 |
| 收到回调但Unity没有响应 | 方法签名不匹配 | 确保C#方法名、参数类型与Java接口完全一致 |
| Unity崩溃无错误信息 | 线程冲突 | 确保Unity API在主线程调用,使用Dispatcher |
| 只有第一次回调有效 | 代理对象被GC回收 | 在C#端保持对代理对象的强引用 |
| 参数传递错误 | JNI类型转换问题 | 使用简单类型,复杂对象需序列化为JSON |
在Android Studio的Logcat中过滤Unity日志:
bash复制adb logcat -s Unity
在Java端添加详细日志:
java复制Log.d("UnityBridge", "Callback received: " + message);
在C#端使用条件编译确保只在开发时输出调试信息:
csharp复制[Conditional("DEBUG")]
private void DebugLog(string message) {
Debug.Log(message);
}
在最近的一个AR项目中,我们使用AndroidJavaProxy来处理设备的陀螺仪数据。Java端每秒会产生数百次回调,直接处理会导致Unity端性能问题。最终解决方案是:
关键代码片段:
csharp复制public class SensorProxy : AndroidJavaProxy {
private ConcurrentQueue<SensorData> dataQueue = new ConcurrentQueue<SensorData>();
public void onSensorChanged(float x, float y, float z, long timestamp) {
dataQueue.Enqueue(new SensorData(x, y, z, timestamp));
}
public bool TryGetLatestData(out SensorData data) {
return dataQueue.TryDequeue(out data);
}
}
void Update() {
while(sensorProxy.TryGetLatestData(out var data)) {
// 处理传感器数据
}
}
另一个经验是关于异常处理。JNI异常不会自动转换为C#异常,必须手动检查:
csharp复制public void onMessageReceived(string message) {
try {
IntPtr env = AndroidJNI.GetEnv();
if(AndroidJNI.ExceptionOccurred() != IntPtr.Zero) {
AndroidJNI.ExceptionDescribe();
AndroidJNI.ExceptionClear();
Debug.LogError("JNI异常发生");
return;
}
// 正常处理逻辑...
} catch(Exception e) {
Debug.LogException(e);
}
}
在长时间的项目维护中,我们发现良好的接口版本管理也很重要。建议:
java复制public interface UnityCallback {
String getInterfaceVersion(); // 返回如"1.0.0"
// 其他方法...
}
通过这些实践,我们构建了稳定可靠的Unity-Android通信层,支撑了多个商业项目的核心功能。AndroidJavaProxy虽然学习曲线较陡,但一旦掌握就能极大扩展Unity与原生平台的集成能力。