このページは Android の プラグイン について記述します。
Android のプラグインをビルドするには、まずは Android NDK を入手し、共通ライブラリ( shared library )を構築するために必要な手順を理解しておいてください。
プラグインを実装するためには、C++( .cpp )を使用している場合は name mangling issues を避けるため、関数が C リンケージで宣言されていることを確認する必要があります。
extern "C" {
float FooPluginFunction ();
}
ビルドしたら、共通ライブラリを Assets->Plugins->Android にコピーしてください。以下の例のように、Unity は C# スクリプトで関数を定義するときに名前でプラグインを参照します。
[DllImport ("PluginName")]
private static extern float FooPluginFunction ();
PluginName にはプレフィックスの (‘lib’) や (‘.so’) 拡張子を含めないでください。 ネイティブプラグインは実際のデバイス展開時にのみ呼び出すことができるので、すべてのネイティブプラグインは C# コードレイヤーでメソッドをラップすることをお勧めします。このコードは Application.platform でどのデバイス上で動作しているか確認し、動作中の時のみネイティブプラグインを呼び出し、エディターで動作しているときはダミーの値を返すことができます。または プラットフォームの Define を使用しコンパイルを制御することもできます。
プリコンパイルした Android ライブラリプロジェクトを Assets->Plugins->Android フォルダーにドロップできます。プリコンパイルとは、すべての .java ファイルをプロジェクトの bin/ か libs/ フォルダーのいずれかに置かれた jar ファイルにコンパイルすることです。 これらのフォルダーから AndroidManifest.xml はビルドしたとき自動的にメインのマニフェストファイルへとマージされます。
詳細については Android Library Projectsを参照してください。
クロスプラットフォームで配置する場合、サポートする各プラットフォームのプラグイン( Android ならば libPlugin.so、Mac であれば Plugin.bundle、Windows であれば Plugin.dll )を含めておく必要があります。 Unity がターゲットのプラットフォームに対応したプラグインを自動的にピックアップし、ビルド時に含めます。
特定の Android プラットフォーム (armv7、x86) では、ライブラリ (lib*.so) を以下のように配置する必要があります:
Assets/Plugins/Android/libs/x86/
Assets/Plugins/Android/libs/armeabi-v7a/
Android のプラグインは、Android OS と連動することができます。
Java プラグインを作成する方法はいくつかありますが、どの方法もプラグインのクラスファイルを含む .jar ファイルを使用します。 一つ目のアプローチは、JDK をダウンロードし、javac コマンドを使用してコマンドラインから java ファイルをコンパイルする方法です。その後 .class ファイルを jar コマンドを使用して .jar ファイルにパッケージ化します。 この .jar は Eclipse と 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);
ここでは、java.lang.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 にあります。
これを行うには、まず Unity/Android に付属している classes.jar を探します。このファイルは Unity をインストールしたフォルダー(Windows であれば C:\Program Files\Unity\Editor\Data 、MacOSX であれば /Applications/Unity )のサブフォルダー PlaybackEngines/AndroidPlayer/Variations/mono または il2cpp/Development or Release/Classes/ にあります。その後、新しい Activity をコンパイルするために使用する classpath に classes.jar を追加します。最終的にはクラスファイルは .jar に圧縮され、Assets->Plugins->Android に配置される形になります。 どのアクティビティーを開始するかはマニフェストによって決定されるので、新しい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
は、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 のカスタムサブクラスに渡すイベントを有効にします。
ネイティブプラグインの簡単な例は、ここ を参照してください。
このサンプルは、C 言語のコードを Unity Android アプリケーションから呼び出す方法についてです。 パッケージにはネイティブプラグインが計算した 2 つの値の合計を表示するシーンが含まれています。 プラグインをコンパイルするには Android NDK が必要なことに注意してください。
Java コードの使用例は ここ を参照してください。
このサンプルでは、Java コードと AndroidOS の連動方法や、C++から C# ・ Java 間のブリッジを作成する方法を示します。 パッケージ内のシーンは、ボタンを押すと Android が定義しているアプリケーションのキャッシュディレクトリを取得する方法について表示されます。 プラグインをコンパイルするには、JDK と Android NDK が必要な事に注意してください。
ここ にあるよく似たサンプルは、JNI ライブラリをベースに、以前作成したネイティブコードを C# 向けにラップしたものです。