Unity Native Pluginの作り方（Android編）

#概要
Android向けに開発したSDKをUnityアプリに対応させる場合、プラグインを作ってSDKとUnityアプリの橋渡しをしてやる必要があります。Android向けネイティブプラグインの実装方法は複数存在しますが、本記事ではUnityPlayerActivityを継承する方式を紹介します。iOS向けプラグインとの共通部分の実装については[共通編](https://qiita.com/maebaru/items/ff6c414427b4512a10c5)を参考にして下さい。 
 
#プラグイン側

##BaseNativeActivity
BaseNativeActivityはUnityPlayerActivityを継承したクラスです。SampleやSampleLifecycleといったSDKで定義されたクラスのメソッドを代理で呼び出すことで、Wrapperとしての役割を果たしています。

```java:BaseNativeActivity
public class BaseNativeActivity extends UnityPlayerActivity {
    public static final void setCallbackGameObjectName(String name) {
        SampleUnityListener.getInstance().setCallbackGameObjectName(name);
    }

    @Override
    protected void onStart() {
        super.onStart();
        SampleLifecycle.onStart(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        SampleLifecycle.onResume(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        SampleLifecycle.onPause(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SampleLifecycle.onDestroy();
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        return super.onPrepareOptionsMenu(menu);
    }

    public void start(String sampleCode) {
        Sample.getInstance().start(this);
    }

    public void doSomething() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Sample.getInstance().doSomething()
            }
        });
    }

    public int getScore() {
        return Sample.getInstance().getScore();
    }

    public String getSomeText() {
        return Sample.getInstance().getSomeText();
    }

    public boolean isAvailable() {
        return Sample.getInstance().isAvailable();
    }
}
```

##SampleUnityListener
SampleUnityListenerはSDK側のコールバックをUnity側へ伝えるためのクラスです。SDK側で定義されたリスナークラス、SampleListenerを実装しています。

```java:SampleUnityListener
public class SampleUnityListener implements SampleListener {
    private static final Sample sample = Sample.getInstance();
    private static SampleUnityListener instance;
    private String callbackGameObjectName;

    public synchronized static SampleUnityListener getInstance() {
        if (instance == null) {
            instance = new SampleUnityListener();
            sample.setListener(instance);
        }

        return instance;
    }

    public final void setCallbackGameObjectName(String name) {
        callbackGameObjectName = name;
    }

    @Override
    public void onStatusChanged(SampleStatus status) {
        if (callbackGameObjectName != null) {
            String statusIndex = String.valueOf(status.ordinal());
            UnityPlayer.UnitySendMessage(callbackGameObjectName, "_Sample_didStatusChange", statusIndex);
        }
    }
}
```

##build.grade
build.gradleの一例です。 SDK本体(SampleSDK-*.jar)に加え、UnityPlayerActivity等のクラスをimportするためclasses.jarをlibsディレクトリに含めていますが、ビルド時は不要のためexcludeしています。またUnity側から名前解決する必要があるため、minifyもfalseにしています。

```groovy:build.gradle
apply plugin: 'com.android.library'

android {
    compileSdkVersion 26
    buildToolsVersion "25.0.3"
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:26.+'
}

android.libraryVariants.all { variant ->
    variant.outputs.each { output ->
        output.packageLibrary.exclude('libs/classes.jar')
        output.packageLibrary.exclude('libs/SampleSDK-*.jar')
    }
}
```

##ビルドについて
AndroidStudioの場合、Build -> Make Moduleからaarを作成することができます。ビルドしたaarはUnityプロジェクトのAssets/Plugins/Android配下に置くと良いでしょう。また、コンパイル時に使用しているclasses.jarの取得方法等については[こちら](https://docs.unity3d.com/ja/540/Manual/PluginsForAndroid.html)を参照して下さい。

#Unity側
##Sample_Android.cs
Sample_Android.csは[ISample](https://qiita.com/maebaru/items/ff6c414427b4512a10c5#isamplecs)を実装したクラスで、プラグイン側で実装したクラス（BaseNativeActivity)にアクセスすることでUnityアプリとSDKの橋渡しを行います。

```c#:Sample_Android.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;

public class Sample_Android : ISample {
    private Sample sampleGameObject;
    private SampleEventListener listener;
    private static AndroidJavaObject androidInstance;

    public Sample_Android(Sample sampleParent) {
        sampleGameObject = sampleParent;

        InitAndroidInstance();
        CreateListenerObject();

        if (sampleParent.androidSampleCode != null) {
            Start(null);
        }
    }

    private void CreateListenerObject() {
        listener = sampleGameObject.gameObject.AddComponent<SampleEventListener>();

        using (AndroidJavaObject activityObject = GetCurrentActivity()) {
            activityObject.CallStatic("setCallbackGameObjectName", sampleGameObject.gameObject.name);
        }

        listener.SetNativeParent(this);
    }

    public void Start(string sampleCode) {
        using (AndroidJavaObject activityObject = GetCurrentActivity()) {
            if (sampleGameObject.androidSampleCode != null) {
                activityObject.Call("start", sampleGameObject.androidSampleCode);
            } else if (sampleCode != null) {
                activityObject.Call("start", sampleCode);
            }
        }
    }

    public void DoSomething() {
        using (AndroidJavaObject activityObject = GetCurrentActivity()) {
            activityObject.Call("doSomething");
        }
    }

    public int GetScore() {
        using (AndroidJavaObject activityObject = GetCurrentActivity()) {
            return activityObject.Call<int>("getScore");
        }
    }

    public SampleStatus GetStatus() {
        SampleStatus status = SampleStatus.Unavailable;

        using (AndroidJavaObject statusObject = androidInstance.Call<AndroidJavaObject>("getStatus")) {
            string statusName = statusObject.Call<string>("name");

            if (statusName.Equals("Unavailable")) {
                status = SampleStatus.Unavailable;
            } else if(statusName.Equals("Available")) {
                status = SampleStatus.Available;
            }
        }

        return status;
    }

    public string GetSomeText() {
        return androidInstance.Call<string>("getSomeText");
    }

    public bool IsAvailable() {
        using (AndroidJavaObject activityObject = GetCurrentActivity()) {
            return activityObject.Call<bool>("isAvailable");
        }
    }

    public AndroidJavaObject GetCurrentActivity() {
        using (AndroidJavaClass playerClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
            return playerClass.GetStatic<AndroidJavaObject>("currentActivity");
        }
    }

    protected static void InitAndroidInstance() {
        using (AndroidJavaClass sampleClass = new AndroidJavaClass("com.sdk.Sample")) {
            androidInstance = sampleClass.CallStatic<AndroidJavaObject>("getInstance"); 
        }
    }
}
```

##AndroidManifest.xml
UnityプロジェクトのAssets/Plugins/Android配下に置くAndroidManifestの一例です。前述のBaseNativeActivityを記載しています。

```xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player"
	  android:installLocation="preferExternal"
	  android:theme="@android:style/Theme.NoTitleBar"
	  android:versionCode="1" android:versionName="1.0">
  <supports-screens android:smallScreens="true"
		    android:normalScreens="true"
		    android:largeScreens="true"
		    android:xlargeScreens="true"
		    android:anyDensity="true" />
  <application android:label="@string/app_name" android:debuggable="false">
    <activity android:name="com.sample.plugin.BaseNativeActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
      <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="true" />
    </activity>
  </application>
  <uses-permission android:name="android.permission.INTERNET" />
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>
```

## Proguard
一部のクラスに対するMinifyの回避等の設定を行いたい場合、proguard-user.txtを作成してUnityプロジェクトのAssets/Plugins/Android配下に置いておくと、AndroidプロジェクトとしてExportした際に自動的に反映してくれます。

```groovy:proguard-user.txt（一例）
-keepattributes *Annotation*
-keepattributes Signature
-keep class com.sample.sdk.** {
  public <fields>;
  public <methods>;
}

-keep class com.sample.plugin.** {
    public <fields>;
    public <methods>;
}

-dontwarn com.sample.sdk.**
```


##ディレクトリ構成
下記を必要に応じてUnityプロジェクトのAssets/Plugins/Android配下に配置して下さい。
- SDK本体(jar/aar)
- プラグイン(jar/aar)
- AndroidManifest.xml
- proguard-user.txt

#参考
https://docs.unity3d.com/ja/540/Manual/PluginsForAndroid.html