[Unity+Android] C#からJavaのネイティブへのメソッド呼び出しを高速化してみる

## 適当に、Javaのメソッドを呼び出してみる
以下は、`UnityEngine.AndroidJavaObject`クラスとJavaの`android.os.StatFs`クラスを使って、100回連続で端末の空き容量を調べるスクリプトです。(普通こんなシチュエーションないですが・・・)

```csharp:hoge.cs
	public int loopCount = 100;
    // 毎回 AndroidJavaObjectを生成 & 破棄
	public void CreateAndDispose ()
	{
		int blockSize = 0;
		int availableSize = 0;
		long startTime = DateTime.Now.Ticks;
		for (var i = 0; i < loopCount; ++i) {
			using (AndroidJavaObject statFs = new AndroidJavaObject( "android.os.StatFs", Application.persistentDataPath)) {
				blockSize = statFs.Call<int> ("getBlockSize");
				availableSize = statFs.Call<int> ("getAvailableBlocks");
			}
		}
		long endTime = DateTime.Now.Ticks;
		var duration = new TimeSpan (endTime - startTime);
		Debug.Log(string.Format ("Create Once {0} msec\n {1}", duration.TotalMilliseconds, (blockSize * availableSize)));
	}

    // 最初にAndroidJavaObjectを一個だけ生成 あとは使い回してCall
	public void CreateOnce ()
	{
		int blockSize = 0;
		int availableSize = 0;
		long startTime = DateTime.Now.Ticks;
		AndroidJavaObject statFs = new AndroidJavaObject ("android.os.StatFs", Application.persistentDataPath);
		for (var i = 0; i < loopCount; ++i) {
			blockSize = statFs.Call<int> ("getBlockSize");
			availableSize = statFs.Call<int> ("getAvailableBlocks");
		}
		statFs.Dispose ();
		long endTime = DateTime.Now.Ticks;
		var duration = new TimeSpan (endTime - startTime);
		Debug.Log(string.Format ("Create Once {0} msec\n {1}", duration.TotalMilliseconds, (blockSize * availableSize)));
	}

    // AndroidJavaObjectのメソッド呼び出しをキャッシュしてみた
	public void CreateTinyAndroidObject ()
	{
		int blockSize = 0;
		int availableSize = 0;
		long startTime = DateTime.Now.Ticks;
		TinyAndroidJavaObject statFs = new TinyAndroidJavaObject ("android.os.StatFs", Application.persistentDataPath);
		int getBlockSizeHash = statFs.LoadIntMethod ("getBlockSize");
		int getAvailableBlocksHash = statFs.LoadIntMethod ("getAvailableBlocks");
		for (var i = 0; i < loopCount; ++i) {
			blockSize = statFs.CallInt (getBlockSizeHash);
			availableSize = statFs.CallInt (getAvailableBlocksHash);
		}
		statFs.Dispose ();
		long endTime = DateTime.Now.Ticks;
		var duration = new TimeSpan (endTime - startTime);
		Debug.Log(string.Format ("Tiny {0} msec\n {1}", duration.TotalMilliseconds, (blockSize * availableSize)));
	}
```

```csharp:TinyAndroidJavaObject.cs
using UnityEngine;
using System.Collections.Generic;
using System;

public class TinyAndroidJavaObject : AndroidJavaObject
{
	protected readonly Dictionary<int, IntPtr> _methodPtrCacheMap = new Dictionary<int, IntPtr> ();

	public TinyAndroidJavaObject (string className, params object[] args) : base(className, args) { }

    // 使いたいメソッドのMethodIDをキャッシュしておく
	public int LoadIntMethod (string methodName, params object[] paramSamples)
	{
        // オーバーロードを考えると、メソッド名だけじゃなく、引数の型もハッシュ計算に入れたほうが良さげ
		int methodNameHash = methodName.GetHashCode ();
		if (_methodPtrCacheMap.ContainsKey (methodNameHash)) {
			return 0;
		}
		IntPtr ptr = AndroidJNIHelper.GetMethodID<int> (m_jclass, methodName, paramSamples, false);
		if (ptr == default(IntPtr)) {
			return 0;
		}
		_methodPtrCacheMap [methodNameHash] = ptr;
		return methodNameHash;
	}

	public int CallInt (int methodNameHash, params object[] args)
	{
		if (_methodPtrCacheMap.ContainsKey (methodNameHash)) {
			if (args == null) {
				args = new object[0];
			}
			jvalue[] array = AndroidJNIHelper.CreateJNIArgArray (args);
			try {
				return AndroidJNI.CallIntMethod (m_jobject, _methodPtrCacheMap [methodNameHash], array);
			} finally {
				AndroidJNIHelper.DeleteJNIArgArray (args, array);
			}
		}
		return 0;
	}

	protected override void Dispose (bool disposing)
	{
		_methodPtrCacheMap.Clear ();
		base.Dispose (disposing);
	}
}
```


## CreateAndDisposeとCreateOnceとCreateTinyAndroidObjectの実行時間の比較結果
環境は、2012年モデルのNexus7

- `CreateAndDispose();` => 35ms 前後
- `CreateOnce();` => 8ms 前後
- `CreateTinyAndroidObject();` => 1ms 前後

## そこそこ早くできそう
重い重いと言われている、UnityからのネイティブAPI呼び出しだけど、頑張ればそれなりに軽減できるっぽい。
ただし、安全なのかはわかりませんけどね！