[unity3d]Unity3D与android交互----构建android插件
原文地址:Building Plugins for Android
为android构建一个插件
要创建一个android插件,首先要有Android NDK并熟悉使用ndk构建共享库的方法。
如果用C++来实现库,必须声明成用C语言的链接方式,以避免Name Mangling问题。
[cpp] view plain copy
- extern"C"
- {
- floatFooPluginFunction();
- }
通过C#脚本使用插件
构建了共享库后,必须把共享库复制到unity3d工程中的Assets->Plugins->Android目录下。(没有该目录的话,自己依次创建。)
当你在unity3d中在C#脚本中定义如下的函数时,unity3d就能通过名称找到共享库
[csharp] view plain copy
- [DllImport("PluginName")]
- privatestaticexternfloatFooPluginFunction();
注意 PluginName 不要包含共享库文件名中的“lib”前缀和“.so”后缀。建议所有的native代码方法都用C#代码封装一层,并在C#代码中检查 Application.platform 变量,以保证只有app运行在正确的设备上时,才去调用native方法。当在Editor环境下运行时,可以在C#代码中返回空值。
当然也可以用平台宏定义的方法,来控制与平台相关的代码的编译。
部署
对于要部署到多个平台的项目,项目工程中必须包含各个平台所需要的插件(例如:libPlugin.so用于android平台,Plugin.bundle用于mac平台,Plugin.dll用于windows平台)。unity3d会自动为目标平台选择正确的插件。
使用java插件
android插件机制同样允许使用java来与android系统进行交互。
为android构建一个java插件
有好几种方法来构建java插件,最终结果都是生成包含.class文件的.jar包。一种方法是下载JDK,在命令行下用javac命令编译,用jar命令打包成jar文件;另一种方法是Eclipse+ADT。
在native代码中使用java插件
构建好了java插件后,将java插件(.jar)复制到unity3d工程中的
[cpp] view plain copy
- jintJNI_OnLoad(JavaVM*vm,void*reserved)
- {
- JNIEnv*jni_env=0;
- vm->AttachCurrentThread(&jni_env,0);
- }
这个是从c/c++调用java所必需的。JNI超越了本文档的范畴,不做详细解释。通常情况下,先找到类的定义,然后解析类的构造方法(<init>)并创建类的实例,如下面例子所示:
[cpp] view plain copy
- jobjectcreateJavaObject(JNIEnv*jni_env)
- {
- jclasscls_JavaClass=jni_env->FindClass("com/your/java/Class");//找到类定义
- jmethodIDmid_JavaClass=jni_env->GetMethodID(cls_JavaClass,"<init>","()V");//找到构造方法
- jobjectobj_JavaClass=jni_env->NewObject(cls_JavaClass,mid_JavaClass);//创建对象实例
- returnjni_env->NewGlobalRef(obj_JavaClass);//returnobjectwithaglobalreference
- }
通过帮助类来使用java插件
[cpp] view plain copy
- //注释表示是使用原始JNI方法必须做的工作
- AndroidJavaObjectjo=newAndroidJavaObject("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);
- inthash=jo.Call<int>("hashCode");
- //jni.GetMethodID(classID,"hashCode","()I");
- //jni.CallIntMethod(objectID,methodID);
some_string
android.view.ViewGroup$LayoutParams或者android/view/ViewGroup$LayoutParams,这两种方式都是可行的。
上面有个插件的例子是说获取当前程序的缓存目录的,下面这个例子直接用c#代码做同样的事情,而不需要任何插件:
[csharp] view plain copy
- AndroidJavaClassjc=newAndroidJavaClass("com.unity3d.player.UnityPlayer");
- //jni.FindClass("com.unity3d.player.UnityPlayer");
- AndroidJavaObjectjo=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;");//oranybaseclassthereof!
- //jni.CallObjectMethod(objectID,methodID);
- //jni.FindClass("java.io.File");
- //jni.GetMethodID(classID,"getCanonicalPath","()Ljava/lang/String;");
- //jni.CallObjectMethod(objectID,methodID);
- //jni.GetStringUTFChars(javaString);
这个例子中,我们没有首先使用AndroidJavaObject,而是AndroidJavaClass,因为我们想获取类com.unity3d.player.UnityPlayer的一个静态成员,而不是去创建一个新的对象(Android UnityPlayer会自动创建一个实例)。我们访问其静态域"currentActivity" ,这个时候我们用的是AndroidJavaObject作为泛型参数,这是因为实际类型(android.app.Activity)是类java.lang.Object的子类,任意非基本类型都必须作为AndroidJavaObject来访问。有一个例外就是字符串,字符串可以直接访问,尽管它在java中并不是基本类型。
之后就是调用的Activity的getCacheDir()得到缓存目录的文件对象,再调用getCanonicalPath()方法获取缓存目录路径的字符串表示。
当然,现在已经不需要通过这种方式来获取缓存目录了,因为unity3d提供了接口用以访问程序的缓存目录和数据目录,也就是Application.temporaryCachePathandApplication.persistentDataPath。
例子3:
最后,是一个通过UnitySendMessage方法从java代码向脚本代码传递数据的小窍门。
[csharp] view plain copy- usingUnityEngine;
- publicclassNewBehaviourScript:MonoBehaviour
- {
- <spanstyle="white-space:pre"></span>voidStart()
- <spanstyle="white-space:pre"></span>{
- <spanstyle="white-space:pre"></span>JNIHelper.debug=true;
- <spanstyle="white-space:pre"></span>using(JavaClassjc=newJavaClass("com.unity3d.player.UnityPlayer"))
- <spanstyle="white-space:pre"></span>{
- <spanstyle="white-space:pre"></span>jc.CallStatic("UnitySendMessage","MainCamera","JavaMessage","whoowhoo");
- <spanstyle="white-space:pre"></span>}
- }
- voidJavaMessage(stringmessage)
- <spanstyle="white-space:pre"></span>{
- Debug.Log("messagefromjava:"+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帮助类会尽可能多的缓存数据已提高性能。
[csharp] view plain copy
- //第一次像这样调用java函数
- AndroidJavaObjectjo=newAndroidJavaObject("java.lang.String","some_string");//有点费时
- inthash=jo.Call<int>("hashCode");//第一次-费时
- inthash=jo.Call<int>("hashCode");//第二次-不那么费时,因为我们已经知道了这个java方法,可以直接调用它。
在使用过后,Mono垃圾回收器会释放所有创建的AndroidJavaObject和AndroidJavaClass实例,但我们还是建议把它们放到using(){}块中,以保证它们能被尽快的清除掉。除此之外,你无法保证它们会被销毁掉。如果你设置了AndroidJNIHelper.debug为true,你会在log输出中看到垃圾回收器的活动记录。 [csharp] view plain copy
- //获取系统语言的安全方法
- voidStart()
- {
- using(AndroidJavaClasscls=newAndroidJavaClass("java.util.Locale"))
- <spanstyle="white-space:pre"></span>{
- using(AndroidJavaObjectlocale=cls.CallStatic<AndroidJavaObject>("getDefault"))
- <spanstyle="white-space:pre"></span>{
- Debug.Log("currentlang="+locale.Call<string>("getDisplayLanguage"));
- }
- }
- }
继承UnityPlayerActivity java代码
在Unity Android上,我们可以继承标准的UnityPlayerActivity类(android上Unity Player的主要java类,类似于Unity iOS上的AppController.mm)。
应用程序可以覆写android系统与Unity Android之间的任意交互方法,只要新建一个Activity继承UnityPlayerActivity就可以实现。(在mac系统上,UnityPlayerActivity.java在/Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player目录下;在windows系统中,它通常在C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player目录下)
首先定位Unity Android的classes.jar文件,可以在Unity3d的安装目录(windows下通常是C:\Program Files\Unity\Editor\Data,mac下是/Assets->Plugins->Android目录下。 )下的子文件夹PlaybackEngines/AndroidPlayer/bin中找到,将它添加到你编译activity的classpath中。最终编译出来的.class文件,需要打包成.jar文件,放到工程中的Assets->Plugins->Android目录下。因为android中manifest文件指明了启动哪个Activity,因此我们也需要重新写一个AndroidManifest.xml文件,也需要将它放到
继承UnityPlayerActivity的一个例子,OverrideExample.java:
[java] view plain copy
- packagecom.company.product;
- importcom.unity3d.player.UnityPlayerActivity;
- importandroid.os.Bundle;
- importandroid.util.Log;
- publicclassOverrideExampleextendsUnityPlayerActivity{
- protectedvoidonCreate(BundlesavedInstanceState){
- //callUnityPlayerActivity.onCreate()
- super.onCreate(savedInstanceState);
- //printdebugmessagetologcat
- Log.d("OverrideActivity","onCreatecalled!");
- }
- publicvoidonBackPressed()
- {
- //insteadofcallingUnityPlayerActivity.onBackPressed()wejustignorethebackbuttonevent
- //super.onBackPressed();
- }
- }
相关的AndroidManifest.xml文件如下:
[html] view plain copy
- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.company.product">
- <applicationandroid:icon="@drawable/app_icon"android:label="@string/app_name">
- <activityandroid:name=".OverrideExample"
- android:label="@string/app_name"
- android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid: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文件如下:
[html] view plain copy
- <?xmlversion="1.0"encoding="utf-8"?>
- <manifestxmlns:android="http://schemas.android.com/apk/res/android"package="com.company.product">
- <applicationandroid:icon="@drawable/app_icon"android:label="@string/app_name">
- <activityandroid:name=".OverrideExampleNative"
- android:label="@string/app_name"
- android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">
- <meta-dataandroid:name="android.app.lib_name"android:value="unity"/>
- <meta-dataandroid:name="unityplayer.ForwardNativeEventsToDalvik"android:value="true"/>
- <intent-filter>
- <actionandroid:name="android.intent.action.MAIN"/>
- <categoryandroid:name="android.intent.category.LAUNCHER"/>
- </intent-filter>
- </activity>
- </application>
- </manifest>
要注意activity元素中的.OverrideExampleNative属性,还有两条meta-data元素,第一条meta-data元素指明使用unity3d库libunity.so,第二条meta-data元素使事件能传递到你创建的UnityPlayerNativeActivity子类中。
例子
native插件例子
这里有一个简单的使用native插件的例子。
这个例子演示了如果从unity3d android程序中来调用c代码,包中包含了一个通过native插件计算出来的两个数之和的场景,要注意,你必须用Android NDK来编译这个插件。
java插件例子
这里有一个简单的使用java代码的例子。
这个例子演示了怎么用java代码与android系统进行交互,以及如何用c++来将c#和java沟通起来,包中的场景显示了一个按钮,点击该按钮,会显示出程序在android系统中的缓存目录路径。需要JDK和Android NDK来编译这个插件。
这里有一个相似的例子,但是是基于预先编译好的JNI库,来封装native代码,供c#调用。
更多相关文章
- Android(安卓)-- 利用ContentProvider 读取和写入短信
- android画图-----DensityActivity 添加view
- Android-- Dialog对话框的使用方法
- Android开发--玩转WebView
- android 文件操作方法集合类分享
- Android(安卓)app项目和开发总结
- Android里遇到Debug能用,Release不能用的API的问题
- Android(安卓)Databinding:再见Presenter,你好ViewModel
- Recovery模式的命令行参数