依存ライブラリ更新で大爆死

※ChatGPTを使用して記事を作成しています。

はじめに

Android開発において、依存ライブラリはもはや欠かせない存在です。Retrofit、OkHttp、Glide、Hilt、Room、Compose関連など、公式・非公式を問わず、多くのライブラリが私たちの開発スピードを劇的に向上させてくれます。

しかし、その恩恵の裏には大きなリスクがあります。そう、「ライブラリの更新」です。最新バージョンにアップデートした途端、ビルドが通らなくなったり、実行時にクラッシュしたり、最悪の場合はリリース後にユーザー環境で大規模な不具合が発生することさえあります。

今回の記事では、私が実際に「依存ライブラリ更新で大爆死」した体験談を交えながら、なぜそんなことが起こるのか、どうすれば回避できるのかを掘り下げていきます。

ケース1: Retrofitの大規模更新

ある日、Retrofitを2.x系の最新バージョンに更新しました。Release Noteには「Bug Fix」「パフォーマンス改善」など良いことばかり書いてあり、何も考えずに libs.versions.tomlretrofit の行を書き換えてビルド。

最初はビルドが通ったので安心していました。しかし、いざ実行するとAPI通信部分で大量のクラッシュが発生。調べてみると、内部的に依存しているOkHttpのバージョンも引き上げられており、TLSまわりの挙動が変わっていたのです。

失敗コード例

// 旧バージョンでは問題なかったコード
val client = OkHttpClient.Builder()
    .retryOnConnectionFailure(true)
    .build()

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build()

新しいバージョンでは、このコードで一部の古いAndroid端末からの接続が失敗するようになりました。TLS1.2対応が必須になったためです。

修正版

val spec = ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS)
    .tlsVersions(TlsVersion.TLS_1_2, TlsVersion.TLS_1_3)
    .allEnabledCipherSuites(true)
    .build()

val client = OkHttpClient.Builder()
    .connectionSpecs(listOf(spec, ConnectionSpec.CLEARTEXT))
    .retryOnConnectionFailure(true)
    .build()

教訓: Retrofitの更新は単なるメソッド仕様の変更にとどまらず、内部の依存ライブラリ(OkHttp)の影響も受けるため、必ずRelease Noteをチェックし、古い端末での動作確認も忘れないこと。

ケース2: Jetpack ComposeのCompose BOM地獄

Composeを使っている方ならご存知の通り、ライブラリ群はBOM(Bill of Materials)で一括管理します。これ自体は便利なのですが、Compose CompilerやKotlinのバージョンと密接に関係しているため、相性問題が頻発します。

ある日、Kotlinを2.0に更新したタイミングでCompose BOMも最新に引き上げたところ、全画面で真っ白になる現象に遭遇しました。

失敗コード例

implementation(platform("androidx.compose:compose-bom:2025.01.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")

Kotlin 2.0では新しいCompose Compilerが必須だったにもかかわらず、古いCompose Compilerが使われていたため、実行時にクラッシュせず「描画されない」だけという最悪の症状になりました。

修正版

plugins {
    id("org.jetbrains.kotlin.android") version "2.0.0"
    id("org.jetbrains.kotlin.plugin.compose") version "2.0.0"
}

dependencies {
    implementation(platform("androidx.compose:compose-bom:2025.02.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
}

教訓: Compose関連は「Kotlin」「Compose Compiler」「BOM」の3点セットで必ずバージョン整合性を確認すること。1つでもズレると、描画すらされなくなる。

ケース3: ProGuard/R8との地獄絵図

ライブラリ更新の地雷原の一つがProGuard(R8)です。

あるライブラリを更新した途端、リリースビルドでのみクラッシュ。原因は内部クラスが難読化され、リフレクションで呼び出される箇所が全滅していたことでした。

失敗例

# 旧バージョンではこれで動いていた
-keep class com.example.mylib.** { *; }

新バージョンでは内部構造が大きく変わり、-keep の指定だけでは足りなくなりました。

修正版

# Retrofit + Gson の例
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.stream.** { *; }
-keep class sun.misc.Unsafe { *; }
-dontwarn okhttp3.**

教訓: ライブラリ更新時はProGuard設定の再確認が必須。特にリフレクションを使うライブラリは要注意。

なぜ爆死するのか?

  1. 依存関係が芋づる式に更新される
    → Retrofit更新 → OkHttp更新 → TLS仕様変更 → 古い端末で通信不能
  2. 非互換性が突然出る
    → ComposeとKotlinのバージョン整合性
  3. ドキュメント不足
    → ProGuardの必要設定が明示されていない
  4. CI/CDでの自動更新の罠
    → RenovateやDependabotで一括更新して壊れる

防止策

  • Release Noteを必ず読む
  • 段階的に更新する(一気に複数ライブラリを更新しない)
  • テストを拡充する(古い端末・リリースビルドを含む)
  • CIにスモークテストを組み込む
  • バージョン固定 (libs.versions.toml で安定版を管理)
  • 本番リリース前のステージング環境で必ず確認

まとめ

ライブラリ更新は「最新が最強」と思い込みがちですが、実際は爆弾が仕込まれていることが多いです。安易に更新して「動かない!戻そう!」となると依存関係の巻き戻しでさらに時間を浪費します。

一番大切なのは 「ライブラリ更新はプロジェクトの一大イベント」 という認識を持つことです。計画的に、慎重に、そしてテストを厚くして臨みましょう。

タイトルとURLをコピーしました