На этой странице описан нативный код плагинов для Android.
Чтобы построить плагин для Android, вы должны сначала получить Android NDK и ознакомиться с инструкцией по созданию общей библиотеки.
Если вы используете C++ (.cpp), при создании плагина вы должны убедиться, что функции объявлены с C-связями, чтобы избежать проблем с коверканьем имен.
extern "C" {
float FooPluginFunction ();
}
После сборки, общая библиотека должна быть скопированы в папку
. Unity будет искать её по имени, когда вы определяете функцию вроде следующей в C#-скрипте:-[DllImport ("PluginName")]
private static extern float FooPluginFunction ();
Обратите внимание, что PluginName не должно включать ни префикс (‘Lib’), ни расширение (‘.so’). Вы должны обернуть все нативные методы кода дополнительным слоем C#-кода. Этот код должен проверить Application.platform и вызывать собственные методы только тогда, когда приложение работает на реальном устройстве; фиктивные значения могут быть возвращены из С#-кода при работе в редакторе. Вы также можете использовать определения платформы для контроля зависимости компиляции кода от платформы.
Вы можете закинуть проекты прекомпилированной Android библиотеки в папку
. Прекомпилированная - значит все .java файлы должны быть скомпилированы в jar файлы, расположенные либо в папке bin/, либо в папке libs/ проекта. AndroidManifest.xml из этих папок будет автоматически объединяться с основным файлом манифеста при сборке проекта.Смотрите Проекты библиотек для Android для большей информации.
Для кросс-платформенного развертывания, ваш проект должен включать плагины для каждой поддерживаемой платформы (т.е. libPlugin.so для Android, Plugin.bundle для Mac и Plugin.dll для Windows). Unity автоматически выбирает правильный плагин для целевой платформы и включает его с плеером.
For specific Android platform (armv7, x86), the libraries (lib*.so) should be placed in the following:
Assets/Plugins/Android/libs/x86/
Assets/Plugins/Android/libs/armeabi-v7a/
Механизм Android-плагина также обеспечивает Java, который будет использоваться для взаимодействия с ОС Android.
Есть несколько способов создать плагин Java, но результатом во всех случаях является файл .jar, содержащий файлы .class для вашего плагина. Один из подходов - скачать JDK, затем скомпилировать файлы .java из командной строки с javac. Это создаст файлы .class, которые затем можно упаковать в .jar из командной строки с утилитой jar. Другой вариант заключается в использовании Eclipse IDE вместе с ADT.
Примечание: Unity ожидает Java-плагины, которые будут построены с использованием JDK v1.6. Если вы используете v1.7, вы должны включить “-source 1,6 -target 1,6” в параметрах командной строки для компилятора.
После того как вы создали свой Java-плагин (.jar), вы должны скопировать его в папку Java Native Interface (JNI). JNI используется как при вызове нативного кода из Java, так и при взаимодействии с Java (или JavaVM) из нативного кода.
в проекте Unity. Unity упакует ваши файлы .class вместе с остальной частью Java-кода, а затем получить доступ к коду, используяЧтобы найти Java код из нативной части, вы должны иметь доступ к Java VM. К счастью, доступ можно легко получить, добавив такую функцию в свой C/C++ код:
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* jni_env = 0;
vm->AttachCurrentThread(&jni_env, 0);
return JNI_VERSION_1_6;
}
Это все, что необходимо, чтобы начать использовать Java из C/C++. Полное объяснение 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
}
AndroidJNIHelper и AndroidJNI могут быть использованы для избавления от некоторых проблем с первичным JNI.
AndroidJavaObject и AndroidJavaClass автоматизируют множество задач и используют кэширование для ускорения вызовов к Java. Комбинация AndroidJavaObject и AndroidJavaClass надстраивается над AndroidJNI и AndroidJNIHelper, но также содержит много логики сама по себе (для управления автоматизацией). Эти классы также идут в ‘static’ версии для доступа к статическим членам Java классов.
Вы можете выбрать в зависимости от того, что вам подходит, будь то первичный JNI с помощью методов класса AndroidJNI, или AndroidJNIHelper вместе с AndroidJNI, и в конце концов AndroidJavaObject/AndroidJavaClass для максимальной автоматизации и удобства.
UnityEngine.AndroidJNI является оберткой для вызовов JNI доступных в C (как описано выше). Все методы этого класса являются статическими и соответствуют 1:1 Java Native Interface. UnityEngine.AndroidJNIHelper предоставляет вспомогательный модуль, используемый на следующем уровне, но предоставляется как открытые методы, потому что они могут быть полезны для некоторых частных случаев.
Экземпляры UnityEngine.AndroidJavaObject и UnityEngine.AndroidJavaClass соответствуют один-в-один экземпляру java.lang.Object и java.lang.Class (или их подклассов) на стороне Java, соответственно. Они по существу обеспечивают 3 типа взаимодействия с Java:-
Call(Вызов) разделяется на две категории: Call(Вызов) ‘void’ метода, и Call(Вызов) метода, возвращающего не-void тип. Универсальный тип используется для представления типа возвращаемого методами, которые возвращают не-void тип. Get и Set всегда берут универсальный тип, представляющий тип поля.
//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”. Последующий Call(Вызов) метода hashCode() возвращает ‘int’, который мы используем в качестве параметра универсального типа в Call(Вызове) метода.
Примечание: Вы не можете создать экземпляр вложенного класса Java с помощью разделения точкой. Внутренние классы должны использовать разделитель $, и это должно работать в точечном и слэш формате. Так \[android.view.ViewGroup$LayoutParams или android/view/ViewGroup$LayoutParams могут быть использованы, где LayoutParams класс вложенный в класс ViewGroup\].
Один из примеров плагина выше показывал, как получить директорию кэша для текущего приложения. Вот, как вы могли бы сделать то же самое на 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() для получения объекта File, представляющего папку кэша с последующим вызовом getCanonicalPath() для получения строкового представления.
Конечно, в настоящее время вам не нужно делать этого, чтобы получить каталог кэша, поскольку Unity предоставляет доступ к директории кэша и файлов приложения с Application.temporaryCachePath и Application.persistentDataPath.
Наконец, здесь есть уловка для передачи данных из Java в код скрипта с помощью UnitySendMessage.
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, что эквивалентно функции UnitySendMessage в нативной части iOS. Он может быть использован в Java для передачи данных в код скрипта.
Здесь, однако, мы вызываем непосредственно из кода скрипта, который по сути передает сообщение Java. Это посылает обратный вызов нативному/Unity коду, чтобы доставить сообщение объекту с именем “Main Camera”. Этот объект имеет скрипт, который содержит метод, называемый “JavaMessage”.
Так как этот раздел в основном ориентирован на людей, которые не имеют большого опыта работы с JNI, Java и Android, мы предполагаем, что подход с AndroidJavaObject/AndroidJavaClass был использован для взаимодействия с кодом Java из Unity.
Первое, что нужно отметить, это то, что любая операция выполненная с AndroidJavaObject или AndroidJavaClass дорогая в отношении производительности (как первичный JNI подход). Крайне желательно, чтобы количество переходов между управляемым и нативным/Java кодом было минимальным, ради производительности, а также ясности кода.
У вас может быть метод Java, чтобы сделать всю фактическую работу, а затем использовать AndroidJavaObject/AndroidJavaClass для взаимодействия с этим методом и получения результата. Однако стоит иметь в виду, что вспомогательные классы JNI попробуют кэшировать столько данных, сколько возможно, чтобы улучшить производительность.
//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.
С Unity Android можно расширить стандартный класс UnityPlayerActivity (основной класс Java для Unity плеера на Android, аналогично AppController.mm на Unity iOS).
Приложение может переопределить любые и все основные взаимодействия между ОС Android и Unity Android. Вы можете cделать это, создав новую Activity, которая является производной от UnityPlayerActivity(UnityPlayerActivity.java можно найти в на Mac и обычно в на Windows).
Чтобы сделать это, сначала найдите AndroidManifest.xml. Файл AndroidManifest.xml также должен быть помещен в папку (размещение пользовательского манифеста полностью заменяет манифест Unity Android по умолчанию).
, поставляемый с Unity Android. Он находится в папке установки (обычно (на Windows) или (на Mac)) в подпапке . Затем добавить в папку с классами, используемыми для компиляции новой Activity. Полученный(-ые) .class файл(ы) должны быть сжаты в .jar файл, а этот файл надо добавить в папку . Поскольку манифест определяет запуск Activity, его также необходимо создать новыйНовая Activity может выглядеть, как в следующем примере 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
. Это будет иметь такой же эффект, как создать подкласс UnityPlayerActivity но с улучшенной задержкой ввода. Помните, однако, что NativeActivity был введен в Gingerbread и не работает со старыми устройствами. Поскольку события касания/движения обрабатываются в нативном коде, окна 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” в элементе Activity и два дополнительных элемента мета-данных. Первый элемент мета-данных является инструкцией по использованию библиотеки libunity.so для Unity. Второй обеспечивает события, которые будут переданы пользовательскому подклассу UnityPlayerNativeActivity.
Простой пример использования кода нативного плагина можно найти здесь
В этом примере демонстрируется как код С может быть вызван из приложения Unity Android. Пакет содержит сцену, которая отображает сумму двух значений, рассчитанную нативным плагином. Пожалуйста, обратите внимание, что вам нужен будет Android NDKдля компиляции плагина.
Используемый в примере код Java можно найти здесь
В этом примере демонстрируется как код Java может быть использован для взаимодействия с ОС Android, и как C++ создает мост между C# и Java. Сцена в пакете отображает кнопку, при нажатии на которую выдается каталог кэша приложения, как это определено в ОС Android. Пожалуйста, обратите внимание, что вам понадобится JDK и Android NDK для компиляции плагинов.
Здесь находиться подобный пример, но на основе предварительно собранной библиотеки JNI, чтобы обернуть нативный код в C#.