retlat's blog

Robolectric 4.4でJava 9必須のAPI Levelを対象とした時、Android Studio 4.0の同梱JDKで動かないのを回避する

AndroidでJSONを操作するところのUnit Testを書こうと思ったら、Robolectricが動かなくて困ったのでメモ

環境

  • Android Studio 4.0.1
  • Robolectric 4.4
  • targetSdkVersion 30

Robolectric 4.3.1からはリリースノートにある通り、API Level 29が対象の時にはJava 9が必須になっている
そしてAndroid Studio 4.0に同梱されているのはJava 8系なので動かない

解決の方法としては以下の2通りが考えられる

  1. Java 9以降のJDKをインストールする
  2. Robolectricの対象API Levelを下げる

個人的にはAndroid Studio以外にインストールするものがあるのは面倒だし、環境を作り直すときに確実に忘れるので 対象API Levelを下げる 方向で対処する

Robolectricで対象のAPI Levelを設定するには以下のような方法がある

  1. @Config annotationで指定する
  2. robolectric.propertiesで指定する
  3. RobolectricTestRunnerをextendする

ただどれも面倒だと思ってしまったので、今回は build.gradle.kts を編集する

公式サイトにあるようにRobolectricは targetSdkVersion に指定されているバージョンを対象にしている
このため、例えばbuild.gradle.ktsが以下のようになっているとAPI Level 29が使用される

android {
    defaultConfig {
        targetSdkVersion(29)
    }
}

であるならば、testの実行時だけtargetSdkVersionを書き換えれば、他のAPI Levelを使用するよう設定できる
どうするかというと

android {
    defaultConfig {
        targetSdkVersion(29)
    }
    testOptions {
        defaultConfig {
            targetSdkVersion(28)
        }
    }
}

これでUnit TestとInstrumented Testの両方に作用する
Unit Testだけに絞るなら以下のようにネストすれば良い

android {
    defaultConfig {
        targetSdkVersion(29)
    }
    testOptions {
        unitTests {
            defaultConfig {
                targetSdkVersion(28)
            }
        }
    }
}

AndroidからJDBC DriverでPostgreSQLに接続

ちょっと作りたいものがあるので, AndroidからDocker Desktop for Macで動かしているPostgreSQLにJDBC Driverで接続できるか実験

環境

  • Android Studio 3.5.2
  • Android Emulator 29.2.1
  • Nexus 5
  • PostgreSQL JDBC Driver 42.2.8
  • postgres:11.5 Docker image

実験用ソース

Android Studioの Start a new Android Studio Project から, Empty ActivityでMinimum API level 23なプロジェクトを作る
これに必要な分をちょっとだけいれたものを使う
まずはapp/build.gradleのdependenciesにドライバを追加する

dependencies {
    // 略
    implementation 'org.postgresql:postgresql:42.2.8'
    // 略
}

次に適当な接続してSQLを実行するコードを, MainActivity.ktに追加する

override fun onResume() {
    super.onResume()

    thread {
        val c = DriverManager.getConnection("jdbc:postgresql://10.0.2.2/postgres?user=postgres&password=password")
        val s = c.createStatement()
        val r = s.executeQuery("CREATE TABLE IF NOT EXISTS sample (id SERIAL PRIMARY KEY, number INTEGER)")
        r.close()
        s.close()
        c.close()
    }
}

ネットワークアクセスするのでパーミッションの記述をAndroidManifest.xmlに追加する

+ <uses-permission android:name="android.permission.INTERNET" />

Emulator と実機の比較

最近の端末を持っていないのでエミュレータを使うとして, 実機と同じように動作するかテストする
Android 6.0.1がインストールされたNexusとAPI 23のエミュレータで実行したところ, 同じように以下のExceptionが出力された
カスタマイズして出荷されている可能性があるので一概に言えないが, とりあえずエミュレータが実機と同じように動作すると判断する

E/AndroidRuntime: FATAL EXCEPTION: Thread-147
    Process: com.example.sample, PID: 2812
    java.sql.SQLException: No suitable driver
        at java.sql.DriverManager.getConnection(DriverManager.java:186)
        at java.sql.DriverManager.getConnection(DriverManager.java:144)
        at com.example.sample.MainActivity$onResume$1.invoke(MainActivity.kt:19)
        at com.example.sample.MainActivity$onResume$1.invoke(MainActivity.kt:8)
        at kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)

実験

いろいろなバージョンで試してみる
とりあえず上の実験でAPI 23はダメだったので, それ以降を順次試す
API 24, 25はクラスが見つからないのでダメ

java.lang.NoClassDefFoundError: Failed resolution of: Ljava/time/Duration;
 Caused by: java.lang.ClassNotFoundException: Didn't find class "java.time.Duration" on path: DexPathList[[zip file "/data/app/com.example.sample-1/base.apk"],nativeLibraryDirectories=[/data/app/com.example.sample-1/lib/x86_64, /system/lib64, /vendor/lib64]]

API 26から29はExceptionがthrowされているものの, SQLを実行した結果なのでOK

org.postgresql.util.PSQLException: No results were returned by the query.

追加実験 (追記: 2019/12/30)

API 24のExceptionはJava 8およびAPI 26で追加された java.time.Duration を使っているからダメなだけで, DriverをJava 7用にしたら問題なかったのではと思い立ったので実験
app/build.gradleのdependenciesを以下のように書き換える

  dependencies {
      // 略
-     implementation 'org.postgresql:postgresql:42.2.8'
+     implementation 'org.postgresql:postgresql:42.2.8.jre7'
      // 略
  }

他は変更せずに動かしてみるとAPI 26以降と同じ org.postgresql.util.PSQLException が投げられたので接続OK

結論

Android 7.0 NougatからJDBC DriverでPostgreSQLに接続できる
なおAndroid 7.0, 7.1 はJava 7用を使う必要があるが, Android 8.0 OreoからはJava 8用で構わない
ただし端末による