visible true

技術的なメモを書く

MultiDexにしたアプリケーションでRobolectricがコケる

問題

以下の様に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.MultiDexApplicationattachBaseContext(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サポートしていかないといけないので大変だのー。