Version: 5.3 (switch to 5.4b)
Продвинутый скриптинг в Unity для мобильных платформ
Кастомизация экрана приветствия (Splash Screen) на Android

Сборка плагинов для Android

На этой странице описан нативный код плагинов для Android.

Сборка плагина для Android

Чтобы построить плагин для Android, вы должны сначала получить Android NDK и ознакомиться с инструкцией по созданию общей библиотеки.

Если вы используете C++ (.cpp), при создании плагина вы должны убедиться, что функции объявлены с C-связями, чтобы избежать проблем с коверканьем имен.

extern "C" {
  float FooPluginFunction ();
}

Использование вашего плагина из C

После сборки, общая библиотека должна быть скопированы в папку Assets->Plugins->Android. Unity будет искать её по имени, когда вы определяете функцию вроде следующей в C#-скрипте:-

[DllImport ("PluginName")]
private static extern float FooPluginFunction ();

Обратите внимание, что PluginName не должно включать ни префикс (‘Lib’), ни расширение (‘.so’). Вы должны обернуть все нативные методы кода дополнительным слоем C#-кода. Этот код должен проверить Application.platform и вызывать собственные методы только тогда, когда приложение работает на реальном устройстве; фиктивные значения могут быть возвращены из С#-кода при работе в редакторе. Вы также можете использовать определения платформы для контроля зависимости компиляции кода от платформы.

Проекты библиотек для Android

Вы можете закинуть проекты прекомпилированной Android библиотеки в папку Assets->Plugins->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/

Использование плагинов Java

Механизм Android-плагина также обеспечивает Java, который будет использоваться для взаимодействия с ОС 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-плагинов из нативного кода

После того как вы создали свой ​​Java-плагин (.jar), вы должны скопировать его в папку Assets->Plugins->Android в проекте Unity. Unity упакует ваши файлы .class вместе с остальной частью Java-кода, а затем получить доступ к коду, используя Java Native Interface (JNI). JNI используется как при вызове нативного кода из Java, так и при взаимодействии с Java (или JavaVM) из нативного кода.

Чтобы найти 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
}

Использование Java-плагинов со вспомогательными классами

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 всегда берут универсальный тип, представляющий тип поля.

Пример 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”. Последующий Call(Вызов) метода hashCode() возвращает ‘int’, который мы используем в качестве параметра универсального типа в Call(Вызове) метода.

Примечание: Вы не можете создать экземпляр вложенного класса 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() для получения объекта File, представляющего папку кэша с последующим вызовом getCanonicalPath() для получения строкового представления.

Конечно, в настоящее время вам не нужно делать этого, чтобы получить каталог кэша, поскольку Unity предоставляет доступ к директории кэша и файлов приложения с Application.temporaryCachePath и Application.persistentDataPath.

Пример 3

Наконец, здесь есть уловка для передачи данных из 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”.

Лучшая практика при использовании плагинов Java с Unity

Так как этот раздел в основном ориентирован на людей, которые не имеют большого опыта работы с 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.

Расширение Java-кода UnityPlayerActivity

С Unity Android можно расширить стандартный класс UnityPlayerActivity (основной класс Java для Unity плеера на Android, аналогично AppController.mm на Unity iOS).

Приложение может переопределить любые и все основные взаимодействия между ОС Android и Unity Android. Вы можете cделать это, создав новую Activity, которая является производной от UnityPlayerActivity(UnityPlayerActivity.java можно найти в /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player на Mac и обычно в C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player на Windows).

Чтобы сделать это, сначала найдите classes.jar, поставляемый с Unity Android. Он находится в папке установки (обычно C:\Program Files\Unity\Editor\Data (на Windows) или /Applications/Unity (на Mac)) в подпапке PlaybackEngines/AndroidPlayer/bin. Затем добавить PlaybackEngines/AndroidPlayer/bin в папку с классами, используемыми для компиляции новой Activity. Полученный(-ые) .class файл(ы) должны быть сжаты в .jar файл, а этот файл надо добавить в папку Assets->Plugins->Android. Поскольку манифест определяет запуск Activity, его также необходимо создать новый AndroidManifest.xml. Файл AndroidManifest.xml также должен быть помещен в папку Assets->Plugins->Android (размещение пользовательского манифеста полностью заменяет манифест Unity Android по умолчанию).

Новая 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

Кроме того, можно создать свой собственный подкласс 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 можно найти здесь

В этом примере демонстрируется как код Java может быть использован для взаимодействия с ОС Android, и как C++ создает мост между C# и Java. Сцена в пакете отображает кнопку, при нажатии на которую выдается каталог кэша приложения, как это определено в ОС Android. Пожалуйста, обратите внимание, что вам понадобится JDK и Android NDK для компиляции плагинов.

Здесь находиться подобный пример, но на основе предварительно собранной библиотеки JNI, чтобы обернуть нативный код в C#.

Продвинутый скриптинг в Unity для мобильных платформ
Кастомизация экрана приветствия (Splash Screen) на Android