このページはAndroidの Native Code Plugins について記述します。
Androidのプラグインをビルドするには,まずは Android NDK を入手し,共通ライブラリ(shared library)を構築するために必要な手順を理解しておいてください。
プラグインを実装するためには,C++(.cpp)を使用している場合は name mangling issues を避けるため,関数がCリンケージで宣言されていることを確認する必要があります。
extern "C" {
float FooPluginFunction ();
}
まずビルドする際,共通ライブラリを
にコピーしてください。UnityはC#スクリプトに定義した関数名からプラグインを参照します。[DllImport ("PluginName")]
private static extern float FooPluginFunction ();
PluginName にはプレフィックスの (‘lib’) や (‘.so’) 拡張子を含めないでください。 ネイティブプラグインは実際のデバイス展開時にのみ呼び出すことができるので,全てのネイティブプラグインはC#コードレイヤーでメソッドをラップすることをお勧めします。このコードは Application.platform でどのデバイス上で動作しているか確認し,動作中の時のみネイティブプラグインを呼び出し,エディターで動作しているときはダミーの値を返すことができます。または platform defines を使用しコンパイルを制御することもできます。
事前コンパイルした Android ライブラリ プロジェクトを
フォルダにドロップ出来ます。事前コンパイルとは全ての .java ファイルはプロジェクトの bin/ または libs/ フォルダのいずれかの jar ファイルでコンパイルしている必要があります。 これらのフォルダから AndroidManifest.xml はビルドした時,自動的にメインのマニフェストファイルへとマージされます。詳細については Android Library Projectsを参照して下さい。
クロスプラットフォームで配置する場合,サポートする各プラットフォームのプラグイン(AndroidならばlibPlugin.so,MacであればPlugin.bundle,WindowsであればPlugin.dll)を含めておく必要があります。 Unity がターゲットのプラットフォームに対応したプラグインを自動的にピックアップし,ビルド時に含めます。
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)を Java Native Interface (JNI) を使用してアクセスします。JNIはJavaからネイティブコード,ネイティブコードからJava(またはJavaVM)を相互に呼び出す時に使用します。
フォルダにコピーします。Unityはクラスファイルと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;
}
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 を組み合わせて構築されていますが,ロジックの多くは(自動で処理を行うために)独自の権利を要求します。これらのクラスも静的なメンバにアクセスする際は’static’ バージョンを使用します。
AndroidJNI で直接クラスやメソッドを利用するか, 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以外の型を返すメソッドの戻り値の型はジェネリックで表現しています。 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 でインスタンスを作る際に,設定した stringを使って初期化し, hash value の文字列を受けとる例を 示します。
AndroidJavaObject のコンストラクタの最初の引数は,生成するインスタンスのクラス名を設定しています。クラス名以降の任意のパラメータは,コンストラクタの呼び出しに使用します。この場合,引数として文字 “some_string” を渡しています。次はhashCode()は, Call 時に’int’ を返すので,ジェネリックに型を設定しています。
注意: ドット表記法を使用してネストされたJavaクラスをインスタンス化することはできないことに注意してください。インナークラスは $ セパレーターを使用してください。それはドットとスラッシュフォーマットで動作するはずです。そして android.view.ViewGroup$LayoutParams もしくはViewGroup クラスと LayoutParams クラスはネストされているので, android/view/ViewGroup$LayoutParams を使用することができます。
このプラグインサンプルは,現在のアプリケーションのキャッシュディレクトリを取得する方法を示します。この方法はプラグイン無しで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 で開始しています( com.unity3d.player.UnityPlayer インスタンスよりもスタティックフィールドにアクセスしたいため)。そして,スタティックフィールドの “currentActivity” にアクセスします。その後 AndroidJavaObject を使用します。変数の型 ( android.app.Activity ) は java.lang.Object のサブクラスのため, non-primitive type にアクセスするには AndroidJavaObject からアクセスしなければなりません。なおstring型は例外的に(それがプリミティブ型ではないにもかかわらず)直接アクセスすることができます。
次の要素では, getCacheDir() を通してキャッシュディレクトリのファイルオブジェクトを取得し,続く getCanonicalPath() でファイルパスを取得します。
もちろん,最近はUnityがアプリケーションのキャッシュフォルダ( Application.temporaryCachePath )とファイルのフォルダパスへ( Application.persistentDataPath )のアクセスをサポートしているので ,キャッシュディレクトリをプラグインから取得する必要はありません。
最後は, 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 には iOS のネイティブサイドにある UnitySendMessage に相当する UnitySendMessage があります。これはJavaからスクリプトコードにデータを渡すために使用することができます。
この例では,Java側でメッセージを中継スクリプトから直接呼び出しを行なっています。native/Unityコードの “Main Camera” オブジェクトにメッセージにコールバックし, そのオブジェクトが持っている “JavaMessage” メソッドを呼び出します。
このセクションは,主にJINやJavaとAndroidの経験を持っていない人を対象にしており, AndroidJavaObject/AndroidJavaClass アプローチを使用していることを前提としています。
最初に注意すべきことは,(JNIと同様に) AndroidJavaObject や AndroidJavaClass の処理が非常に高負荷ということです。そのため,パフォーマンスやコードの可読性を維持するためJava/ネイティブコード間の呼び出しを最小限にすることをお勧めします。
AndroidJavaObject / AndroidJavaClass を使用すれば,すべてのJavaメソッドを参照したり処理結果を得ることができます。しかし,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
生成した 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では UnityPlayerActivity クラス(iOSのAppController.mmのようなAndroid上でUnityプレイヤーを動かすための主要なJavaクラス)を拡張することができます。
アプリケーションはAndroid OSとUnity Android間で連携する処理の一部もしくは全部を書き換えることができます。これは新しいUnityPlayerActivityの Activity を作成する形で行います。(UnityPlayerActivity.javaは,MacOSXなら ,Windowsなら から取得することができます)
これを行うには,まずUnity/Androidに付属している AndroidManifest.xml を作成し, に配置する必要があります。(デフォルトのUnity Android マニフェストをオーバーライドするカスタムマニフェストを配置します。)
を探します。このファイルはUnityをインストールしたフォルダ(Windowsであれば ,MacOSXであれば )のサブフォルダ にあります。その後,新しいActivityからUnityの機能を使用するために,classpathに を追加します。最終的にはクラスファイルを.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
のサブクラスではなく,入力の待ち時間が改善された新しい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#向けにラップしたものです。