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

简介:

原文地址:Building Plugins for Android


为android构建一个插件


要创建一个android插件,首先要有 Android NDK 并熟悉使用ndk构建共享库的方法。

如果用C++来实现库,必须声明成用C语言的链接方式,以避免Name Mangling问题。

[cpp]  view plain copy
  1. extern "C"   
  2. {  
  3.   float FooPluginFunction ();  
  4. }   


通过C#脚本使用插件

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

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

[csharp]  view plain copy
  1. [DllImport ("PluginName")]  
  2. private static extern float FooPluginFunction ();   


注意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工程中的Assets->Plugins->Android文件夹下面,unity3d会将你的.class文件和其余的java代码打包,并通过Java Native Interface (JNI)来访问这些代码。JNI既可以用于java代码调用native代码,也可用于native代码与java(java虚拟机)的交互。


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

[cpp]  view plain copy
  1. jint JNI_OnLoad(JavaVM* vm, void* reserved)   
  2. {  
  3.   JNIEnv* jni_env = 0;  
  4.   vm->AttachCurrentThread(&jni_env, 0);  
  5. }   


这个是从c/c++调用java所必需的。JNI超越了本文档的范畴,不做详细解释。通常情况下,先找到类的定义,然后解析类的构造方法(<init>)并创建类的实例,如下面例子所示:

[cpp]  view plain copy
  1. jobject createJavaObject(JNIEnv* jni_env)   
  2. {  
  3.   jclass cls_JavaClass = jni_env->FindClass("com/your/java/Class");          // 找到类定义  
  4.   jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>",  "()V");        // 找到构造方法  
  5.   jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass);      // 创建对象实例  
  6.   return jni_env->NewGlobalRef(obj_JavaClass);                       // return object with a global reference  
  7. }   


通过帮助类来使用java插件

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


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


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


UnityEngine.AndroidJNI是对那些c代码可用的JNI调用的封装,该类中的所有方法都是静态的并且一一对应到JNI。

UnityEngine.AndroidJNIHelper通过public方法提供了一些不常用的辅助功能,在某些特殊情况下会比较有用处。


在java端,UnityEngine.AndroidJavaObjectUnityEngine.AndroidJavaClass的实例分别一一对应于 java.lang.Object和java.lang.Class (或它的子类)的实例。它们提供了3种与java端交互的方法:

Call方法 Get域的值 Set域的值

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


例子1:

[cpp]  view plain copy
  1. //注释表示是使用原始JNI方法必须做的工作  
  2.  AndroidJavaObject jo = new AndroidJavaObject("java.lang.String""some_string");   
  3.  // jni.FindClass("java.lang.String");   
  4.  // jni.GetMethodID(classID, "<init>", "(Ljava/lang/String;)V");   
  5.  // jni.NewStringUTF("some_string");   
  6.  // jni.NewObject(classID, methodID, javaString);   
  7.  int hash = jo.Call<int>("hashCode");   
  8.  // jni.GetMethodID(classID, "hashCode", "()I");   
  9.  // jni.CallIntMethod(objectID, methodID);  
这个例子中,我们创建了一个  java.lang.String的实例,并用我们自定义的一个字符串初始化它,最后我们得到该字符串的哈希值。


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


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


例子2:

上面有个插件的例子是说获取当前程序的缓存目录的,下面这个例子直接用c#代码做同样的事情,而不需要任何插件:

[csharp]  view plain copy
  1. AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer");   
  2. // jni.FindClass("com.unity3d.player.UnityPlayer");   
  3. AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity");   
  4. // jni.GetStaticFieldID(classID, "Ljava/lang/Object;");   
  5. // jni.GetStaticObjectField(classID, fieldID);   
  6. // jni.FindClass("java.lang.Object");   
  7.   
  8. Debug.Log(jo.Call<AndroidJavaObject>("getCacheDir").Call<string>("getCanonicalPath"));   
  9. // jni.GetMethodID(classID, "getCacheDir", "()Ljava/io/File;"); // or any baseclass thereof!   
  10. // jni.CallObjectMethod(objectID, methodID);   
  11. // jni.FindClass("java.io.File");   
  12. // jni.GetMethodID(classID, "getCanonicalPath", "()Ljava/lang/String;");   
  13. // jni.CallObjectMethod(objectID, methodID);   
  14. // 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.temporaryCachePath and Application.persistentDataPath


例子3:

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

