Beautiful HD Wallpapers の Android 版を v2.1.0 にアップデートした後、Firebase Crashlytics のダッシュボードに見慣れないエラーが積み上がり始めました。
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/function/Supplier;
Android 6.0.1 のユーザーだけが再現するこのクラッシュ、端末が古いせいだろうと最初は思いました。しかし原因は別のところにありました。Rork Max が生成するコードには Glide 5.x や AGP 9.x との組み合わせで Java 8 の API がそのまま使われることがあります。Android 8.0 未満の端末はこれをネイティブに処理できないため、特定のユーザー層だけがアプリを起動できない状態になります。
廣川政樹として個人開発を続けて12年、累計5,000万DL超のアプリを運営してきた中で、このタイプのクラッシュは繰り返し遭遇する問題です。以下で診断手順と coreLibraryDesugaringEnabled による修正を具体的に説明します。
なぜ「特定の OS バージョンだけ」クラッシュするのか
Android はバージョンごとに対応している Java API の範囲が異なります。Java 8 で追加された Stream、Optional、Supplier などのクラスは、Android 8.0(API 26)以上でのみ標準サポートされています。
Rork Max が生成するコードは最新の Android 環境を前提としているため、これらのクラスを使うライブラリ(Glide 5.x など)との組み合わせで、古い OS バージョンで動かないコードが出てくることがあります。
個人開発を続ける中でリリース後 28 日間で 50 件以上の NoClassDefFoundError クラッシュレポートが届き、Crash-free users が 99.4% まで落ちたときは、放置できない数字だと感じました。Play Store の評価に直結する問題です。
Step 1:Firebase Crashlytics でクラッシュを特定する
まず Crashlytics のダッシュボードで、どの端末・OS バージョンでクラッシュが集中しているか確認します。
「Issues」タブでエラーを開くと「Session details」にデバイス情報が表示されます。「Android 6.0.1 / 7.0 が多い」というパターンが見えたら、Java 8 デシュガリング問題の可能性が高いです。
確認するポイントは以下の通りです。
- OS バージョンが Android 8.0(API 26)未満に集中していないか
- スタックトレースに
NoClassDefFoundErrorやClassNotFoundExceptionが出ていないか - Glide、OkHttp、Firebase SDK など外部ライブラリが絡んでいないか
Rork Max プロジェクトの場合、Expo の managed workflow から ejected した後の android/app/build.gradle を確認します。
Step 2:app/build.gradle の compileOptions を確認する
Rork Max が生成した Android プロジェクトの android/app/build.gradle を開いてください。
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}sourceCompatibility と targetCompatibility が VERSION_1_8 になっていても、それだけでは Android 8.0 未満の実機での動作は保証されません。デシュガリング(Desugaring) の設定が別途必要です。
デシュガリングとは、Java 8 以上の構文を古い Android でも理解できる形に変換するプロセスです。有効にするには、同じ compileOptions ブロックに1行追加します。
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
coreLibraryDesugaringEnabled true
}
}
dependencies {
coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:2.0.4"
}この修正を行った後にビルドし直したところ、Android 6.0.1 ユーザーのクラッシュが完全に消えました。v2.1.0 のリリースに間に合わせることができ、ユーザーレビューの星評価の低下も防ぐことができました。
Step 3:Rork の AI に修正を依頼する際のプロンプト
Rork の AI に修正を依頼する際は、具体的なエラーメッセージとファイル名を添えて指示します。漠然と「クラッシュを直して」と伝えても、AI は何を変えればいいか判断できません。
修正依頼のポイントは「対象ファイル」「追加する行」「エラーの再現条件」の3点を明示することです。このように変更スコープを限定することで、AI が余計なファイルを書き換えるリスクを減らせます。Rork の AI は親切ですが、スコープを広く与えると関係のないコードまで変えることがあります。
Step 4:AGP バージョンと Glide の組み合わせに注意する
AGP(Android Gradle Plugin)9.x と Glide 5.0.x を組み合わせた場合に特にこの問題が出やすいです。Rork Max が生成するプロジェクトはデフォルトで最新の AGP を使いますが、desugar_jdk_libs のバージョンが古い場合も同様のクラッシュが起きます。
desugar_jdk_libs は 2.0.4 以上を使うことを推奨します。古いバージョンでは一部の Java 8 API が正しく変換されないケースがあります。Glide の Kotlin API や Coroutines 統合を使っている場合も同じ設定で対応できます。
Step 5:段階公開でリスクを最小化する
個人開発者にとって、リリース後のクラッシュは評価とユーザー離脱に直結します。Play Store の段階公開(5% → 25% → 50% → 100%)を使うことで、被害を最小限に抑えられます。
個人開発の現場で使っている監視基準は以下の通りです。
Crash-free users: 各フェーズで 99.7% 以上をキープするANR rate: 0.20% 未満をキープする- 各フェーズで 24〜48 時間の様子見期間を設ける
5% の段階で Crash-free users が 99.4% に落ちたら、それ以上展開せずに原因を特定します。今回の Java 8 デシュガリング問題は、このプロセスで段階公開の 25% 到達前に発見できました。全ユーザーへの展開前に対処できたことが、評価への影響を最小化できた理由です。
Step 6:リリース前の予防策として古い端末でのテストを組み込む
Rork Max で生成したコードを AGP 9.x や最新ライブラリと組み合わせて使う場合、リリース前に Android 7.0〜8.0 の実機またはエミュレーターでの動作確認を必ず行うことをお勧めします。
個人開発では実機テストの時間が限られるため、私は Android Studio のエミュレーターで API 24(Android 7.0)と API 28(Android 9)の2つを常時用意しています。このテスト環境があるだけで、Java 8 デシュガリング問題の大半はリリース前に発見できます。
テスト手順は以下のような流れです。まず Rork Max でビルドした APK を API 24 エミュレーターにインストールします。次にアプリの主要な画面をひと通り操作し、スタックトレースが出ないことを確認します。最後に API 28 エミュレーターで同じ操作を繰り返し、新旧の OS で動作に差がないことを確認します。
この工程を CI に組み込むのが理想的ですが、個人開発の場合は手動でも十分です。リリース前の 5〜10 分で、リリース後の評価低下リスクを大幅に下げることができます。
Rork の AI でコードを生成するたびに依存ライブラリが変わる可能性があるため、新機能を追加するたびにこの確認を行うことを習慣にするとよいでしょう。
全体を振り返って:Crashlytics の確認から始める
Rork Max で生成した Android アプリが「特定の古い端末だけクラッシュする」という症状のとき、最初にやることは Crashlytics でクラッシュの OS バージョン分布を見ることです。「Android 8.0 未満に集中」していれば、coreLibraryDesugaringEnabled true と desugar_jdk_libs の追加で大半のケースが解決します。
まずは Firebase Crashlytics を Rork Max プロジェクトに設定するところから始めるとよいでしょう。