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

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

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

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

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

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

extern "C" {
  float FooPluginFunction ();
}

Using Your Plugin from C#

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

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

Please note that PluginName should not include the prefix (‘lib’) nor the extension (‘.so’) of the filename. You should wrap all native code methods with an additional C# code layer. This code should check Application.platform and call native methods only when the app is running on the actual device; dummy values can be returned from the C# code when running in the Editor. You can also use platform defines to control platform dependent code compilation.

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

You can drop pre-compiled Android library projects into the Assets->Plugins->Android folder. Pre-compiled means all .java files must have been compiled into jar files located in either the bin/ or the libs/ folder of the project. AndroidManifest.xml from these folders will get automatically merged with the main manifest file when the project is built.

Смотрите Проекты библиотек для Android для большей информации.

Развертывание

For cross platform deployment, your project should include plugins for each supported platform (ie, libPlugin.so for Android, Plugin.bundle for Mac and Plugin.dll for Windows). Unity automatically picks the right plugin for the target platform and includes it with the player.

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

There are several ways to create a Java plugin but the result in each case is that you end up with a .jar file containing the .class files for your plugin. One approach is to download the JDK, then compile your .java files from the command line with javac. This will create .class files which you can then package into a .jar with the jar command line tool. Another option is to use the Eclipse IDE together with the 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 and AndroidJNI can be used to ease some of the pain with raw JNI.

AndroidJavaObject and AndroidJavaClass automate a lot of tasks and also use cacheing to make calls to Java faster. The combination of AndroidJavaObject and AndroidJavaClass builds on top of AndroidJNI and AndroidJNIHelper, but also has a lot of logic in its own right (to handle the automation). These classes also come in a ‘static’ version to access static members of Java classes.

Вы можете выбрать в зависимости от того, что вам подходит, будь то первичный 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:-

  • Вызов метода
  • Получить значения поля
  • Установить значение поля

The Call is separated into two categories: Call to a ‘void’ method, and Call to a method with non-void return type. A generic type is used to represent the return type of those methods which return a non-void type. The Get and Set always take a generic type representing the field type.

Пример 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);


Here, we’re creating an instance of java.lang.String, initialized with a string of our choice and retrieving the hash value for that 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);


In this case, we start with AndroidJavaClass instead of AndroidJavaObject because we want to access a static member of com.unity3d.player.UnityPlayer rather than create a new object (an instance is created automatically by the Android UnityPlayer). Then we access the static field “currentActivity” but this time we use AndroidJavaObject as the generic parameter. This is because the actual field type (android.app.Activity) is a subclass of java.lang.Object, and any non-primitive type must be accessed as AndroidJavaObject. The exceptions to this rule are strings, which can be accessed directly even though they don’t represent a primitive type in Java.

After that it is just a matter of traversing the Activity through getCacheDir() to get the File object representing the cache directory, and then calling getCanonicalPath() to get a string representation.

Конечно, в настоящее время вам не нужно делать этого, чтобы получить каталог кэша, поскольку 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); 
    }
} 


The Java class com.unity3d.player.UnityPlayer now has a static method UnitySendMessage, equivalent to the iOS UnitySendMessage function on the native side. It can be used in Java to pass data to script code.

Here though, we call it directly from script code, which essentially relays the message on the Java side. This then calls back to the native/Unity code to deliver the message to the object named “Main Camera”. This object has a script attached which contains a method called “JavaMessage”.

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

Так как этот раздел в основном ориентирован на людей, которые не имеют большого опыта работы с JNI, Java и Android, мы предполагаем, что подход с AndroidJavaObject/AndroidJavaClass был использован для взаимодействия с кодом Java из Unity.

The first thing to note is that any operation you perform on an AndroidJavaObject or AndroidJavaClass is computationally expensive (as is the raw JNI approach). It is highly advisable to keep the number of transitions between managed and native/Java code to a minimum, for the sake of performance and also code clarity.

You could have a Java method to do all the actual work and then use AndroidJavaObject / AndroidJavaClass to communicate with that method and get the result. However, it is worth bearing in mind that the JNI helper classes try to cache as much data as possible to improve performance.

//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).

An application can override any and all of the basic interaction between Android OS and Unity Android. You can enable this by creating a new Activity which derives from UnityPlayerActivity (UnityPlayerActivity.java can be found at /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player on Mac and usually at C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player on Windows).

To do this, first locate the classes.jar shipped with Unity Android. It is found in the installation folder (usually C:\Program Files\Unity\Editor\Data (on Windows) or /Applications/Unity (on Mac)) in a sub-folder called PlaybackEngines/AndroidPlayer/Variations/mono or il2cpp/Development or Release/Classes/. Then add classes.jar to the classpath used to compile the new Activity. The resulting .class file(s) should be compressed into a .jar file and placed in the Assets->Plugins->Android folder. Since the manifest dictates which activity to launch it is also necessary to create a new AndroidManifest.xml. The AndroidManifest.xml file should also be placed in the Assets->Plugins->Android folder (placing a custom manifest completely overrides the default Unity Android manifest).

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

Примеры

Пример нативного плагина

Простой пример использования кода нативного плагина можно найти здесь

This sample demonstrates how C code can be invoked from a Unity Android application. The package includes a scene which displays the sum of two values as calculated by the native plugin. Please note that you will need the Android NDK to compile the plugin.

Пример плагина Java

Используемый в примере код Java можно найти здесь

This sample demonstrates how Java code can be used to interact with the Android OS and how C++ creates a bridge between C# and Java. The scene in the package displays a button which when clicked fetches the application cache directory, as defined by the Android OS. Please note that you will need both the JDK and the Android NDK to compile the plugins.

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

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