在学习 Unity 的过程中接触到了 Unity 与 Android 的互相调用,通过
AndroidJavaObject与AndroidJavaClass两个类来实现在 Unity 中调用 Android 代码,查阅Unity 手册时发现 使用上面两个类调用 Android 在首次运行时比较昂贵,而第二次之后就能够有性能优势。本文就是通过分析 Untiy 与 Android 端的代码来探索 -> 为什么第二次调用会比第一次性能要好
源码资源
在查看 Unity 项目中的AndroidJavaObject 类,发现方法声明,没有具体的实现。可以通过查看 Unity 在 Github 开源代码找到对应的具体实现。
AndroidJava.cs
AndroidJNISafe.cs
AndroidJNI.bindings.cs
上面这三个类包含了 Unity 端 C# 层用于与 Android 交互的代码。
ReflectionHelper.java
unity-class.jar 中包含的Android类,用于反射获取方法和变量的ID
调用过程
通过跟踪观察单个函数的调用过程,分析 Unity 调用 Android 方法过程
以 上文图片中的 jo.Call<int>("hashcode") 官方示例为例
整体函数调用逻辑
//AndroidJavaObject
public ReturnType Call<ReturnType>(string methodName, params object[] args)
{
return _Call<ReturnType>(methodName, args);
}
protected ReturnType _Call<ReturnType>(string methodName, params object[] args)
{
if (args == null) args = new object[] { null };
//获取对应的方法 ID
IntPtr methodID = AndroidJNIHelper.GetMethodID<ReturnType>(m_jclass, methodName, args, false);
//创建参数数组
jvalue[] jniArgs = AndroidJNIHelper.CreateJNIArgArray(args);
try
{
//判断是原始类型,直接调用对应的方法
if (AndroidReflection.IsPrimitive(typeof(ReturnType)))
{
if (typeof(ReturnType) == typeof(Int32))
return (ReturnType)(object)AndroidJNISafe.CallIntMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Boolean))
return (ReturnType)(object)AndroidJNISafe.CallBooleanMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Byte))
{
Debug.LogWarning("Return type <Byte> for Java method call is obsolete, use return type <SByte> instead");
return (ReturnType)(object)(Byte)AndroidJNISafe.CallSByteMethod(m_jobject, methodID, jniArgs);
}
else if (typeof(ReturnType) == typeof(SByte))
return (ReturnType)(object)AndroidJNISafe.CallSByteMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Int16))
return (ReturnType)(object)AndroidJNISafe.CallShortMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Int64))
return (ReturnType)(object)AndroidJNISafe.CallLongMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Single))
return (ReturnType)(object)AndroidJNISafe.CallFloatMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Double))
return (ReturnType)(object)AndroidJNISafe.CallDoubleMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(Char))
return (ReturnType)(object)AndroidJNISafe.CallCharMethod(m_jobject, methodID, jniArgs);
}
else if (typeof(ReturnType) == typeof(String))
return (ReturnType)(object)AndroidJNISafe.CallStringMethod(m_jobject, methodID, jniArgs);
else if (typeof(ReturnType) == typeof(AndroidJavaClass))
{
IntPtr jclass = AndroidJNISafe.CallObjectMethod(m_jobject, methodID, jniArgs);
return (jclass == IntPtr.Zero) ? default(ReturnType) : (ReturnType)(object)AndroidJavaClassDeleteLocalRef(jclass);
}
else if (typeof(ReturnType) == typeof(AndroidJavaObject))
{
IntPtr jobject = AndroidJNISafe.CallObjectMethod(m_jobject, methodID, jniArgs);
return (jobject == IntPtr.Zero) ? default(ReturnType) : (ReturnType)(object)AndroidJavaObjectDeleteLocalRef(jobject);
}
else if (AndroidReflection.IsAssignableFrom(typeof(System.Array), typeof(ReturnType)))
{
IntPtr jobject = AndroidJNISafe.CallObjectMethod(m_jobject, methodID, jniArgs);
return (jobject == IntPtr.Zero) ? default(ReturnType) : (ReturnType)(object)AndroidJNIHelper.ConvertFromJNIArray<ReturnType>(jobject);
}
else
{
throw new Exception("JNI: Unknown return type '" + typeof(ReturnType) + "'");
}
return default(ReturnType);
}
finally
{
//销毁运行过程中产生内存
AndroidJNIHelper.DeleteJNIArgArray(args, jniArgs);
}
}
//AndroidJNISafe
public static Int32 CallIntMethod(IntPtr obj, IntPtr methodID, jvalue[] args)
{
try { return AndroidJNI.CallIntMethod(obj, methodID, args); } finally { CheckException(); }
}
//AndroidJNI
[ThreadSafe]
public static extern Int32 CallIntMethod(IntPtr obj, IntPtr methodID, jvalue[] args);
分析代码,发现在 Unity 中实际上也是通过 JNI 层与 Android 交互。
- 获取目标方法的 ID
- 创建目标方法参数数组
- 执行对应的 JNI 方法
获取方法签名
//AndroidJNIHelper
// Get a JNI method ID based on calling arguments.
public static System.IntPtr GetMethodID<ReturnType>(System.IntPtr jclass, string methodName, object[] args, bool isStatic)
{
return _AndroidJNIHelper.GetMethodID<ReturnType>(jclass, methodName, args, isStatic);
}
//_AndroidJNIHelper
public static System.IntPtr GetMethodID<ReturnType>(System.IntPtr jclass, string methodName, object[] args, bool isStatic)
{
return AndroidJNIHelper.GetMethodID(jclass, methodName, GetSignature<ReturnType>(args), isStatic);
}
//AndroidJNIHelper
// Scans a particular Java class for a method matching a name and a signature.
public static IntPtr GetMethodID(IntPtr javaClass, string methodName, [UnityEngine.Internal.DefaultValue("")] string signature, [UnityEngine.Internal.DefaultValue("false")] bool isStatic)
{
return _AndroidJNIHelper.GetMethodID(javaClass, methodName, signature, isStatic);
}
//_AndroidJNIHelper
public static IntPtr GetMethodID(IntPtr jclass, string methodName, string signature, bool isStatic)
{
IntPtr method = IntPtr.Zero;
try
{
method = AndroidReflection.GetMethodMember(jclass, methodName, signature, isStatic);
return AndroidJNISafe.FromReflectedMethod(method);
}
catch (Exception e)
{
// Make sure this method does not throw to keep e intact
IntPtr memberID = GetMethodIDFallback(jclass, methodName, signature, isStatic);
if (memberID != IntPtr.Zero)
return memberID;
throw e;
}
finally
{
AndroidJNISafe.DeleteLocalRef(method);
}
}
//AndroidReflection
private const string RELECTION_HELPER_CLASS_NAME = "com/unity3d/player/ReflectionHelper";
private static readonly GlobalJavaObjectRef s_ReflectionHelperClass = new GlobalJavaObjectRef(AndroidJNISafe.FindClass(RELECTION_HELPER_CLASS_NAME));
private static readonly IntPtr s_ReflectionHelperGetMethodID = GetStaticMethodID(RELECTION_HELPER_CLASS_NAME, "getMethodID", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Z)Ljava/lang/reflect/Method;");
public static IntPtr GetMethodMember(IntPtr jclass, string methodName, string signature, bool isStatic)
{
jvalue[] jniArgs = new jvalue[4];
try
{
jniArgs[0].l = jclass;
jniArgs[1].l = AndroidJNISafe.NewString(methodName);
jniArgs[2].l = AndroidJNISafe.NewString(signature);
jniArgs[3].z = isStatic;
return AndroidJNISafe.CallStaticObjectMethod(s_ReflectionHelperClass, s_ReflectionHelperGetMethodID, jniArgs);
}
finally
{
AndroidJNISafe.DeleteLocalRef(jniArgs[1].l);
AndroidJNISafe.DeleteLocalRef(jniArgs[2].l);
}
}
//AndroidJNISafe
public static IntPtr CallStaticObjectMethod(IntPtr clazz, IntPtr methodID, jvalue[] args)
{
try { return AndroidJNI.CallStaticObjectMethod(clazz, methodID, args); } finally { CheckException(); }
}
//AndroidJNI
[ThreadSafe]
public static extern IntPtr CallStaticObjectMethod(IntPtr clazz, IntPtr methodID, jvalue[] args);
// Converts a <tt>java.lang.reflect.Method</tt> or <tt>java.lang.reflect.Constructor</tt> object to a method ID.
[ThreadSafe]
public static extern IntPtr FromReflectedMethod(IntPtr refMethod);
核心部分是通过调用AndroidReflection的反射方法,调用到 Android端的反射函数。获取方法的 ID,然后再通过 JNI 转换一下。
Android 端方法调用
//ReflectionHelper
protected static Method getMethodID(Class clazz, String method, String signature, boolean isStatic) {
if (LOG) {
g.Log(3, "? getMethodID(\"" + clazz.getName() + "\", \"" + method + "\", \"" + var2 + "\", " + (isStatic ? "static)" : "non-static)"));
}
Method androidMethod = null;
ReflectionHelper.ReflectionData reflectiondata;
int methodCount;
if (hasReflectionDataCache(reflectiondata = new ReflectionHelper.ReflectionData(clazz, method, signature))) {
androidMethod = (Method)reflectiondata.member;
} else {
Class[] paramClass = GetAndroidMenthodFunParamClass(method);
for(float i = 0.0F; clazz != null; clazz = clazz.getSuperclass()) {
Method[] methods;
methodCount = (methods = clazz.getDeclaredMethods()).length;
for(int j = 0; j < methodCount; ++j) {
Method localMethod = methods[j];
float var12;
if (isStatic == Modifier.isStatic(localMethod.getModifiers()) && localMethod.getName().compareTo(method) == 0 && (percent = GetFunctionMatchPercent(localMethod.getReturnType(), localMethod.getParameterTypes(), var6)) > i) {
androidMethod = localMethod;
i = percent;
if (percent == 1.0F) {
break;
}
}
}
if (i == 1.0F || clazz.isPrimitive() || clazz.isInterface() || clazz.equals(Object.class) || clazz.equals(Void.TYPE)) {
break;
}
}
SetReflectionDataToCache(reflectiondata, androidMethod);
}
return androidMethod;
}
通过分析源码能够发现,ReflectionHelper 类中保存这当前反射过的方法引用,在之后的调用中如果缓存中存在,就直接返回,不会再次获取。
- 查看缓存,存在则直接返回
- 不存在,遍历当前类和所有父类,查找方法签名一致的方法
- 添加到缓存中
- 返回查找到的方法
总结
在实际使用中 ,第二次调用能够比第一次调用性能优秀的关键在于 ReflectionHelper中的缓存数组,在每次获取到新的方法,变量,构造函数之后都会添加到 缓存数组中,用于之后的再次调用。