UnityからAndroid Javaをコールする場合、AndroidJavaClass, AndroidJavaObject, AndroidJavaProxyなどを使うと簡単にJavaを呼べます。これらを使うと、Javaを書かなくても簡単にC#から大体のAPIが叩けるように優れものです。特にAndroidJavaProxyは、JavaにあるinterfaceをC#側で実装できるというとんでもなく便利な代物です。
しかしながら先日このAndroidJavaProxyを使っていて少しハマったので、それを共有したいと思います。
罠の例
まず以下のJavaのinterfaceがあるとします。
public interface IHoge {
void onHoge(int a, String b, String c);
}
これを実装したクラスをJava側に渡すための、UnityにおけるAndroidJavaProxyを継承したクラスは、ドキュメント読む限り以下のようになります。
class Hoge : AndroidJavaProxy
{
public Hoge() : base("some.package.IHoge") {}
void onHoge(int a, string b, string c)
{
// do something
}
}
そしてこのクラスをインスタンス化してJava側に渡し、Javaから以下のように呼び出したとします・・・
IHoge hoge = getHoge();
hoge.onHoge(0, "some string", null);
結果
Unity > No such proxy method: onHoge(int, string AndroidJavaObject)
!?
何で最後の型がAndroidJavaObjectになってるんだ・・・
コードダイブ
原因を探るために、コードダイブしてみましょう。
もちろんUnityはコード非公開ですが、C#のdllなら簡単にデコンパイルできます。
GitHub
若干古いですが、有志がデコンパイルしたものをGitHubにあげてたりもしますので、こちらを見てみることにします・・・
原因
原因はAndroidJavaProxyの実装から読み取れます。
public virtual AndroidJavaObject Invoke(string methodName, object[] args)
{
Exception ex = null;
BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
Type[] array = new Type[args.Length];
for (int i = 0; i < args.Length; i++)
{
array[i] = ((args[i] != null) ? args[i].GetType() : typeof(AndroidJavaObject));
}
try
{
MethodInfo method = base.GetType().GetMethod(methodName, bindingAttr, null, array, null);
if (method != null)
{
AndroidJavaObject result = _AndroidJNIHelper.Box(method.Invoke(this, args));
return result;
}
}
(中略)
}
Javaから呼び出された時に、AndroidJavaProxyは対応するメソッドをリフレクションを使って呼び出すようになっていますが、AndroidJavaObject型をC#の基本型に変換できる場合は変換してから探します。しかしながらnullだけは型がわからないため、AndroidJavaObjectで探しているために、エラーが出てしまっていたのです。
自動変換される型はプリミティブ型と文字列型だけですが、文字列型だけnullableなのでこのようなことが起こってしまいます。
解決方法
ぶっちゃけリフレクションとか重いので、以下のようにすればパフォーマンス的にも良く、上のような問題は起こりません。
class Hoge : AndroidJavaProxy
{
public Hoge() : base("some.package.IHoge") {}
public override AndroidJavaObject Invoke(string methodName, object[] args)
{
onHoge((int)args[0], (string)args[1], (string)args[2]);
return null;
}
void onHoge(int a, string b, string c)
{
// do something
}
}
もちろんこれは1メソッドの場合なので、複数メソッドの場合は工夫が必要です。またエラー処理もやっていません。適宜調整して下さい。