JAR(Java Archive) 插件主要用于实现与 Android 操作系统的交互,或用于从 C# 脚本中调用使用 Java 编写的方法。
这些插件只能包含 Java 代码(例如,不能包含 Android 资源),因此它们的使用范围非常有限。 要将 JAR 插件添加到项目中,请将 .jar 文件复制到项目的任意文件夹中,然后在 Unity 中选择该文件,从而在 Inspector 窗口中打开 Import Settings。应勾选 Android 复选框以将此 .jar 文件标记为与 Android 兼容:
从 Java 调用代码时以及使用本机代码或 C# 脚本与 Java 或 Java VM(虚拟机)交互时,Unity 均使用 Java 原生接口 (JNI)。
使用 Gradle 构建系统时,可以避免创建 JAR 文件。为此需要执行以下操作:
Unity 将 java 文件复制到 Gradle 项目并使用它进行构建。
注意:必须具备 Android Java 原生接口 (JNI) 的高级知识才能理解本部分的这些信息。
要从 C 或 C++ 插件访问 Java 代码,您需要访问 Java VM。为了访问 Java VM,请将以下方法添加到 C/C++ 代码中。
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* jni_env = 0;
vm->AttachCurrentThread(&jni_env, 0);
return JNI_VERSION_1_6;
}
篇幅所限,本文档不能完全解释 JNI,但需要说明的是,此方法通常涉及查找类定义、解析构造函数 (<init>) 方法和创建新对象实例,如以下示例所示:
jobject createJavaObject(JNIEnv* jni_env) {
jclass cls_JavaClass = jni_env->FindClass("com/your/java/Class"); // 查找类定义
jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>", "()V"); // 查找构造函数方法
jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass); // 创建对象实例
return jni_env->NewGlobalRef(obj_JavaClass); // 返回具有全局引用的对象
}
有关 JNI 的更多信息,请参阅 Android 开发者文档的 JNI 部分。
注意:必须具备 Android Java 原生接口 (JNI) 的高级知识才能理解本部分的这些信息。
AndroidJNIHelper
和 AndroidJNI
Unity API 类用作“原始”JNI 接口的包装器。
AndroidJavaObject 和 AndroidJavaClass Unity API 类在使用 JNI 调用时自动执行许多任务,并且还使用缓存来加快 Java 调用速度。AndroidJavaObject
和 AndroidJavaClass
的组合构建在 AndroidJNI
和 AndroidJNIHelper
之上,但也有一些额外的功能。这些类还具有静态方法可用于访问 Java 类的静态成员。
可通过三种方式从 C# 脚本进行 Java JNI 调用:
AndroidJNI
方法;AndroidJNIHelper
类与 AndroidJNI
相结合;AndroidJavaObject
和 AndroidJavaClass
类,作为最方便的高级 API。UnityEngine.AndroidJNI 是以 C 语言编写的 JNI 调用的包装器(如上所述)。此类中的所有方法都是静态的,并具有与 Java 原生接口的 1:1 映射。
UnityEngine.AndroidJNIHelper 提供由下一级使用的 helper 功能,该功能作为公共方法公开,在特殊情况下可能有用。
UnityEngine.AndroidJavaObject 和 UnityEngine.AndroidJavaClass 的实例分别与 Java 端的 java.lang.Object 和 java.lang.Class(或它们的子类)实例进行一对一映射。它们基本上提供与 Java 端的 3 种交互:
调用一个方法
获取字段的值
设置字段的值
该调用分为两类:调用“void”方法以及调用具有非 void 返回类型的方法。返回非 void 类型的方法的返回类型用泛型类型表示。Get 和 Set 始终采用表示字段类型的泛型类型。
AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string");
// jni.FindClass("java.lang.String");
// jni.GetMethodID(classID, "<init>", "(Ljava/lang/String;)V");
// jni.NewStringUTF("some_string");
// jni.NewObject(classID, methodID, javaString);
int hash = jo.Call<int>("hashCode");
// jni.GetMethodID(classID, "hashCode", "()I");
// jni.CallIntMethod(objectID, methodID);
此示例将创建一个用字符串进行实例化的 java.lang.String 实例,并检索该字符串的哈希值。
AndroidJavaObject
构造函数至少使用一个参数:要构造实例的类的名称。类名后的所有参数都用于对象的构造函数调用,在本例中为字符串“some_string”。随后对 hashCode() 的调用返回一个“int”(在本示例中用作调用方法的泛型类型参数)。
__注意__:无法使用点分表示法来实例化嵌套的 Java 类。内部类必须使用 $ 分隔符。请使用 android.view.ViewGroup$LayoutParams
或 android/view/ViewGroup$LayoutParams
,其中的 LayoutParams 类嵌套在 ViewGroup 类中。
此示例显示了如何在不使用插件的情况下用 C# 获取当前应用程序的缓存目录:
AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
// jni.FindClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject jo = jc.GetStatic AndroidJavaObject>("currentActivity");
// jni.GetStaticFieldID(classID, "Ljava/lang/Object;");
// jni.GetStaticObjectField(classID, fieldID);
// jni.FindClass("java.lang.Object");
Debug.Log(jo.Call AndroidJavaObject>("getCacheDir").Call<string>("getCanonicalPath"));
// jni.GetMethodID(classID, "getCacheDir", "()Ljava/io/File;"); // 或其任何基类!
// jni.CallObjectMethod(objectID, methodID);
// jni.FindClass("java.io.File");
// jni.GetMethodID(classID, "getCanonicalPath", "()Ljava/lang/String;");
// jni.CallObjectMethod(objectID, methodID);
// jni.GetStringUTFChars(javaString);
此示例以 AndroidJavaClass
而不是 AndroidJavaObject
开头,目的是访问 com.unity3d.player.UnityPlayer
的静态成员,而不是创建新对象。然后访问静态字段“currentActivity”,但这次使用 AndroidJavaObject
作为泛型参数。这是因为实际字段类型 android.app.Activity 是 java.lang.Object
的子类,并且任何非原始类型都必须作为 AndroidJavaObject
进行访问。此规则的例外是字符串,即使字符串不代表 Java 中的原始类型,也可以直接访问它们。
然后可在 Activity 对象上调用 getCacheDir()
来获取表示缓存目录的 File 对象,接着可调用 getCanonicalPath()
来获取字符串表示。
Unity 使用 Application.temporaryCachePath
和 Application.persistentDataPath
API 提供对应用程序的缓存和文件目录的访问。
此示例显示了如何使用 UnitySendMessage
将数据从 Java 传递到 Unity。
using UnityEngine;
public class NewBehaviourScript : MonoBehaviour {
void Start () {
AndroidJNIHelper.debug = true;
using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
jc.CallStatic("UnitySendMessage", "Main Camera", "JavaMessage", "NewMessage");
}
}
void JavaMessage(string message) {
Debug.Log("message from java: " + message);
}
}
Java 类 com.unity3d.player.UnityPlayer
有一个静态方法 UnitySendMessage
,相当于 iOS 方法:UnitySendMessage
。该方法在 Java 中用于将数据传递给 Unity。
尽管是在 Unity 内部调用 UnitySendMessage
方法,但该方法使用 Java 来中继消息,然后 Java 回调本机/Unity 代码以将消息传递给名为“Main Camera”的对象。此对象附带一个脚本,脚本中包含一个名为 JavaMessage
的方法。
AndroidJavaObject
和 AndroidJavaClass
方法的计算成本非常高(与使用原始 JNI 的方法一样)。为获得更好的性能以及代码清晰度,请将托管代码与本机/Java 代码之间的转换次数保持在最低限度。
//第一次调用 Java 方法,如
AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string"); // 成本有点高
int hash = jo.Call<int>("hashCode"); // 第一次 - 成本高
int hash = jo.Call<int>("hashCode"); // 第二次 - 成本不像我们已经知道的 java 方法那么高,可以直接调用它
Mono 垃圾回收器应该在使用创建的 AndroidJavaObject
和 AndroidJavaClass
实例后将所有这些实例释放,但建议将它们保留在 using(){}
语句中,以确保尽快删除它们。如果不这么做,无法确定什么时候销毁它们。如果将 AndroidJNIHelper.debug
设置为 true,您将在调试输出中看到垃圾回收器的活动记录。
//安全地获取系统语言
void Start () {
using (AndroidJavaClass cls = new AndroidJavaClass("java.util.Locale")) {
using(AndroidJavaObject locale = cls.CallStatic<AndroidJavaObject>("getDefault")) {
Debug.Log("current lang = " + locale.Call<string>("getDisplayLanguage"));
}
}
}