概要

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

プラグイン側

BaseNativeActivity

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

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を実装しています。

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にしています。

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の取得方法等についてはこちらを参照して下さい。

Unity側

Sample_Android.cs

Sample_Android.csはISampleを実装したクラスで、プラグイン側で実装したクラス(BaseNativeActivity)にアクセスすることでUnityアプリとSDKの橋渡しを行います。

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 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した際に自動的に反映してくれます。

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

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.