問題
以下の様にMultiDexに対応させたアプリケーションで、Robolectricのテストが動かない。
build.gradle
defaultConfig { //... multiDexEnabled = true }
AndroidManifest.xml
<application android:name="android.support.multidex.MultiDexApplication"> <!-- ... --> </application>
こんなエラーが出る。
java.lang.RuntimeException: java.lang.RuntimeException: Multi dex installation failed at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:240) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177) at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
原因
原因はandroid.support.multidex.MultiDexApplication
のattachBaseContext(Context)
内で実行されるMultiDex.install(Context)
。ここでapkファイル(zip)のパスから内部のdexファイルを探索する処理が走るが、Robolectricの場合通常のディレクトリのパスが渡されてエラーになるっぽい。
対応
Robolectricは.class
の世界で動くのでMultiDexは関係ない。なのでMultiDex関連のエラーは握りつぶせば良い。ただ問答無用で握りつぶすと怖いので、JavaVM上での実行かどうかについては判定しておきたい。これらを対応する為に以下の2つが必要となる。
- MultiDexApplicationを継承したApplicationクラスを作成する
attachBaseContext(Context)
をOverrideし、JavaVM上での実行なら例外を握りつぶす
実装は以下の通り。System.getProperty("java.vm.name")
でVM名がJava
から始まる場合例外を握りつぶす。Android上だとDalvik
になるのでその場合は再度throwする。環境はOracleのJDK1.7.0_45を使用した。OpenJDKだともしかしたら名前が変わるかも。
Application.java
package hoge.fuga; import android.content.Context; import android.support.multidex.MultiDexApplication; public class Application extends MultiDexApplication { @Override protected void attachBaseContext(Context base) { try { super.attachBaseContext(base); } catch (Exception e) { String vmName = System.getProperty("java.vm.name"); if (!vmName.startsWith("Java")) { throw e; } } } }
AndroidManifest.xmlに設定するのを忘れずに。
AndroidManifest.xml
<application android:name=".Application"> <!-- ... --> </application>
MultiDexApplicationを継承できない時は
既に何らかのApplicationクラスを継承していて、MultiDexApplicationを継承できない時はonCreate()
でMultiDex.install(Context)
を呼び出せばよい。ここで同じようにtry-catchでVMの判定をしつつ例外を握りつぶせばRobolectricで動作が可能となる。
まとめ
RobolectricはtargetSdkVersion 18
までしかサポートしてないし大変だのー。これからはライブラリもMultiDexサポートしていかないといけないので大変だのー。