为了从 C# 脚本调用 Java 代码,Unity 提供了通过 C++ 与 Android Java 原生接口 (JNI) 通信的 C# API。Unity 同时提供低级和高级 API,借助它们就可以使用 JNI 与 Java 代码交互了。
低级 AndroidJNI 类封装 JNI 调用并提供直接映射到 JNI 方法的静态方法。AndroidJNIHelper API 提供的 helper 功能主要由高级 API 使用,但在某些情况下很有用。
高级 AndroidJavaObject、AndroidJavaClass 和 AndroidJavaProxy API 可自动执行 JNI 调用所需的大量任务。它们还使用缓存来更快地调用 Java。AndroidJavaObject 和 AndroidJavaClass 的组合构建在 AndroidJNI 和 AndroidJNIHelper 之上,但它们还包含其他功能,例如可用于访问 Java 类的静态成员的静态方法。
AndroidJavaObject 和 AndroidJavaClass 的实例分别与 java.lang.Object 和 java.lang.Class 的实例进行一对一映射。它们提供了三种类型的 Java/Kotlin 代码交互:
每个交互还有一个静态版本:
获取字段的值或调用返回值的方法时,可使用泛型指定返回类型。设置字段的值时,还可使用泛型来指定要设置的字段的类型。对于不返回值的方法,存在常规的非泛型调用版本。
重要信息:必须以 AndroidJavaObject 身份访问任何非原始类型。唯一的例外是字符串,即使字符串不代表 Java 中的原始类型,也可以直接访问它们。
本节包含的代码示例展示了如何使用高级 AndroidJavaObject 和 AndroidJavaClass API。
以下代码示例创建了一个通过字符串初始化的 java.lang.String 实例,并检索了该字符串的哈希值)。
using UnityEngine;
public class JavaExamples
{
public static int GetJavaStringHashCode(string text)
{
using (AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", text))
{
int hash = jo.Call<int>("hashCode");
return hash;
}
}
}
此示例:
AndroidJavaObject。AndroidJavaObject 构造函数至少使用一个参数:要构造实例的类的名称。类名后的所有参数都用于对象的构造函数调用,在本例中为来自 GetJavaStringHashCode 的 text 参数。Call 使用 int 泛型类型参数,因为 hashCode() 将哈希代码返回为整数。
注意:不能使用虚线表示法来实例化嵌套的 Java 类。必须使用 $ 分隔符来实例化内部类。例如,使用 android.view.ViewGroup$LayoutParams 或 android/view/ViewGroup$LayoutParams,其中 LayoutParams 类嵌套在 ViewGroup 类中。
以下代码示例显示了如何在不使用插件的情况下用 C# 获取当前应用程序的缓存目录。
using UnityEngine;
public class JavaExamples
{
public static string GetApplicationCacheDirectory()
{
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
using (AndroidJavaObject javaFile = currentActivity.Call<AndroidJavaObject>("getCacheDir"))
{
string cacheDirectory = javaFile.Call<string>("getCanonicalPath");
return cacheDirectory;
}
}
}
此示例:
AndroidJavaClass 来表示 com.unity3d.player.UnityPlayer。最佳做法是使用 AndroidJavaClass 而不是 AndroidJavaObject 来访问静态成员。AndroidJavaObject 来表示当前活动,这是 com.unity3d.player.UnityPlayer 的静态成员。注意:此示例仅供参考。要访问应用程序的缓存和文件目录,请使用 Application.temporaryCachePath 和 Application.persistentDataPath API。
以下代码示例显示了如何使用 UnitySendMessage 将数据从 Java 传递到 Unity。
using UnityEngine;
public class JavaExamples : MonoBehaviour
{
private void Start()
{
AndroidJNIHelper.debug = true;
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
{
jc.CallStatic("UnitySendMessage", "My GameObject", "JavaMessage", "NewMessage");
}
}
private void JavaMessage(string message)
{
Debug.Log("message from java: " + message);
}
}
此示例:
AndroidJavaClass 来表示 com.unity3d.player.UnityPlayer。com.unity3d.player.UnityPlayer 成员的静态 UnitySendMessage 方法。尽管是在 Unity 内部调用 UnitySendMessage,但该方法使用 Java 来中继消息,然后 Java 回调 native/Unity 代码,将消息传递给对象(在本示例中是名为 My__ GameObject__Unity 场景中的基础对象,可以表示角色、道具、风景、摄像机、路径点等。GameObject 的功能由所附的组件决定。更多信息
See in Glossary 的游戏对象)。此对象附带一个脚本,脚本中包含一个名为 JavaMessage 的方法。
本节介绍从 C# 脚本调用 Java 和 Kotlin 插件代码时需要注意的最佳做法。
使用 Java 原生接口 (JNI),通过高级或低级 C# API 会占用大量资源,并且速度可能很慢。为了提高性能和代码清晰度,最佳做法是保持较低的 JNI 调用数量。
为了避免不必要的 JNI 调用,高级 C# API 会缓存调用的每个 Java 方法的 ID。这意味着对同一方法的后续调用不像第一次调用那样耗费资源。调用不需要发生在同一帧期间,甚至不需要来自同一 AndroidJavaObject/AndroidJavaClass 实例。如果使用低级 API 并希望获得此性能优势,则必须自己缓存方法 ID。否则,最好使用高级 API。
注意:Unity 会保留缓存数据,直到应用程序关闭。应用程序在后台时也是如此。
应该使用 using 语句将所有 AndroidJavaObject 或 AndroidJavaClass 实例封装起来,以确保 Unity 尽快销毁它们。如果不使用 using,Unity 的垃圾回收器应该仍会释放所有创建的实例,但无法控制何时释放。
以下代码示例展示了如何使用 using 语句以最佳方式获取系统语言:
using UnityEngine;
public class LocaleExample : MonoBehaviour
{
void Start()
{
using (AndroidJavaClass cls = new AndroidJavaClass("java.util.Locale"))
using (AndroidJavaObject locale = cls.CallStatic<AndroidJavaObject>("getDefault"))
{
if (locale != null)
{
Debug.Log("current lang = " + locale.Call<string>("getDisplayLanguage"));
}
}
}
}
注意:要在 Android Logcat 中看到垃圾回收器的活动记录,请将 AndroidJNIHelper.debug 设为 true。