ANTIGRAVITY LABEN
記事一覧/アプリ開発
アプリ開発/2026-06-22上級

リリースビルドだけで落ちる — R8 フルモードが Gson のマッピングを壊したときの調べ方と直し方

デバッグでは動くのにリリースだけでクラッシュする。R8 フルモードが Gson のリフレクションを壊す典型と、mapping.txt を使った原因特定、keep ルールの最小化までを実装視点でまとめます。

android29r8proguardgsonrelease-build

プレミアム記事

ある朝、Crashlytics に見覚えのない NullPointerException が並んでいました。スタックトレースの一番上は a.b.c.d のような難読化された記号だけで、自分が書いた覚えのないクラス名です。手元の端末でデバッグビルドを動かしても、まったく再現しません。リリースして数時間後、特定の画面を開いたユーザーだけが落ちている——個人開発で広告付きの壁紙アプリを長く運用してきた中で、この「リリースビルドだけで落ちる」種類のクラッシュには何度か足をすくわれてきました。

原因はたいてい同じ場所にあります。R8 のフルモード難読化が、Gson がリフレクションで読み書きしているデータクラスのフィールド名を書き換えてしまい、JSON との対応が取れなくなっているのです。デバッグビルドは難読化が無効なので何事もなく動き、リリースでだけ壊れる。だから再現が難しく、原因にたどり着くまでに時間を溶かします。

私自身が実際にこの問題を踏んだときの調べ方と、最終的にどう直したかを書き残しておきます。「とりあえず全部 keep すれば消える」というのは事実ですが、それは設計判断を放棄しているだけで、難読化の恩恵もアプリサイズの縮小も同時に捨てています。壊れたクラスだけを最小限に守る、というところまで一緒に詰めていきます。

なぜデバッグでは起きずリリースでだけ落ちるのか

Android のリリースビルドでは、minifyEnabled true のときに R8 がコードの圧縮(未使用コードの削除)と難読化(クラス名・メソッド名・フィールド名の短縮)を行います。AGP 8.0 以降は R8 が標準のコードシュリンカーで、さらに「フルモード」が既定で有効になりました。フルモードは旧 ProGuard 互換モードより踏み込んだ最適化を行い、keep ルールで明示的に守られていないものは、より積極的に削除・改名します。

問題は、Gson がフィールドを「名前」で突き合わせている点です。{"display_name": "夕焼け"} という JSON を data class Category(val displayName: String) にマッピングするとき、Gson は実行時にリフレクションでフィールド名を読み、そこにアノテーションがなければフィールド名そのものを JSON キーとして使おうとします。ところが R8 がそのフィールドを a という名前に書き換えてしまうと、Gson から見えるのは a であって displayName ではありません。結果、JSON のキーと一致せず、その値は埋まらないまま null になります。

デバッグビルドでは minifyEnabled false なのでフィールド名は元のまま残り、何も壊れません。この非対称性が、再現の難しさの正体です。コードを書いた本人が手元で動かす限り、まず再現しないのです。

手元でリリースの挙動を再現する

原因を推測で潰すと時間がかかります。まず、ローカルでリリースビルドの挙動を確実に再現させてしまうのが近道です。

# リリースビルドを生成して実機/エミュレータに入れる
./gradlew assembleRelease
 
# 署名済みリリース APK をインストール(debuggable でなくても adb install は通る)
adb install -r app/build/outputs/apk/release/app-release.apk

これで手元でも本番と同じ難読化済みコードが動きます。「リリースだけで落ちる」と聞くと本番でしか調べられない気がしますが、assembleRelease を一度回すだけで、自分の端末で何度でも再現できる状態に持ち込めます。私はここを飛ばして本番ログとにらめっこしていた時期があり、それが一番の時間の無駄でした。

落ちる画面を開いてクラッシュさせ、adb logcat でスタックトレースを取ります。ただしこの時点のスタックトレースはまだ難読化された記号のままなので、次に元の名前へ戻す作業が必要になります。

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
デバッグでは再現せずリリースだけで落ちるクラッシュを、ローカルで確実に再現させる手順を持ち帰れる
R8 フルモードが Gson のリフレクションを壊す仕組みを理解し、mapping.txt と retrace で難読化済みスタックトレースを復元できる
「全部 keep」で逃げずに、壊れたクラスだけを最小限の keep ルールで守る設計判断を自分のアプリに適用できる
Stripe による安全な決済 · いつでもキャンセル可能

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Antigravity Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

アプリ開発2026-08-31
有料導線とレビュー訴求が重なるのを、ダイアログではなくゲートで止めています
有料プラン案内・レビュー訴求・リワード広告のダイアログが同じ瞬間に重なった実例から、表示可否の判断を各ダイアログから中央ゲートへ移した設計と、エージェントに不変条件を渡す指示の書き方、CI での機械検査までをまとめました。
アプリ開発2026-08-18
API レベル 36 の期限に向けた、6本のアプリを上げる順番と段階公開の刻み方
Google Play の API レベル 36 要件は6本すべてに同じ期日で来ます。締切が同じなら決めるのは順番だけです。壊れやすさの棚卸しと、段階公開の刻み幅を必要観測数から逆算する手順をまとめました。
アプリ開発2026-07-18
「Compose ファースト」の後で、View の画面をどの順に移すか — 画面数ではなく変更頻度で決める
Google が Android 開発を Compose ファーストと表明しました。View ベースの画面をどの順で移すかを、画面数ではなく git の変更履歴から決める判断軸と、実際に動かしたスコアリングスクリプトをまとめます。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます