Unity与Android交互

Preface

  Unity中可以使用C#与Android平台java代码交互(相互调用,传递数据),为在Unity中相关功能实现和原生Android功能实现完美配合提供技术基础。通过Unity提供的Android交互辅助模块,可以很快捷方便的实现交互,接下来就逐个介绍技术细节。

Unity中调用Java函数

  通过使用AndroidJavaClass和AndroidJavaObject配合可以调用几乎所有的java类和对象的静态方法,非静态方法,获取静态或非静态对象,当然操作的对象的属性必须是public。比如下面的代码,目的就是在UnityC#端给AndroidJava端设置Youmi插件相关参数,可以看出使用AndroidJavaClass和AndroidJavaObject可以完美配合操作。

AndroidJavaClass unityplayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
AndroidJavaObject currentactivity = unityplayer.GetStatic<AndroidJavaObject>("currentActivity");
var packMgr = jo.Call<AndroidJavaObject>("getPackageManager");
var activityInfo = packMgr.Call<AndroidJavaObject>("getActivityInfo", jo.Call<AndroidJavaObject>("getComponentName"), 0x00000080/*PackageManager.GET_META_DATA*/);
var metaData = activityInfo.Get<AndroidJavaObject>("metaData");
appkey = metaData.Call<string>("getString", "YIMAppKey");
appSecret = metaData.Call<string>("getString", "YIMAppSecretKey");

  这里介绍示例中Call关键词相关的方法,其中包含Call和CallStatic两类,在AndroidJavaClass和AndroidJavaObject中都包含这两类方法,定义如下:

        //
        // 摘要:
        //     ///
        //     Call a Java method on an object.
        //     ///
        //
        // 参数:
        //   methodName:
        //     Specifies which method to call.
        //
        //   args:
        //     An array of parameters passed to the method.
        public void Call(string methodName, params object[] args);
        public ReturnType Call<ReturnType>(string methodName, params object[] args);
        //
        // 摘要:
        //     ///
        //     Call a static Java method on a class.
        //     ///
        //
        // 参数:
        //   methodName:
        //     Specifies which method to call.
        //
        //   args:
        //     An array of parameters passed to the method.
        public void CallStatic(string methodName, params object[] args);
        public ReturnType CallStatic<ReturnType>(string methodName, params object[] args);

   选择调用什么函数要依据传入函数参数列表和返回值类型来匹配函数签名,非模板的Call和CallStatic默认调用返回值为void的函数。Call方法和CallStatic比较,顾名思义,可以得知Call调用非静态的成员函数,CallStatic调用静态成员函数。带Return模板类型的模板函数则是定义返回类型。需要注意的是,一定要注意传入的参数和返回类型所对应的函数签名一定要和Java中目标函数签名对应上,稍有不同都不会调用成功。下面展示不同的C#类型对应的在Java函数签名的标记:

Element TypeEncoding
booleanZ
byteB
charC
doubleD
floatF
intI
longJ
shortS
class or interfaceLclassname
class or basictype[] (数组类型)[classname or basictypeEndcoding

   这里针对最后两项详细说明一下,向Call系列函数(包含CallStatic,以下不再说明)传入某个C#中class类型的对象,Unity中Android辅助模块是不能识别该类型,更加不能生成如上的类型编码,比如传入AndroidJavaObject或者AndroidJavaClass对象方可,这两种都可以以文章最开始示例代码中的方式去获取定义。数组类型也可以被识别,比如int[]就会对应于[I, AndroidJavaObject[]对应的Encoding是 [Ljava.lang.Object。
   之前在实际项目中遇到一个问题,我需要在Unity中调用一个特定的函数来达到一个临时的需求,我按照java代码中传参的使用示例在Unity中对应的传参数,发现报错提示找不到对应函数签名的函数,后来查看该函数定义,发现参数列表中最后一项是一个参数包,(java.lang.Object…),虽然在我需要参考的调用中并没有使用这个参数,但是定义确实存在很明显这是设计者用来扩展使用的。经过多次尝试,我在C#代码中如下定义了一个局部变量在Call函数中最后传入,就营造出完全匹配的函数签名。

AndroidJavaObject[] = {};

   之前在StackOverflow上发现一个类似的问题的解决方案,我参考其稍加修改以适合自己的问题,代码如下:

AndroidJavaClass arrayClass  = new AndroidJavaClass("java.lang.reflect.Array");
AndroidJavaObject arrayObject = arrayClass.CallStatic<AndroidJavaObject>("newInstance", new AndroidJavaClass("java.lang.Object"), 0);

   实际运行的时候提示的函数签名中关于这一个参数却是L[Ljava/lang/Object,并非期望的[Ljava/lang/Object,观察代码发现其实也不难理解,从java中的得到数组对象在倒C#端来被描述的时候又被视为一个完整的单个对象,故前面会多一个L。
   后来在调用java目标函数成功(得到期望的效果)后会产生一个来源于Call(returntype)中很严重的一个关于AndroidJavaObject的空错,通过各种猜想尝试均未能解决该问题,然后我依据错误信息在之前反编译的UnityEngine的代码中找到报错的位置:

protected ReturnType _CallStatic<ReturnType>(string methodName, params object[] args)
{
    if (args == null)
    {
        args = new object[1];
    }
    IntPtr methodID = AndroidJNIHelper.GetMethodID<ReturnType>(this.m_jclass, methodName, args, true);
    jvalue[] array = AndroidJNIHelper.CreateJNIArgArray(args);
    ReturnType result;
    try
    {
        if (AndroidReflection.IsPrimitive(typeof(ReturnType)))
        {
            if (typeof(ReturnType) == typeof(int))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticIntMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(bool))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticBooleanMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(byte))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticByteMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(short))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticShortMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(long))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticLongMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(float))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticFloatMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(double))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticDoubleMethod(this.m_jclass, methodID, array));
            }
            else if (typeof(ReturnType) == typeof(char))
            {
                result = (ReturnType)((object)AndroidJNISafe.CallStaticCharMethod(this.m_jclass, methodID, array));
            }
            else
            {
                result = default(ReturnType);
            }
        }
        else if (typeof(ReturnType) == typeof(string))
        {
            result = (ReturnType)((object)AndroidJNISafe.CallStaticStringMethod(this.m_jclass, methodID, array));
        }
        else if (typeof(ReturnType) == typeof(AndroidJavaClass))
        {
            IntPtr jclass = AndroidJNISafe.CallStaticObjectMethod(this.m_jclass, methodID, array);
            result = (ReturnType)((object)AndroidJavaObject.AndroidJavaClassDeleteLocalRef(jclass));
        }
        else if (typeof(ReturnType) == typeof(AndroidJavaObject))
        {
            IntPtr jobject = AndroidJNISafe.CallStaticObjectMethod(this.m_jclass, methodID, array);
            result = (ReturnType)((object)AndroidJavaObject.AndroidJavaObjectDeleteLocalRef(jobject));
        }
        else
        {
            if (!AndroidReflection.IsAssignableFrom(typeof(Array), typeof(ReturnType)))
            {
                throw new Exception("JNI: Unknown return type '" + typeof(ReturnType) + "'");
            }
            IntPtr array2 = AndroidJNISafe.CallStaticObjectMethod(this.m_jclass, methodID, array);
            result = (ReturnType)((object)AndroidJNIHelper.ConvertFromJNIArray<ReturnType>(array2));
        }
    }
    finally
    {
        AndroidJNIHelper.DeleteJNIArgArray(args, array);
    }
    return result;
}

   依据错误堆栈中最后一处关于AndroidJavaObjectDeleteLocalRef的空错。猜想在如下代码块中jobject可能是空,依据上面完整函数的分析,可以猜想我调用的目标函数反复了一个null值,才会在这个函数引起异常,不知道为什么Unity没在这里做保护,因为调用的java函数很有可能返回空值。于是我参照开函数的实现,使用AndroidJNIHelper,AndroidJNI自己来实现调用java函数,规避这个错误,然后得到期望结果。

else if (typeof(ReturnType) == typeof(AndroidJavaObject))
{
    IntPtr jobject = AndroidJNISafe.CallStaticObjectMethod(this.m_jclass, methodID, array);
    result = (ReturnType)((object)AndroidJavaObject.AndroidJavaObjectDeleteLocalRef(jobject));
}

   关于实现的伪代码如下:

AndroidJavaObject[] argsForExtend = { };
object[] argsOfCF = { currentactivity, "xxxx", argsForExtend };
IntPtr methodID = AndroidJNIHelper.GetMethodID<AndroidJavaObject>(someobject.GetRawClass(), "callFunction", argsOfCF, true);
jvalue[] argsArray = AndroidJNIHelper.CreateJNIArgArray(argsOfCF);

AndroidJNI.CallStaticObjectMethod(someobject.GetRawClass(), methodID, argsArray);

小结

   如上介绍Unity中调用Java的时候,在后边通过描述我自己遇到的一个典型的问题来深入的说明了关于Unity中调用Java的深度内容。在解决问题的时候,关于反射的理解,关于一些底层的知识和工具也为解决问题提供了关键的支持。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值