天天看点

[unity3d]Unity3D与android交互----构建android插件

为android构建一个插件

extern "C"   

{  

  float FooPluginFunction ();  

}   

通过C#脚本使用插件

构建了共享库后,必须把共享库复制到unity3d工程中的Assets->Plugins->Android目录下。(没有该目录的话,自己依次创建。)

当你在unity3d中在C#脚本中定义如下的函数时,unity3d就能通过名称找到共享库

[DllImport ("PluginName")]  

private static extern float FooPluginFunction ();   

部署

对于要部署到多个平台的项目,项目工程中必须包含各个平台所需要的插件(例如:libPlugin.so用于android平台,Plugin.bundle用于mac平台,Plugin.dll用于windows平台)。unity3d会自动为目标平台选择正确的插件。

使用java插件

android插件机制同样允许使用java来与android系统进行交互。

为android构建一个java插件

在native代码中使用java插件

要找到你的java代码,必须要能访问到java虚拟机。幸运的是,可以通过在c/c++代码中添加如下函数来很容易的实现这种访问:

jint JNI_OnLoad(JavaVM* vm, void* reserved)   

  JNIEnv* jni_env = 0;  

  vm->AttachCurrentThread(&jni_env, 0);  

这个是从c/c++调用java所必需的。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);                       // return object with a global reference  

通过帮助类来使用java插件

使用AndroidJNIHelper 和AndroidJNI会减轻些使用原始JNI的痛苦。

AndroidJNIHelper 和AndroidJNI自动完成了很多任务(指找到类定义,构造方法等),并且使用缓存使调用java速度更快。AndroidJavaObject和AndroidJavaClass基于AndroidJNIHelper 和AndroidJNI创建,但在处理自动完成部分也有很多自己的逻辑,这些类也有静态的版本,用来访问java类的静态成员。

你可以选择任意你喜欢的方式来替代这种原始JNI的做法,可以通过 AndroidJNI类,也可以通过AndroidJNIHelper和AndroidJNI, 最后也可以使用 AndroidJavaObject/AndroidJavaClass,这样会有最大程度的自动完成和最大的便利性。

Call方法

Get域的值

Set域的值

Call分为两类,调用void方法和调用非void返回类型的方法,会使用一个泛型类型来表示这些非void返回类型的方法的返回类型;Get和Set也经常带一个泛型类型用以表示域的类型。

例子1:

//注释表示是使用原始JNI方法必须做的工作  

 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);  

 AndroidJavaObject的构造方法至少需要一个参数----你想要实例化的类的名称。类名之后的参数会被对象的构造函数所使用,如上例种的字符串“some_string”,随后的对hashCode方法的Call会返回一个int型值,这也是为什么我们会传一个泛型参数给Call方法。

注意:不能使用点.来初始化一个嵌套类型,内部类必须使用$分隔符,在斜线/或点.分隔的类名中都可以使用。所以当类LayoutParams嵌套在ViewGroup类中时,像android.view.ViewGroup$LayoutParams或者android/view/ViewGroup$LayoutParams,这两种方式都是可行的。

例子2:

上面有个插件的例子是说获取当前程序的缓存目录的,下面这个例子直接用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;"); // or any baseclass thereof!   

// jni.CallObjectMethod(objectID, methodID);   

// jni.FindClass("java.io.File");   

// jni.GetMethodID(classID, "getCanonicalPath", "()Ljava/lang/String;");   

// jni.GetStringUTFChars(javaString);  

例子3:

最后,是一个通过UnitySendMessage方法从java代码向脚本代码传递数据的小窍门。

using UnityEngine;   

public class NewBehaviourScript : MonoBehaviour   

{   

<span style="white-space:pre">  </span>void Start ()   

<span style="white-space:pre">  </span>{   

    <span style="white-space:pre">  </span>JNIHelper.debug = true;   

    <span style="white-space:pre">  </span>using (JavaClass jc = new JavaClass("com.unity3d.player.UnityPlayer"))   

<span style="white-space:pre">      </span>{   

<span style="white-space:pre">  </span>     jc.CallStatic("UnitySendMessage", "Main Camera", "JavaMessage", "whoowhoo");   

    <span style="white-space:pre">  </span>}   

    }   

