找回密码
 注册帐号

扫一扫,访问微社区

为 android 构建插件

2015-1-8 18:06| 发布者: 杨炎| 查看: 3257| 评论: 0|原作者: 蛮牛|来自: unity3d脚本manual

摘要: 为 android 构建插件本页介绍了 android本地代码插件。为 android 构建插件若想要为 android 构建插件, 您首先应该获得android ndk,并熟悉包括构建共享库在内的所有步骤。如果使用 c++ (.cpp) 实现插件,则必须确保 ...

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 文件。 另外一种选择时使用eclipseide 和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, "", "()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 插件

androidjnihelperandroidjni可以用来缓解原声 jni 带来的麻烦。

androidjavaobjectandroidjavaclass可以实现很多任务的自动化,并使用即时缓存更快地调用 java。androidjavaobjectandroidjavaclass的组合建立在androidjniandroidjnihelper的基础上,但也也拥有很多自己独有的逻辑(以处理自动化)。这些类也有在'静态'版本中访问 java 类的部分静态成员。

您可以选择任何您喜欢的方法,无论是通过androidjni类的方法使用原生 jni,或者是androidjnihelperandroidjni相结合,并在最终使用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 类型的返回类型。getset始终采用泛型类型表示字段类型。

示例 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, "", "(ljava/lang/string;)v");

// jni.newstringutf("some_string");

// jni.newobject(classid, methodid, javastring);

int hash = jo.call("hashcode");

// jni.getmethodid(classid, "hashcode", "()i");

// jni.callintmethod(objectid, methodid);

在此,我们创建了一个java.lang.string实例,初始化我们选择的字符串,并检索该字符串的哈希值。

androidjavaobject构造器至少需要一个参数,我们想要构建的实例的类名。在类名称之后的任何参数用于让构造器调用对象,在这个实例中,即字符串 "some_string"。随后调用hashcode() 返回 “int”,这就是为什么将其作为泛型类型参数的调用方法。

注意:您不能使用句点符号实例化 java 类。内部类必须使用 $ 分隔符,并能适用于句点和斜线格式。因此可以使用android.view.viewgroup$layoutparamsandroid/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("currentactivity");

// jni.getstaticfieldid(classid, "ljava/lang/object;");

// jni.getstaticobjectfield(classid, fieldid);

// jni.findclass("java.lang.object");

debug.log(jo.call("getcachedir").call("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 代码交互。

首先要注意的是在androidjavaobjectandroidjavaclass上执行的任何操作在运算上非常昂贵(原生 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("hashcode"); // first time - expensive

int hash = jo.call("hashcode"); // second time - not as expensive as we already know the java method and can call it directly

在使用之后,mono 垃圾收集器应释放所有androidjavaobjectandroidjavaclass创建的实例,但仍然建议在 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("getdefault")) {

debug.log("current lang = " + locale.call("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,如下所示:

android:label="@string/app_name"

android:configchanges="fontscale|keyboard|keyboardhidden|locale|mnc|mcc|navigation|orientation|screenlayout|screensize|smallestscreensize|uimode|touchscreen">

unityplayernativeactivity

您也可以创建自己的unityplayernativeactivity子类。这与 unityplayeractivity 子类化具有相同的效果,但改善了输入延迟。但还是要注意,nativeactivity 在 gingerbread 引入,但在老设备并不能运行。因为 touch/motion 事件在本地代码中处理,java 视图通常不会看到这些事件。然而,unity 有一个的转发机制,允许事件传递到 dalvikvm。您必须设置清单文件以开启此功能,如下所示:

android:label="@string/app_name"

android:configchanges="fontscale|keyboard|keyboardhidden|locale|mnc|mcc|navigation|orientation|screenlayout|screensize|smallestscreensize|uimode|touchscreen">

注意,".overrideexamplenative" 属于活动元素以及两种其他的元数据元素。第一个元数据是使用 unity 库libunity.so。第二个元数据允许事件传递至 unityplayernativeactivity 的自定义子类。

示例

本地插件示例

您可以点击此处,查看使用本地代码插件的简单示例

此示例演示如何从 unity android 应用程序调用 c 代码。 这个包包括一个显示通过本地插件计算两个值总和的场景。 请注意,您将需要android ndk编译该插件。

java 插件示例

您可以点击此处,查看使用 java 代码的示例

此示例演示 java 代码如何用来与 android 系统交互,以及 c++ 如何创建 c# 和 java 之间的桥接。包中的场景显示一个按钮,按照 android os 定义,点击此按钮将可以读取应用程序的缓存目录。请注意,您将需要 jdk和android ndk编译此插件。

此处是一个类似的示例,但基于预构建的 jni 库来打包本地代码到 c#。

相关阅读

文章点评
相关文章