# 从 C# 脚本调用 Java 和 Kotlin 插件代码

> Interact with Java and Kotlin plug-in code from your C# scripts.

为了从 C# 脚本调用 Java 代码，Unity 提供了通过 C++ 与 Android [Java 原生接口](https://developer.android.com/training/articles/perf-jni) (JNI) 通信的 C# API。Unity 同时提供低级和高级 API，借助它们就可以使用 JNI 与 Java 代码交互了。

## 低级 API

低级 [AndroidJNI](/engine/6000.0/script-reference/unity-engine/android-jni.md) 类封装 JNI 调用并提供直接映射到 JNI 方法的静态方法。[AndroidJNIHelper](/engine/6000.0/script-reference/unity-engine/android-jni-helper.md) API 提供的 helper 功能主要由高级 API 使用，但在某些情况下很有用。

## 高级 API

高级 [AndroidJavaObject](/engine/6000.0/script-reference/unity-engine/android-java-object.md)、[AndroidJavaClass](/engine/6000.0/script-reference/unity-engine/android-java-class.md) 和 [AndroidJavaProxy](/engine/6000.0/script-reference/unity-engine/android-java-proxy.md) API 可自动执行 JNI 调用所需的大量任务。它们还使用缓存来更快地调用 Java。`AndroidJavaObject` 和 `AndroidJavaClass` 的组合构建在 `AndroidJNI` 和 `AndroidJNIHelper` 之上，但它们还包含其他功能，例如可用于访问 Java 类的静态成员的静态方法。