    void JavaMessage(string message)   

        Debug.Log("message from java: " + message);   

    }  

这里我们直接从脚本中调用的,但它确实是在java端发送的消息,它会调回到unity3d的native代码,传递消息到名为"Main Camera"的游戏对象上去,该对象上绑定的某个脚本中包含有名为"JavaMessage"的方法。

在unity3d中使用java插件的最佳实践

这一节主要针对那些没有足够jni,java和android经验的人。假设我们在unity3d中使用AndroidJavaObject/AndroidJavaClass来与java交互。

首先就是要注意对AndroidJavaObject/AndroidJavaClass的任何操作都是很费时的(是通过JNI来进行的)。因此为了代码性能和代码清晰性,我们强烈建议托管代码与native/java代码间的转换次数保持在最小数量。

你可以定义一个java方法完成所有的事情,然后我们通过AndroidJavaObject/AndroidJavaClass来与这个方法通信和获取结果,我们的JNI帮助类会尽可能多的缓存数据已提高性能。

//第一次像这样调用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,你会在log输出中看到垃圾回收器的活动记录。

//获取系统语言的安全方法  

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"));   

        }   

}  

也可以直接调用.Dispose()方法确保没有java对象残留,c#对象会存活长一点,最终还是会被mono的垃圾回收器回收。

继承UnityPlayerActivity java代码

在Unity Android上,我们可以继承标准的UnityPlayerActivity类(android上Unity Player的主要java类,类似于Unity iOS上的AppController.mm)。

继承UnityPlayerActivity的一个例子,OverrideExample.java:

package com.company.product;  

import com.unity3d.player.UnityPlayerActivity;  

import android.os.Bundle;  

import android.util.Log;  

public class OverrideExample extends UnityPlayerActivity {  

  protected void onCreate(Bundle savedInstanceState) {  

    // call UnityPlayerActivity.onCreate()  

    super.onCreate(savedInstanceState);  

    // print debug message to logcat  

    Log.d("OverrideActivity", "onCreate called!");  

  }  

  public void onBackPressed()  

  {  

    // instead of calling UnityPlayerActivity.onBackPressed() we just ignore the back button event  

    // super.onBackPressed();  

相关的AndroidManifest.xml文件如下:

<?xml version="1.0" encoding="utf-8"?>  

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">  

  <application android:icon="@drawable/app_icon" android:label="@string/app_name">  

    <activity android:name=".OverrideExample"  

              android:label="@string/app_name"  

              android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">  

        <intent-filter>  

            <action android:name="android.intent.action.MAIN" />  

            <category android:name="android.intent.category.LAUNCHER" />  

        </intent-filter>  

    </activity>  

  </application>  

</manifest>   

UnityPlayerNativeActivity

同样我们可以创建UnityPlayerNativeActivity的子类,这与创建UnityPlayerActivity的子类具有相同的效果,但是会有较小的输入延迟。但是,需要明白的是,NativeActivity是在Gingerbread中引入的(即android 2.3),老的android版本没有这个特性,因为在NativeActivity中,触摸事件都是在native代码中处理的,java视图正常情况下是无法获取这些事件的,不过在unity3d中,有允许将事件传到DalvikVM的转发机制,要应用这个转发机制,必须修改manifest文件如下:

    <activity android:name=".OverrideExampleNative"  

  <meta-data android:name="android.app.lib_name" android:value="unity" />  

  <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />  

要注意activity元素中的.OverrideExampleNative属性,还有两条meta-data元素,第一条meta-data元素指明使用unity3d库libunity.so,第二条meta-data元素使事件能传递到你创建的UnityPlayerNativeActivity子类中。

例子

native插件例子

java插件例子

本文转蓬莱仙羽51CTO博客,原文链接:http://blog.51cto.com/dingxiaowei/1366208,如需转载请自行联系原作者

继续阅读