- 用户指南
- 资源商店 (Asset Store)
- 资源服务器 (Asset Server)(仅限团队许可证)
- 缓存服务器(仅限团队许可证)
- 幕后场景
- 创建游戏
- 运行时实例化预设 (Prefabs)
- 变换 (Transforms)
- 物理
- 添加随机的游戏元素
- 粒子系统(Particle Systems)
- Mecanim 动画系统
- 旧动画系统
- 导航网格 (Navmesh) 和寻路 (Pathfinding)(仅限专业版 (Pro))
- Sound (音频侦听器)
- 游戏界面元素
- 多玩家联网游戏
- iOS 开发入门
- Android 开发入门
- Blackberry 10 开发入门
- Metro:入门指南
- 本地客户端开发入门
- FAQ
- Advanced
- Vector Cookbook
- 资源包(仅限专业版)
- Graphics Features
- 资源数据库 (AssetDatabase)
- 构建播放器管道
- 分析器(仅限专业版)
- 光照贴图快速入门
- 遮挡剔除(仅限专业版)
- 相机使用技巧
- 运行时加载资源
- 通过脚本修改源资源
- 用程序生成网格几何体
- 富文本
- 在 Unity 工程 (Project) 中使用 Mono DLL
- 事件函数的执行顺序
- 移动优化实用指南
- Unity XCode 工程结构
- 优化图形性能
- 减少文件大小
- 理解自动内存管理
- 平台依赖编译
- 泛型函数
- 调试
- 插件(专业版/移动版特有功能)
- 文本场景文件格式(仅限专业版)
- 流媒体资源
- 启动时运行编辑器脚本代码
- 网络模拟
- VisualStudio C 集成
- 分析
- 检查更新
- 安装多版本 Unity
- 故障排除
- Unity 中的阴影
- Unity 中的 IME
- 对集成显卡进行优化
- 网络播放器 (Web Player) 部署
- 使用网络播放器中的信任链系统
为 Android 构建插件
本页介绍了 Android 本地代码插件。
为 Android 构建插件
若想要为 Android 构建插件, 您首先应该获得 Android NDK,并熟悉包括构建共享库在内的所有步骤。
如果使用 C++ (.cpp) 实现插件,则必须确保所创建的功能使用 C linkage 方式进行声明,以避免出现名称重整问题。
extern "C" { float FooPluginFunction (); }
在 C# 中使用插件
构建之后,共享库将复制到资源 (Assets)->插件 (Plugins)->Android 文件夹。在定义以下 C# 脚本之类的函数时,Unity 将按照名称找到共享库:
[DllImport ("PluginName")] private static extern float FooPluginFunction ();
请注意, 插件名称 (PluginName) 不应包含文件名称的前缀 ('lib') 和后缀 ('.so')。 建议将所有本地代码的方法都包含在一个 C# 代码层中。这个代码层需要检查 Application.platform,并且只有在应用程序在真机上运行时才会调用本地代码方法;但在编辑器中运行时将返回虚值。您还可以使用平台定义控制依赖于平台的代码编译。
部署
对于跨平台部署,您的工程应包含支持不同平台的插件(如 Android 的 libPlugin.so 插件、Mac 的 Plugin.bundle 插件以及 Windows 的 Plugin.dll 插件)。 Unity 将自动为目标平台选择正确的插件,并将其包括在播放器中。
使用 Java 插件
Android 的插件原理也允许使用 Java 与 Android OS 进行交互。
为 Android 构建 Java 插件
构建 Java 插件的方法多种多样,但是每种方法的最终结果都是插件包含 .class 文件的 .jar 文件。 其中一种方法是下载 JDK,然后使用 javac 编译 .java 文件命令行。这将创建一个 .class 文件,然后使用 jar 命令行工具将其打包至 .jar 文件。 另外一种选择时使用 Eclipse IDE 和 ADT。
在本地代码中使用 Java 插件
构建 Java 插件 (.jar) 之后,您应该将其复制至 Unity 工程的资源 (Assets)->插件 (Plugins)->Android 文件夹内。Unity 会将 .class 文件与其余部分 Java 代码打包在一起,然后使用 Java 本地接口 (JNI) 访问代码。JNI 用于从 Java 调用本地代码以及从本地代码与 Java (或 JavaVM)交互。
若要从本地端找到 Java 代码,则必须访问 Java VM。幸运的是,只需在 /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"); // find class definition jmethodID mid_JavaClass = jni_env->GetMethodID (cls_JavaClass, "<init>", "()V"); // find constructor method jobject obj_JavaClass = jni_env->NewObject(cls_JavaClass, mid_JavaClass); // create object instance return jni_env->NewGlobalRef(obj_JavaClass); // return object with a global reference }
使用 helper 类的 Java 插件
AndroidJNIHelper 和 AndroidJNI 可以用来缓解原声 JNI 带来的麻烦。
AndroidJavaObject 和 AndroidJavaClass 可以实现很多任务的自动化,并使用即时缓存更快地调用 Java。AndroidJavaObject 和 AndroidJavaClass 的组合建立在 AndroidJNI 和 AndroidJNIHelper 的基础上,但也也拥有很多自己独有的逻辑(以处理自动化)。这些类也有在'静态'版本中访问 Java 类的部分静态成员。
您可以选择任何您喜欢的方法,无论是通过 AndroidJNI 类的方法使用原生 JNI,或者是 AndroidJNIHelper 与 AndroidJNI 相结合,并在最终使用 AndroidJavaObject/AndroidJavaClass,以达到最大的自动化和便利。
UnityEngine.AndroidJNI 提供 是 C 中可用的 JNI (如上所述)的封装。这个类中的所有方法都是静态方法,并且与 Java 本地接口有 1:1 的映射关系。UnityEngine.AndroidJNIHelper 提供 了下一级所使用的辅助功能,但因为它可能在某些特殊情况下非常有用,因为作为公共方法。
UnityEngine.AndroidJavaObject 和 UnityEngine.AndroidJavaClass 的实例与 Java 一方的 java.lang.Object 和 java.lang.Class(或其子类)分别拥有 1:1 的映射关系。基本上提供 了与 Java 端 3 种类型的交互:
- 调用方法
- 获得字段值
- 设置字段值
调用 (Call) 分为两种类别:调用 'void' 方法,以及调用 non-void 返回类型的方法。泛型类型用来表示这些方法返回 non-void 类型的返回类型。Get 和 Set 始终采用泛型类型表示字段类型。
示例 1
//The comments describe what you would need to do if you were using raw 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);
在此,我们创建了一个 java.lang.String 实例,初始化我们选择的字符串,并检索该字符串的哈希值。
AndroidJavaObject 构造器至少需要一个参数,我们想要构建的实例的类名。在类名称之后的任何参数用于让构造器调用对象,在这个实例中,即字符串 "some_string"。随后调用 hashCode() 返回 “int”,这就是为什么将其作为泛型类型参数的调用方法。
注意:您不能使用句点符号实例化 Java 类。内部类必须使用 $ 分隔符,并能适用于句点和斜线格式。因此可以使用 android.view.ViewGroup$LayoutParams 或 android/view/ViewGroup$LayoutParams,其中 LayoutParams 类是嵌套在 ViewGroup 类中。
示例 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.CallObjectMethod(objectID, methodID); // jni.GetStringUTFChars(javaString);
在这个案例中,我们以 AndroidJavaClass 开始,而非 AndroidJavaObject,因为我们要访问 com.unity3d.player.UnityPlayer 的静态成员,而不是创建新的对象(通过 Android UnityPlayer 自动创建示例)。然后,我们将访问静态字段 "currentActivity",但在这里,我们使用 AndroidJavaObject 作为泛型参数。这是由于实际字段类型 (android.app.Activity) 是 java.lang.Object 的子类, 任何 非初级类型都必须作为 AndroidJavaObject 访问。这一规则的例外是字符串,因为就算字符串不代表 Java 中的初级类比,也可以直接访问。
在这之后仅仅是一个 Activity 遍历的问题,通过 getCacheDir() 获得代表缓存目录的文件目标,然后调用 getCanonicalPath() 获得字符串代表。
当然,您现在不需要为了获得缓存目录这样做,因为 Untiy 提供 了Application.temporaryCachePath 和 Application.persistentDataPath 来访问应用程序的缓存和文件目录。
示例 3
最后,这里是一个使用 UnitySendMessage 从 Java 向本地代码传递数据的技巧。
using UnityEngine; public class NewBehaviourScript : MonoBehaviour { void Start () { AndroidJNIHelper.debug = true; using (AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) { jc.CallStatic("UnitySendMessage", "Main Camera", "JavaMessage", "whoowhoo"); } } void JavaMessage(string message) { Debug.Log("message from java: " + message); } }
Java 类 com.unity3d.player.UnityPlayer 现在有静态方法 UnitySendMessage,相当于 iOS 本地端的 UnitySendMessage。
虽然在这里,我们直接从脚本代码调用,这从根本上说是在 Java 端中继该消息。然后回调到本地 /Unity 代码传递消息给名为 “Main Camera” 对象。这个对象附加了包含叫作 “JavaMessage” 方法的脚本。
在 Unity 中使用 Java 插件的最佳方法
本节主要针对没有全面 JNI、Java 和 Android 经验的用户,我们假设 AndroidJavaObject/AndroidJavaClass 方法已经在 Unity 中用来与 Java 代码交互。
首先要注意的是在 AndroidJavaObject 或 AndroidJavaClass 上执行的任何操作在运算上非常昂贵(原生 JNI 方法)。出于性能和代码简洁等方面考虑,强烈建议您将托管和本地/Java 代码之间的转换数量保持到最低限度。
您应该有一个完成所有实际工作的 Java 方法,然后使用 AndroidJavaObject / AndroidJavaClass 与该方法进行通信,获得结果。需要记住的是,JNI helper 类尝试缓存尽可能多的数据来提高性能。
//The first time you call a Java function like AndroidJavaObject jo = new AndroidJavaObject("java.lang.String", "some_string"); // somewhat expensive int hash = jo.Call<int>("hashCode"); // first time - expensive int hash = jo.Call<int>("hashCode"); // second time - not as expensive as we already know the java method and can call it directly
在使用之后,Mono 垃圾收集器应释放所有 AndroidJavaObject 和 AndroidJavaClass 创建的实例,但仍然建议在 using(){} 语句保留这些实例,以确保尽快删除。不这样操作的话,将不能确保实例何时被销毁。如果设置 AndroidJNIHelper.debug 为 true,您将看到垃圾收集器在调试输出的活动纪录。
//Getting the system language with the safe approach 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 Andriod 可以扩展标准的 UnityPlayerActivity 类 (在 Android 上用于 Unity 播放器的主 Java 类,类似于 Unity iOS 中的 AppController.mm)。
应用程序可以覆盖 Android OS 和 Unity Android 之间的任何基本交互。您可以通过创建从 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。它位于安装文件夹内(通常,Windows 系统为 C:\Program Files\Unity\Editor\Data,Mac 系统为 PlaybackEngines/AndroidPlayer/bin 子文件夹 /Applications/Unity 中)。然后添加 classes.jar 至用来编译新活动 (Activity) 的类路径中。最终的 .class 文件将压缩成 .jar 文件,且位于资源 (Assets)->插件 (Plugins)->Android 文件夹。 由于清单决定要启动哪个活动,因此还必须建立一个新的 AndroidManifest.xml。AndroidManifest.xml 文件也放置在资源 (Assets)->插件 (Plugins)->Android 文件夹。
新活动可能与如下示例类似,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 引入,但在老设备并不能运行。因为 touch/motion 事件在本地代码中处理,Java 视图通常不会看到这些事件。然而,Unity 有一个的转发机制,允许事件传递到 DalvikVM。您必须设置清单文件以开启此功能,如下所示:
<?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=".OverrideExampleNative" android:label="@string/app_name" android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"> <meta-data android:name="android.app.lib_name" android:value="unity" /> <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" /> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
注意,".OverrideExampleNative" 属于活动元素以及两种其他的元数据元素。第一个元数据是使用 Unity 库 libunity.so。第二个元数据允许事件传递至 UnityPlayerNativeActivity 的自定义子类。
示例
本地插件示例
您可以点击此处,查看使用本地代码插件的简单示例
此示例演示如何从 Unity Android 应用程序调用 C 代码。 这个包包括一个显示通过本地插件计算两个值总和的场景。 请注意,您将需要 Android NDK 编译该插件。
Java 插件示例
您可以点击此处,查看使用 Java 代码的示例
此示例演示 Java 代码如何用来与 Android 系统交互,以及 C++ 如何创建 C# 和 Java 之间的桥接。包中的场景显示一个按钮,按照 Android OS 定义,点击此按钮将可以读取应用程序的缓存目录。请注意,您将需要 JDK和 Android NDK 编译此插件。
此处是一个类似的示例,但基于预构建的 JNI 库来打包本地代码到 C#。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论