C# 스크립트에서 Java 코드를 호출하기 위해 Unity는 C++를 통해 Android JNI(Java 네이티브 인터페이스)와 통신하는 C# API를 제공합니다. Unity는 JNI를 사용하여 Java 코드와 상호 작용하는 데 사용할 수 있는 저수준 API와 고수준 API를 모두 제공합니다.
저수준 AndroidJNI 클래스는 JNI 호출을 래핑하고 JNI 메서드에 직접 매핑되는 정적 메서드를 제공합니다. AndroidJNIHelper API는 주로 고수준 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 파라미터입니다.hashCode()가 해시 코드를 정수로 반환하므로 Call에 대한 int 제네릭 유형 파라미터를 사용합니다.
참고: 점 표기법을 사용하여 중첩된 Java 클래스를 인스턴스화할 수 없습니다. $ 구분 기호를 사용하여 내부 클래스를 인스턴스화해야 합니다. 예를 들어 LayoutParams 클래스가 ViewGroup 클래스에 중첩된 android.view.ViewGroup$LayoutParams 또는 android/view/ViewGroup$LayoutParams를 사용합니다.
다음 코드 샘플은 플러그인을 사용하지 않고 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;
}
}
}
예시는 다음과 같습니다.
com.unity3d.player.UnityPlayer를 나타내는 AndroidJavaClass를 만듭니다. 정적 멤버에 액세스하기 위해 AndroidJavaObject 대신 AndroidJavaClass를 사용하는 것이 가장 좋습니다.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);
}
}
예시는 다음과 같습니다.
com.unity3d.player.UnityPlayer를 나타내는 AndroidJavaClass를 만듭니다.com.unity3d.player.UnityPlayer 멤버인 정적UnitySendMessage 메서드를 호출합니다.Unity 내에서 UnitySendMessage를 호출하면 Java를 사용하여 메시지를 중계하고, Java는 네이티브/Unity 코드를 다시 호출하여 오브젝트에 메시지를 전달합니다. 이 예시에서는 JavaMessage라는 메서드가 포함된 스크립트가 연결된 ‘My GameObject’라는 게임 오브젝트입니다.
이 섹션에서는 C# 스크립트에서 Java 및 Kotlin 플러그인 코드를 호출할 때 주의해야 할 베스트 프랙티스에 대해 설명합니다.
고수준 또는 저수준 C# API를 통해 JNI(Java 네이티브 인터페이스)를 사용하면 리소스를 많이 소모하고 속도가 느릴 수 있습니다. 성능을 향상시키고 코드 명확성을 높이기 위해 JNI 호출 수를 적게 유지하는 것이 가장 좋습니다.
불필요한 JNI 호출을 방지하기 위해 고수준 C# API는 호출하는 각 Java 메서드의 ID를 캐싱합니다. 즉, 동일한 메서드에 대한 후속 호출은 첫 번째 호출만큼 리소스를 많이 소모하지 않습니다. 호출은 같은 프레임 동안 또는 동일한 AndroidJavaObject/AndroidJavaClass 인스턴스에서 발생할 필요가 없습니다. 저수준 API를 사용하고 성능상의 이점을 원하는 경우 메서드 ID를 직접 캐싱해야 합니다. 그렇지 않으면 고수준 API를 사용하는 것이 좋습니다.
참고: Unity는 애플리케이션이 종료될 때까지 캐시를 유지합니다. 여기에는 애플리케이션이 백그라운드 상태인 경우가 포함됩니다.
Unity가 가능한 한 빨리 삭제하도록 AndroidJavaObject 또는 AndroidJavaClass의 모든 인스턴스를 using 문으로 래핑해야 합니다. 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 로그캣에서 보려면 AndroidJNIHelper.debug를 true로 설정합니다.