`AndroidJavaObject` 和 `AndroidJavaClass` 的实例分别与 [java.lang.Object](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html) 和 [java.lang.Class](https://docs.oracle.com/javase/7/docs/api/java/lang/Class.html) 的实例进行一对一映射。它们提供了三种类型的 Java/Kotlin 代码交互：

* [调用](/engine/6000.0/script-reference/unity-engine/android-java-object/call.md)一个方法。
* [获取](/engine/6000.0/script-reference/unity-engine/android-java-object/get.md)字段的值。
* [设置](/engine/6000.0/script-reference/unity-engine/android-java-object/set.md)字段的值。

每个交互还有一个静态版本：

* 调用某一静态方法的 [CallStatic](/engine/6000.0/script-reference/unity-engine/android-java-object/call-static.md)。
* 获取某一静态字段的值的 [GetStatic](/engine/6000.0/script-reference/unity-engine/android-java-object/get-static.md)。
* 设置某一静态字段的值的[SetStatic](/engine/6000.0/script-reference/unity-engine/android-java-object/set-static.md)。

获取字段的值或调用返回值的方法时，可使用[泛型](https://docs.microsoft.com/en-us/dotnet/csharp/fundamentals/types/generics)指定返回类型。设置字段的值时，还可使用泛型来指定要设置的字段的类型。对于不返回值的方法，存在常规的非泛型[调用](/engine/6000.0/script-reference/unity-engine/android-java-object/call.md)版本。

**重要信息**：必须以 `AndroidJavaObject` 身份访问任何[非原始类型](http://developer.android.com/reference/java/lang/Class.html)。唯一的例外是字符串，即使字符串不代表 Java 中的原始类型，也可以直接访问它们。

### 示例

本节包含的代码示例展示了如何使用高级 `AndroidJavaObject` 和 `AndroidJavaClass` API。

#### 获取 Java 字符串的哈希代码

以下代码示例创建了一个通过[字符串](http://developer.android.com/reference/java/lang/String.html#String\(java.lang.StringBuilder\))初始化的 [java.lang.String](http://developer.android.com/reference/java/lang/String.html) 实例，并检索了该字符串的[哈希值](http://developer.android.com/reference/java/lang/String.html#hashCode\(\)))。

```c#
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;
        }
    }
}
```

此示例：

1. 创建一个表示 [java.lang.String](http://developer.android.com/reference/java/lang/String.html) 的 `AndroidJavaObject`。`AndroidJavaObject` 构造函数至少使用一个参数：要构造实例的类的名称。类名后的所有参数都用于对象的构造函数调用，在本例中为来自 `GetJavaStringHashCode` 的 `text` 参数。
2. 调用 [hashCode()](https://developer.android.com/reference/java/lang/String.html#hashCode\(\)) 以获取字符串的哈希代码。此调用对 `Call` 使用 `int` 泛型类型参数，因为 `hashCode()` 将哈希代码返回为整数。

**注意**：不能使用虚线表示法来实例化嵌套的 Java 类。必须使用 `$` 分隔符来实例化内部类。例如，使用 `android.view.ViewGroup$LayoutParams` 或 `android/view/ViewGroup$LayoutParams`，其中 `LayoutParams` 类嵌套在 `ViewGroup` 类中。

#### 获取缓存目录

以下代码示例显示了如何在不使用插件的情况下用 C# 获取当前应用程序的缓存目录。

```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;
        }
    }
}
```

此示例：

1. 创建了一个 `AndroidJavaClass` 来表示 `com.unity3d.player.UnityPlayer`。最佳做法是使用 `AndroidJavaClass` 而不是 `AndroidJavaObject` 来访问静态成员。
2. 创建了一个 `AndroidJavaObject` 来表示当前[活动](http://developer.android.com/reference/android/app/Activity.html)，这是 `com.unity3d.player.UnityPlayer` 的静态成员。
3. 对活动对象调用了 [getCacheDir()](http://developer.android.com/reference/android/content/Context.html#getCacheDir\(\))，返回了一个表示缓存目录的[文件](https://developer.android.com/reference/java/io/File.html)对象。
4. 对文件对象调用了 [getCanonicalPath()](http://developer.android.com/reference/java/io/File.html#getCanonicalPath\(\))，可以获取字符串形式的缓存目录。

**注意**：此示例仅供参考。要访问应用程序的缓存和文件目录，请使用 [Application.temporaryCachePath](/engine/6000.0/script-reference/unity-engine/application/temporary-cache-path.md) 和 [Application.persistentDataPath](/engine/6000.0/script-reference/unity-engine/application/persistent-data-path.md) API。

#### 将数据从 Java 传递到 Unity

以下代码示例显示了如何使用 `UnitySendMessage` 将数据从 Java 传递到 Unity。

```c#
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);
    }
}
```

此示例：

1. 创建了一个 `AndroidJavaClass` 来表示 `com.unity3d.player.UnityPlayer`。
2. 调用作为 `com.unity3d.player.UnityPlayer` 成员的静态 `UnitySendMessage` 方法。

尽管是在 Unity 内部调用 `UnitySendMessage`，但该方法使用 Java 来中继消息，然后 Java 回调 native/Unity 代码，将消息传递给对象（在本示例中是名为 My GameObject 的游戏对象）。此对象附带一个脚本，脚本中包含一个名为 `JavaMessage` 的方法。

## 最佳做法

本节介绍从 C# 脚本调用 Java 和 Kotlin 插件代码时需要注意的最佳做法。

### 最小化 JNI 调用

使用 Java 原生接口 (JNI)，通过高级或低级 C# API 会占用大量资源，并且速度可能很慢。为了提高性能和代码清晰度，最佳做法是保持较低的 JNI 调用数量。

为了避免不必要的 JNI 调用，高级 C# API 会缓存调用的每个 Java 方法的 [ID](/engine/6000.0/script-reference/unity-engine/android-jni/get-method-id.md)。这意味着对同一方法的后续调用不像第一次调用那样耗费资源。调用不需要发生在同一帧期间，甚至不需要来自同一 AndroidJavaObject/AndroidJavaClass 实例。如果使用低级 API 并希望获得此性能优势，则必须自己缓存方法 ID。否则，最好使用高级 API。

**注意**：Unity 会保留缓存数据，直到应用程序[关闭](https://support.google.com/android/answer/9079646?hl=en-GB)。应用程序在后台时也是如此。

### 垃圾收集

应该使用 `using` 语句将所有 `AndroidJavaObject` 或 `AndroidJavaClass` 实例封装起来，以确保 Unity 尽快销毁它们。如果不使用 `using`，Unity 的[垃圾回收器](/engine/6000.0/manual/analysis/performance-memory-overview/managed-memory/garbage-collector.md)应该仍会释放所有创建的实例，但无法控制何时释放。

以下代码示例展示了如何使用 `using` 语句以最佳方式获取系统语言：

```c#
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](https://docs.unity3d.com/Packages/com.unity.mobile.android-logcat@latest/index.html) 中看到垃圾回收器的活动记录，请将 [AndroidJNIHelper.debug](/engine/6000.0/script-reference/unity-engine/android-jni-helper/debug.md) 设为 `true`。
