【更新完了】(3) Android StudioでUnity向けにmoduleを作るときのトラブル対応集

前回と前々回で、Android nativeの関数をUnityから呼び出す方法について紹介してきました。

magicbullet.hatenablog.jp

magicbullet.hatenablog.jp


この方法によりJavaで書いたコードをUnityで呼べるのは間違いありません。

しかし、2回目で書いたように、実際に呼びたいAndroidの側のコードはもっと複雑であることが多く、この方法だけではうまくいかないことがあります。

たとえば、下記の記事で試している音声認識ソースコード(Android Studio)をUnityで使おうとすると、色々なエラーがでてしまい、使うことができません。

magicbullet.hatenablog.jp



そこで、この記事では、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' to 'bob' というエラーがでる。(2016/11/1追加)

(5) activityを呼び出すには?(2016/11/28に追加)

(6) activityを呼び出すコードをtypoなく書いたのに、java.lang.ClassNotFoundExceptionが出る。(2017/7に追加)

(7) AndroidプロジェクトにC/C++ネイティブライブラリを含むとき、Unityではどこに配置するか? (2017/7に追加)

(8) Serviceを使うと、 android.os.BinderProxy cannot be cast to aaa というエラーが出る。(2017/7に追加)

(9) Unityで現在のAcitivityを取得するには? (2017/7に追加)

===

(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はMacWindowsで置き場所が異なります。下記は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

インポートする場所は下記です。

f:id:Takyu:20161023200003p:plain

Warningなどが1,2回出ますが、OKを押して進めます。

この後、gradleのsyncを実行すればエラーが消えます。

f:id:Takyu:20161023200927p:plain

(2) manifest.srcFileを書くと、AndroidManifest.xmlがない、というエラーが出る。

build.gradleには、srcやAssetsフォルダなどを認識させるためのオプション "sourceSets"があります。

下記のブログを書かれた方の動機から推測すると、このオプションはEclipse上での構成を移行するときに使うようです。

qiita.com


今回、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フォルダが消えてしまいました。

f:id:Takyu:20161023202818p:plain



原因は、自分で作った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が復活します。

f:id:Takyu:20161023203739p:plain

(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は、MacWindowsで置き場所が異なります。

Macの場合

/アプリケーション/Unity/PlaybackEngine/AndroidPlayer/Apk/AndroidManifest.xml

Windowsの場合

C:\Program Files\Unity\Editor\Data/PlaybackEngines/AndroidPlayer/Apk/AndroidManifest.xml

中身はこうなっています。


gistb991ce1d94b5cdf556bafc7b88a8ae61


たとえば、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とかぶってしまうのが原因のようです。

starzero.hatenablog.com



解決するには、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に追加)

AndroidJavaですが、時にはC/C++で書かれたライブラリを使いたい時があります。

このような外部ライブラリは一般的にPluginsディレクトリ以下に配置しますが、Android向けのライブラリファイルは配置するディレクトリが決まっています。

docs.unity3d.com

特定の 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起動メソッドを呼んでいる
}


途中、だいぶ時間が空いてしまいましたが、ひとまずトラブル対応集は完了です。

もしまたAndroidのネイティブ機能をUnityで使うときがあれば、また更新するかもしれません。