[csharp]  view plain copy
  1. using UnityEngine;   
  2. public class NewBehaviourScript : MonoBehaviour   
  3. {   
  4.   
  5. <span style="white-space:pre">  </span>void Start ()   
  6. <span style="white-space:pre">  </span>{   
  7.     <span style="white-space:pre">  </span>JNIHelper.debug = true;   
  8.     <span style="white-space:pre">  </span>using (JavaClass jc = new JavaClass("com.unity3d.player.UnityPlayer"))   
  9. <span style="white-space:pre">      </span>{   
  10. <span style="white-space:pre">  </span>     jc.CallStatic("UnitySendMessage""Main Camera""JavaMessage""whoowhoo");   
  11.     <span style="white-space:pre">  </span>}   
  12.     }   
  13.   
  14.     void JavaMessage(string message)   
  15. <span style="white-space:pre">  </span>{   
  16.         Debug.Log("message from java: " + message);   
  17.     }  
  18. }   


com.unity3d.player.UnityPlayer现在有一个静态方法 UnitySendMessage,与iOS中native端的 UnitySendMessage一样,可用来在java中向脚本传递数据。


这里我们直接从脚本中调用的,但它确实是在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
  1. //第一次像这样调用java函数  
  2. AndroidJavaObject jo = new AndroidJavaObject("java.lang.String""some_string");  // 有点费时  
  3. int hash = jo.Call<int>("hashCode");  //第一次 - 费时  
  4. int hash = jo.Call<int>("hashCode");  // 第二次 - 不那么费时, 因为我们已经知道了这个java方法,可以直接调用它。  

在使用过后,Mono垃圾回收器会释放所有创建的 AndroidJavaObject AndroidJavaClass实例,但我们还是建议把它们放到using(){}块中,以保证它们能被尽快的清除掉。除此之外,你无法保证它们会被销毁掉。如果你设置了 AndroidJNIHelper.debug为true,你会在log输出中看到垃圾回收器的活动记录。

[csharp]  view plain copy
  1. //获取系统语言的安全方法  
  2. void Start ()   
  3. {   
  4.     using (AndroidJavaClass cls = new AndroidJavaClass("java.util.Locale"))   
  5. <span style="white-space:pre">  </span>{   
  6.         using(AndroidJavaObject locale = cls.CallStatic<AndroidJavaObject>("getDefault"))   
  7. <span style="white-space:pre">      </span>{   
  8.             Debug.Log("current lang = " + locale.Call<string>("getDisplayLanguage"));   
  9.   
  10.         }   
  11.     }   
  12. }  


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

继承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下是/Applications/Unity)下的子文件夹PlaybackEngines/AndroidPlayer/bin中找到,将它添加到你编译activity的classpath中。最终编译出来的.class文件,需要打包成.jar文件,放到工程中的Assets->Plugins->Android目录下。因为android中manifest文件指明了启动哪个Activity,因此我们也需要重新写一个AndroidManifest.xml文件,也需要将它放到Assets->Plugins->Android目录下。


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

[java]  view plain copy
  1. package com.company.product;  
  2.   
  3. import com.unity3d.player.UnityPlayerActivity;  
  4.   
  5. import android.os.Bundle;  
  6. import android.util.Log;  
  7.   
  8. public class OverrideExample extends UnityPlayerActivity {  
  9.   
  10.   protected void onCreate(Bundle savedInstanceState) {  
  11.   
  12.     // call UnityPlayerActivity.onCreate()  
  13.     super.onCreate(savedInstanceState);  
  14.   
  15.     // print debug message to logcat  
  16.     Log.d("OverrideActivity""onCreate called!");  
  17.   }  
  18.   
  19.   public void onBackPressed()  
  20.   {  
  21.     // instead of calling UnityPlayerActivity.onBackPressed() we just ignore the back button event  
  22.     // super.onBackPressed();  
  23.   }  
  24. }   

相关的AndroidManifest.xml文件如下:

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">  
  3.   <application android:icon="@drawable/app_icon" android:label="@string/app_name">  
  4.     <activity android:name=".OverrideExample"  
  5.               android:label="@string/app_name"  
  6.               android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">  
  7.         <intent-filter>  
  8.             <action android:name="android.intent.action.MAIN" />  
  9.             <category android:name="android.intent.category.LAUNCHER" />  
  10.         </intent-filter>  
  11.     </activity>  
  12.   </application>  
  13. </manifest>   


UnityPlayerNativeActivity

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

[html]  view plain copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.company.product">  
  3.   <application android:icon="@drawable/app_icon" android:label="@string/app_name">  
  4.     <activity android:name=".OverrideExampleNative"  
  5.               android:label="@string/app_name"  
  6.               android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen">  
  7.   <meta-data android:name="android.app.lib_name" android:value="unity" />  
  8.   <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />  
  9.         <intent-filter>  
  10.             <action android:name="android.intent.action.MAIN" />  
  11.             <category android:name="android.intent.category.LAUNCHER" />  
  12.         </intent-filter>  
  13.     </activity>  
  14.   </application>  
  15. </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#调用。





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

