前回と前々回で、Android nativeの関数をUnityから呼び出す方法について紹介してきました。
Android StudioでUnity向けmoduleを作る方法 - CrossRoad
Android Studioで作ったmoduleをUnityから呼ぶ方法 - CrossRoad
この方法によりJavaで書いたコードをUnityで呼べるのは間違いありません。しかし、2回目で書いたように、実際に呼びたいAndroidの側のコードはもっと複雑であることが多く、この方法だけではうまくいかないことがあります。
たとえば、下記の記事で試している音声認識のソースコード(Android Studio)をUnityで使おうとすると、色々なエラーがでてしまい、使うことができません。
【2016/11/27更新】【Android】Bing Speech APIを使う手順とトラブル対応法をまとめました - CrossRoad
そこで、この記事では、AndroidのnativeコードをUnityで使うとき、私が困ったことと対応方法について整理したいと思います。
- 1. Android StudioでUnity系のpackageを書くとエラーになる
- 2. manifest.srcFileを書くと、AndroidManifest.xmlがない、というエラーが出る
- 3. aarとjarは、どちらを使えばよいか?
- 4. Unityでビルドすると、Error building Player: IOException: Failed to Move File / Directory from 'aaa/classes.jar' to 'bob/classes.jar' というエラーがでる
- 5. activityを呼び出すには?
- 6. activityを呼び出すコードをtypoなく書いたのに、java.lang.ClassNotFoundExceptionが出る
- 7. AndroidプロジェクトにC/C++ネイティブライブラリを含むとき、Unityではどこに配置するか?
- 8. Serviceを使うと、 android.os.BinderProxy cannot be cast to aaa というエラーが出る
- 9. Unityで現在のAcitivityを取得するには?
- 10. 終わりに
1. Android StudioでUnity系のpackageを書くとエラーになる
たとえば、Unityの関数(例:UnitySendMessage)をAndroidから呼び出したり、Unity上のAndroid向けActivityを取得するためには、下記のようなpackageをインポートする必要があります。import com.unity3d.player.UnityPlayer; import com.unity3d.player.UnityPlayerActivity; import com.unity3d.player.UnityPlayerNativeActivity;
これをソースコードに書くとエラーになります。回避するには、Unityアプリケーションの中にあるclasses.jarをAndroid Studioのプロジェクトにインポートする必要があります。
classes.jarはMacとWindowsで置き場所が異なります。下記はUnity5.3.5f1のときの配置です。Unityのバージョンによって場所が変わることがあるようなのでご注意ください。
Macの場合
/アプリケーション/Unity/PlaybackEngine/AndroidPlayer/Variations/mono/Release/Classes/classes.jar
Windowsの場合
C:\Program Files\Unity\Editor\Data/PlaybackEngines/AndroidPlayer/Varitations/mono/Release/Classes/classes.jar
インポートする場所は下記です。
Warningなどが1,2回出ますが、OKを押して進めます。
この後、gradleのsyncを実行すればエラーが消えます。
2. manifest.srcFileを書くと、AndroidManifest.xmlがない、というエラーが出る
build.gradleには、srcやAssetsフォルダなどを認識させるためのオプション "sourceSets"があります。下記のブログを書かれた方の動機から推測すると、このオプションはEclipse上での構成を移行するときに使うようです。AndroidStudioでmainのディレクトリ構成を使わない方法 - Qiita
今回、MicrosoftのBingSpeechAPIをmodule化するために、新規プロジェクトを作って試しました。そのとき、元のMSのサンプルコードにあるbuild.gradleを書き写したところ、下記でエラーになりました。
android{ (途中省略) sourceSets { main { manifest.srcFile 'AndroidManifest.xml' (途中省略) } }
Error:A problem was found with the configuration of task ':app:checkDebugManifest'.
> File '/Users/UserName/AndroidStudioProjects/Test/app/AndroidManifest.xml' specified for property 'manifest' does not exist.
また、Android Viewからはmanifestフォルダが消えてしまいました。
原因は、自分で作ったAndroid StudioプロジェクトとEclipseから移行したプロジェクトではManifestファイルの配置場所が異なっているためです。
Android Studio(2.1.2)で作った場合
app/src/main/AndroidManifest.xml
Eclipseから移行した場合
app/AndroidManifest.xml
十分には確認していないのですが、今回のオプションは Eclipseから移行したプロジェクトでなければ不要です。ちなみに、build.gradleで manifest.srcFile 'AndroidManifest.xml'のオプションを削除すると、Android ViewでmanifestフォルダとAndroidManifest.xmlが復活します。
3. aarとjarは、どちらを使えばよいか?
UnityでAndroidのコードを使うには、aarかjarのどちらかをインポートする必要があります。aarとjarの違いはここで解説した通りですが、再掲します。aar : Android Archive Library (Androidアプリ開発に必要なファイルをまとめたもの)
jar : Java Archive
つまり、aarの方が範囲が広く、aarにはjarだけでなくAndroidManifest.xmlも入っています。自分の実験結果、および購入したアセットのフォルダ構成調査の結果から、Unityではaarもjarのどちらも使えます。(Plugins/Androidに入れるだけです)
しかし、aarを使う場合、Androidmanifest.xmlの記述内容によっては、Unityでビルドした時にコンフリクトエラーになる場合があるため、Unityで準備されているAndroidManifest.xmlと重複記述がないようにする必要があります。
また、Unityで準備されているAndroidManifest.xmlは、MacとWindowsで置き場所が異なります。
Macの場合
/アプリケーション/Unity/PlaybackEngine/AndroidPlayer/Apk/AndroidManifest.xml
Windowsの場合
C:\Program Files\Unity\Editor\Data/PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml
Unityで提供されているAndroidManifest.xmlを使うときはコンフリクトに注意します。たとえば、Activityでintent.Action.MAINをAndroid Studio側に書いてしまうと、Unity側とコンフリクトを起こすので、書かないようにします。
4. Unityでビルドすると、Error building Player: IOException: Failed to Move File / Directory from 'aaa/classes.jar' to 'bob/classes.jar' というエラーがでる
2016/11/1に追加しました
jarファイルだけを使う場合、このエラーは発生しません。これはAndroid StudioでUnitySendMessageとか、Unity向けのメソッドを使うために入れたclasses.jarが、Unityに入れたaarに残っており、UnityでAndroid向けのビルドをするときに使われるclasses.jarとかぶってしまうのが原因のようです。
AndroidStudioを使ってUnityとAndroidの連携 - ほげほげ(仮)
解決するには、Android Studio側のbuild.gradleで下記を書いておきます。
android.libraryVariants.all { variant -> variant.outputs.each { output -> output.packageLibrary.exclude('libs/classes.jar') } }
この記述により、Android Studioでライブラリのパッケージ(aar)を作る時、libs/classes.jarを除きます。
5. activityを呼び出すには?
(2016/11/28に追加しました)
aarの中にActivityがある場合、Unityから呼び出すことができます。それぞれ、下記のコードを書きます。Java側のAcitivityに画面を準備していない場合、startActivityを呼び出した段階で画面が真っ白になります。
Android Studio側
//import宣言などは省略 public void launchActivity(final Activity m_activity){ Intent i = new Intent(); i.setClassName(m_activity,"よびだしたいactivityをpackage名を含むフルパスで表記"); i.setAction(Intent.ACTION_MAIN); m_activity.startActivity(i); }
Unity側
//using宣言などは省略 public void launchActivity(){ AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject currentUnityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); AndroidJavaClass plugin = new AndroidJavaClass("Java側で呼び出したいActivityを持つクラスを、package名を含むフルパスで表記"); plugin.CallStatic("launchActivity",currentUnityActivity); // 第一引数は、Java側のメソッド名 }
6. activityを呼び出すコードをtypoなく書いたのに、java.lang.ClassNotFoundExceptionが出る
(2017/7に追加しました)
たとえば、Android Studio側でプラグインを作成し、Unity側でこのようなコードを書いたとします。
gist1c06bc192c8f65903dddb081e1f385b3
そして、bindService_Start()メソッドをUnityで呼ぶとこのようなエラーが出ることがあります。
AndroidJavaException: java.lang.ClassNotFoundException: com.microsoft.CognitiveServicesExample.MainActivity
java.lang.ClassNotFoundException: com.microsoft.CognitiveServicesExample.MainActivity
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:308)
at java.lang.Class.forName(Class.java:272)
at com.unity3d.player.UnityPlayer.nativeRender(Native Method)
at com.unity3d.player.UnityPlayer.a(Unknown Source)
at com.unity3d.player.UnityPlayer$b.run(Unknown Source)
Caused by: java.lang.ClassNotFoundException: Didn't find class "com.microsoft.CognitiveServicesExample.MainActivity" on path: DexPathList[[zip file "/data/app/com.tack.example1-1/base.apk"],nativeLibraryDirectories=[/data/app/com.tack.example1-1/lib/arm, /vendor/lib, /system/lib]]
at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:56)
ClassNotFoundException (指定したクラスは存在しない)というエラーです。ただし、Unity側で指定したpackage名は合っています。
今回の私の場合、原因はSpeechSDK.jar(Android Studio側のサンプルに含まれていたプラグインや、ヘッダファイル的なもの)をUnity側にインポートしていないことが原因でした。
もう少し汎用的に言うと、Android Studio側で呼び出したいクラスにimplementの記載があると、implementで宣言されたメソッドの実体が入っているjarファイルがUnityにないと、package名が合っていてもClassNotFoundExceptionになるようです。
この現象は、1からAndroid Studioで作った機能をUnityで使う場合には発生しないかもしれませんが、今回のように既存のAndroidの機能(そこそこ複雑なもの)をUnityで使えるようにしたい、というときにありえます。
7. AndroidプロジェクトにC/C++ネイティブライブラリを含むとき、Unityではどこに配置するか?
(2017/7に追加しました)
AndroidはJavaですが、時にはC/C++で書かれたライブラリを使いたい時があります。このような外部ライブラリは一般的にPluginsディレクトリ以下に配置しますが、Android向けのライブラリファイルは配置するディレクトリが決まっています。
特定の Android プラットフォーム (armv7、x86) では、ライブラリ (lib*.so) を以下のように配置する必要があります:
Assets/Plugins/Android/libs/x86/
Assets/Plugins/Android/libs/armeabi-v7a/
もしこのルールを守らず、例えば、
Assets/Plugins/Android/aaa.so
と配置すると、以下のようなエラーが出ます。
Caused by: java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.tack.test111-1/base.apk"],nativeLibraryDirectories=[/data/app/com.tack.test111-1/lib/arm, /vendor/lib, /system/lib]]] couldn't find "aaa.so"
Unityではディレクトリ構成が比較的自由ですが、プラグインについては記述を守る必要があります。
8. Serviceを使うと、 android.os.BinderProxy cannot be cast to aaa というエラーが出る
(2017/7に追加しました)
たとえば、このようなコードを書いてServiceとしてAndroidの別機能を呼び出すとします。
gistd4f26eb7a4434747971dedbf4f5517d9
すると、こんなエラーが出ることがあります。(一部だけ載せています)
Caused by: java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.microsoft.CognitiveServicesExample.MainActivity$MyServiceLocalBinder
at com.microsoft.CognitiveServicesExample.NativeBridge$1.onServiceConnected(NativeBridge.java:25)
at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1209)
at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1226)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
原因はAndroidManifest.xmlにあります。私の場合は、Serviceの使い方を参考にしたサンプルコードにisolatedProcessというタグが入っていました。つまり、isolatedProcessを抜くと解決します。
gistbfaee572dd60f7ca96b0dd76a956f143
9. Unityで現在のAcitivityを取得するには?
(2017/7に追加しました)
Unity側のC#でこのようなコードを書きます。
AndroidJavaObject currentUnityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
Unityで作ったAndroidアプリは、Unityの準備したActivityがメインのActivity以前になります。書いたブログでは、currentUnityActivity変数を使って現在のActivityを取得し、これを元にServiceを起動していました。
public void bindService_Start(){ Debug.Log("Unity test bindService Start"); AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); AndroidJavaObject currentUnityActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"); AndroidJavaClass plugin = new AndroidJavaClass("com.microsoft.CognitiveServicesExample.NativeBridge"); plugin.CallStatic("startBindService",currentUnityActivity); //ここで、現在のActivityを引数に入れて、Android側で準備したservice起動メソッドを呼んでいる }