在Unity与Android原生代码的混合开发中,双向通信一直是开发者面临的痛点。不同于纯Unity环境下的C#脚本调用,当我们需要让Android端的Java/Kotlin代码主动触发Unity中的逻辑时,传统的SendMessage或UnityPlayer.UnitySendMessage方法存在明显的局限性——它们只能传递简单字符串参数,且缺乏类型安全和回调机制。
这正是AndroidJavaProxy大显身手的场景。作为Unity提供的一个专门用于处理Java端回调的基类,它允许我们在C#中创建一个Java接口的代理实现。当Android端调用该接口方法时,Unity会自动将调用转发到我们实现的C#方法中,同时处理JNI(Java Native Interface)层面的所有类型转换和线程调度。
AndroidJavaProxy的核心在于构建了一个双向的JNI桥接层。当我们在C#中继承AndroidJavaProxy并实现特定Java接口时,Unity会在底层做三件关键事情:
这个过程相当于在Java层创建了一个"傀儡对象",它的每个方法调用都会通过JNI转发到C#端的对应实现。以下是一个典型的接口映射流程:
csharp复制// Java端定义的接口
public interface UnityCallback {
void onEvent(int code, String message);
boolean confirmAction(String action);
}
// Unity端的代理实现
public class MyAndroidProxy : AndroidJavaProxy {
public MyAndroidProxy() : base("com.example.UnityCallback") {}
public void onEvent(int code, string message) {
Debug.Log($"Received event {code}: {message}");
}
public bool confirmAction(string action) {
return action == "allow";
}
}
AndroidJavaProxy自动处理了Java与C#之间的基础类型转换:
| Java类型 | C#类型 | 特殊说明 |
|---|---|---|
| boolean | bool | |
| int | int | |
| float | float | |
| double | double | |
| String | string | |
| Object | AndroidJavaObject | 需要手动处理 |
| array | Array | 需要元素类型匹配 |
对于复杂对象(如自定义类实例),会包装为AndroidJavaObject类型,开发者需要自行处理其字段和方法调用。
xml复制<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:launchMode="singleTask">
<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true"/>
</activity>
code复制-keep class com.yourpackage.UnityCallback { *; }
-keepclassmembers class * implements com.yourpackage.UnityCallback { *; }
步骤一:定义Java/Kotlin接口
java复制// 位于Android模块的src/main/java/com/example/UnityCallback.kt
interface UnityCallback {
fun onDataReceived(json: String)
fun requestPermission(permission: String): Boolean
}
步骤二:Unity端代理实现
csharp复制public class UnityAndroidCallback : AndroidJavaProxy {
public UnityAndroidCallback() : base("com.example.UnityCallback") {}
public void onDataReceived(string json) {
var data = JsonUtility.FromJson<SensorData>(json);
// 处理数据逻辑
}
public bool requestPermission(string permission) {
return Permission.HasUserAuthorizedPermission(permission);
}
}
步骤三:注册代理实例
csharp复制IEnumerator Start() {
yield return new WaitForSeconds(1); // 等待Unity初始化
using (var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (var currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
currentActivity.Call("setUnityCallback", new UnityAndroidCallback());
}
}
步骤四:Android端触发回调
java复制// 在Activity中保存回调引用
private UnityCallback unityCallback;
public void setUnityCallback(UnityCallback callback) {
this.unityCallback = callback;
}
// 任意位置触发回调
void onSensorChanged(SensorEvent event) {
if (unityCallback != null) {
String json = new Gson().toJson(event);
unityCallback.onDataReceived(json);
}
}
重要:所有从Java端发起的回调默认运行在Android的UI线程,而Unity的主循环运行在另一个线程。涉及Unity API调用的操作必须通过Unity的主线程调度:
csharp复制public void onDataReceived(string json) {
UnityMainThreadDispatcher.Instance().Enqueue(() => {
// 在这里安全地调用Unity API
textField.text = json;
});
}
java复制@Override
protected void onDestroy() {
if (unityCallback != null) {
unityCallback = null;
}
super.onDestroy();
}
csharp复制public class UnityAndroidCallback : AndroidJavaProxy, IDisposable {
private bool disposed;
public void Dispose() {
if (!disposed) {
// 释放资源
disposed = true;
}
}
~UnityAndroidCallback() {
Dispose();
}
}
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 回调未触发 | 接口名不匹配 | 检查Java接口全限定名 |
| 参数值为null | 类型转换失败 | 检查参数类型映射 |
| Unity崩溃 | 主线程冲突 | 使用UnityMainThreadDispatcher |
| 方法签名不匹配 | 混淆后方法名变化 | 更新Proguard规则 |
| 仅第一次回调有效 | Java端弱引用丢失 | 改为强引用存储 |
通过组合模式实现多个Java接口的代理:
csharp复制public class MultiCallbackProxy : AndroidJavaProxy {
private IDataCallback dataCallback;
private IPermissionCallback permCallback;
public MultiCallbackProxy(IDataCallback data, IPermissionCallback perm)
: base("com.example.CombinedCallback") {
this.dataCallback = data;
this.permCallback = perm;
}
public void onData(string json) {
dataCallback?.ProcessData(json);
}
public bool onPermission(string perm) {
return permCallback?.CheckPermission(perm) ?? false;
}
}
处理需要等待Unity协程完成的场景:
csharp复制public bool requestData(string query) {
var completionSource = new TaskCompletionSource<bool>();
StartCoroutine(FetchDataCoroutine(query, success => {
completionSource.SetResult(success);
}));
return completionSource.Task.Result;
}
csharp复制public void onError(string errorJson) {
try {
var error = JsonUtility.FromJson<ErrorData>(errorJson);
// 处理错误
} catch (Exception e) {
Debug.LogError($"Error processing callback: {e}");
AndroidJavaClass log = new AndroidJavaClass("android.util.Log");
log.CallStatic<int>("e", "Unity", $"C# error: {e}");
}
}
在实际项目中,我们通过AndroidJavaProxy成功实现了以下复杂交互:
这种方案的稳定性已经在百万级DAU的产品中得到验证,关键是要处理好线程调度和对象生命周期管理。对于高频调用的场景(如传感器数据),建议采用批处理模式减少JNI调用次数。