相关文章
|
13天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
14天前
|
数据库 Android开发 开发者
构建高效Android应用:Kotlin协程的实践指南
【4月更文挑战第2天】随着移动应用开发的不断进步,开发者们寻求更流畅、高效的用户体验。在Android平台上,Kotlin语言凭借其简洁性和功能性赢得了开发社区的广泛支持。特别是Kotlin协程,作为一种轻量级的并发处理方案,使得异步编程变得更加简单和直观。本文将深入探讨Kotlin协程的核心概念、使用场景以及如何将其应用于Android开发中,以提高应用性能和响应能力。通过实际案例分析,我们将展示协程如何简化复杂任务,优化资源管理,并为最终用户提供更加流畅的体验。
|
17天前
|
缓存 监控 Java
构建高效Android应用:从优化用户体验到提升性能
在竞争激烈的移动应用市场中,为用户提供流畅和高效的体验是至关重要的。本文深入探讨了如何通过多种技术手段来优化Android应用的性能,包括UI响应性、内存管理和多线程处理。同时,我们还将讨论如何利用最新的Android框架和工具来诊断和解决性能瓶颈。通过实例分析和最佳实践,读者将能够理解并实施必要的优化策略,以确保他们的应用在保持响应迅速的同时,还能够有效地利用系统资源。
|
22天前
|
编解码 算法 Java
构建高效的Android应用:内存优化策略详解
随着智能手机在日常生活和工作中的普及,用户对移动应用的性能要求越来越高。特别是对于Android开发者来说,理解并实践内存优化是提升应用程序性能的关键步骤。本文将深入探讨针对Android平台的内存管理机制,并提供一系列实用的内存优化技巧,以帮助开发者减少内存消耗,避免常见的内存泄漏问题,并确保应用的流畅运行。
|
14天前
|
Java Android开发 开发者
构建高效Android应用:Kotlin协程的实践与优化
在响应式编程范式日益盛行的今天,Kotlin协程作为一种轻量级的线程管理解决方案,为Android开发带来了性能和效率的双重提升。本文旨在探讨Kotlin协程的核心概念、实践方法及其在Android应用中的优化策略,帮助开发者构建更加流畅和高效的应用程序。通过深入分析协程的原理与应用场景,结合实际案例,本文将指导读者如何优雅地解决异步任务处理,避免阻塞UI线程,从而优化用户体验。
|
20天前
|
Java 编译器 Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
在开发高性能的Android应用时,选择合适的编程语言至关重要。近年来,Kotlin因其简洁性和功能性受到开发者的青睐,但其性能是否与传统的Java相比有所不足?本文通过对比分析Kotlin与Java在Android平台上的运行效率,揭示二者在编译速度、运行时性能及资源消耗方面的具体差异,并探讨在实际项目中如何做出最佳选择。
16 4
|
8天前
|
监控 API Android开发
构建高效安卓应用:探究Android 12中的新特性与性能优化
【4月更文挑战第8天】 在本文中,我们将深入探讨Android 12版本引入的几项关键技术及其对安卓应用性能提升的影响。不同于通常的功能介绍,我们专注于实际应用场景下的性能调优实践,以及开发者如何利用这些新特性来提高应用的响应速度和用户体验。文章将通过分析内存管理、应用启动时间、以及新的API等方面,为读者提供具体的技术实现路径和代码示例。
|
8天前
|
移动开发 API Android开发
构建高效Android应用:探究Kotlin协程的优势与实践
【4月更文挑战第7天】 在移动开发领域,性能优化和应用响应性的提升一直是开发者追求的目标。近年来,Kotlin语言因其简洁性和功能性在Android社区中受到青睐,特别是其对协程(Coroutines)的支持,为编写异步代码和处理并发任务提供了一种更加优雅的解决方案。本文将探讨Kotlin协程在Android开发中的应用,揭示其在提高应用性能和简化代码结构方面的潜在优势,并展示如何在实际项目中实现和优化协程。
|
8天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
22天前
|
移动开发 Android开发 开发者
构建高效Android应用:探究Kotlin协程的优势
在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的核心目标。近年来,Kotlin语言凭借其简洁性和功能性成为Android开发的新宠,特别是Kotlin协程的引入,为编写异步代码提供了一种全新的范式。本文将深入探讨Kotlin协程在Android应用开发中的应用及其带来的优势,旨在帮助开发者理解并运用协程来提高应用的性能和响应性。