このページは Android の プラグイン について記述します。
Android のプラグインをビルドするには、まずは Android NDK を入手し、共通ライブラリ( shared library )を構築するために必要な手順を理解しておいてください。
プラグインを実装するのに C++ (.cpp) を使用する場合は、名前修飾の問題 を避けるために、関数を C リンケージで宣言してください。
extern "C" {
float FooPluginFunction ();
}
ビルドしたら、共通ライブラリを Assets->Plugins->Android にコピーしてください。以下の例のように、Unity は C# スクリプトで関数を定義するときに名前でプラグインを参照します。
[DllImport ("PluginName")]
private static extern float FooPluginFunction ();
PluginName にはファイル名のプレフィックス (‘lib’) や拡張子 (‘.so’) を含めないように気を付けてください。 すべてのネイティブコードメソッドを、追加の C# コードのレイヤーでラップします。このコードは、 Application.platform を確認し、アプリケーションを実際のデバイスで実行するときにのみ、ネイティブメソッドを呼び出します。エディターで実行するときは、ダミーの値が C# コードから返されます。プラットフォーム依存のコードコンパイルを制御するためには、プラットフォームの #define を使用します。
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 Library Projectsを参照してください。
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.
特定の Android プラットフォーム (armv7、x86) では、ライブラリ (lib*.so) を以下のように配置する必要があります:
Assets/Plugins/Android/libs/x86/
Assets/Plugins/Android/libs/armeabi-v7a/
Android のプラグインは、Android OS と連動することができます。
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 プラグインを JDK1.6 で作成されていることを想定しています。もし 1.7 を使用した場合はコンパイラのコマンドオプションとして “-source 1.6 -target 1.6” を含めなければいけません
Java プラグイン (.jar) をビルドしたら、それを Unity プロジェクト内の Assets->Plugins->Android フォルダーにコピーします。Unity はクラスファイルと 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;
}
C/C++から Java を使い始めるために必要なことはこれで全部です。その他にも JNI を使用すれば、次の例に示すように constructor(<init>)メソッドを含むクラス定義を解決したり、新しいオブジェクトのインスタンスを作成したりすることが可能です。JNI の説明は完全にこのドキュメントの範囲を超えているので、割愛します。:
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 を組み合わせて構築されていますが、独自のロジック(オートメーション処理のため)も多くあります。Java クラスの静的なメンバーにアクセスするために、これらのクラスの ‘静的’ バージョンも備わっています。
AndroidJNI クラスの関数を通して raw の JNI を利用するか、 AndroidJNI を設定して AndroidJNIHelper を使用するか、やがては、最大限の自動化と便宜化のために AndroidJavaObject/AndroidJavaClass を使用するか、用途にあったものをどれでも選択できます。
UnityEngine.AndroidJNI は(上記の) C 言語から JNI を参照するためのラッパーです。このクラスに含まれているメソッドはすべての Java ネイティブインターフェースに対応する static なメソッドを持っています。UnityEngine.AndroidJNIHelper は次のレベルで使用されたヘルパー関数を提供していますが、しかし特殊なケースに対応するためいくつかの関数は puhlic メソッドとして公開しています。
UnityEngine.AndroidJavaObject や UnityEngine.AndroidJavaClass のインスタンスは Java 側の java.lang.Class (またはそのサブクラス)のインスタンスに対応しています。これらは基本的に 3 つの機能を提供します。
Call は大きく 2 つのカテゴリに分けることができます: ‘void’ 関数を Call する場合と、Call が void 以外の型を返すタイプの場合です。ジェネリックの型は、void 以外の型を返す関数に使用されます。Get や Set は常に field 型を示すジェネリック型を取得します。
//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 のコンストラクターは少なくとも 1つのパラメーター - インスタンスをコンストラクトしたいクラスの名前 - を取得します。クラス名以降の任意のパラメーターは、コンストラクターがオブジェクトを呼び出すのに使用されます。この場合は文字列 “some_string” です。次の hashCode() への Call は ‘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);
このケースではまず、AndroidJavaObject ではなく AndroidJavaClass で開始します。なぜなら、新しいオブジェクトを作成する (Android UnityPlayer によってインスタンスが自動的に作成されます) よりもむしろ com.unity3d.player.UnityPlayer の静的メンバーににアクセスしたいためです。そして、静的フィールドの “currentActivity” にアクセスします。ただし、今回はジェネリックのパラメーターとして AndroidJavaObject を使用します。これは、実際のフィールドの型 (android.app.Activity) が java.lang.Object のサブクラスであり、すべての プリミティブでない型 は AndroidJavaObject としてアクセスされなければならないからです。なお文字列は例外的に、それが java のプリミティブ型ではないにもかかわらず、直接アクセスすることができます。
その後は、getCacheDir() を通して Activity をスキャンし、キャッシュディレクトリを示すファイルオブジェクトを取得し、getCanonicalPath() を呼び出し、文字列を取得するだけです。
もちろん、最近は Unity がアプリケーションのキャッシュフォルダー( Application.temporaryCachePath )とファイルのフォルダーパスへ( Application.persistentDataPath )のアクセスをサポートしているので、キャッシュディレクトリをプラグインから取得する必要はありません。
最後は、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 には、ネイティブサイドにある iOS の UnitySendMessage に相当する UnitySendMessage があります。Java でこれを使用して、スクリプトコードにデータを渡すことができます。
この例では、Java 側でメッセージを中継スクリプトから直接呼び出しを行なっています。native/Unity コードの “Main Camera” オブジェクトにメッセージにコールバックし、そのオブジェクトが持っている “JavaMessage” メソッドを呼び出します。
このセクションは、主に JIN、Java、Android に関する広範囲な経験を持っていない人を対象にしています。そのような場合、AndroidJavaObject/AndroidJavaClass を使用して Unity から Java にアプローチしていると仮定されます。
最初に注意すべきことは AndroidJavaObject や AndroidJavaClass で行う操作はどれも (raw の 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 のガベージコレクションで処理されます。
Unity Android では、標準の UnityPlayerActivity クラス (iOS の AppController.mm のような Android 上で Unity プレイヤーを動かすための主要な Java クラス) を継承することができます。
アプリケーションは Android OS と Unity Android 間で連携するどんな処理でも、また、全部をオーバーライドできます。これは、UnityPlayerActivity から派生する新しい Activityを作成することにより可能です。UnityPlayerActivity.java は、Mac の場合は /Applications/Unity/Unity.app/Contents/PlaybackEngines/AndroidPlayer/src/com/unity3d/player で、Windows の場合は通常 C:\Program Files\Unity\Editor\Data\PlaybackEngines\AndroidPlayer\src\com\unity3d\player にあります。
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
は、UnityPlayerActivity
のサブクラスではなく、入力の待ち時間が改善された新しい activity クラスです。ただし、Gingerbread ( Android 2.3 )以降の機能を利用しており、古いデバイスでは動作しません。またタッチ/モーションのイベントをネイティブコードで処理しており、Java ビューからは通常見ることはできません。ただし、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 に 2 つの meta-data が追加されたことに注意してください。一つ目の meta-data は libunity.so を利用するための命令です。2 つ目の meta-data は 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 コードの使用例は ここ を参照してください。
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# 向けにラップしたものです。