クイック修正リリースビルドがクラッシュする主な原因は、R8が実行時にリフレクションやJSONパースに必要となるコードを「デッドコード(不要なコード)」と判断して削除してしまったことにあります。これを防ぐには、proguard-rules.proファイルにkeepルールを追加します。
-keep class com.example.MyClass { *; }
(GSONやMoshiなどで使用する)データモデルのパッケージ全体を保持したい場合は、パッケージ以下をすべて指定します:
-keep class com.example.models.** { *; }
ルールが確実に適用されるよう、次のAPKまたはApp Bundleをビルドする前に、Clean Projectを実行してビルドキャッシュをクリアしてください。
なぜR8は動作中のコードを壊すのかisMinifyEnabled = trueに設定すると、ProGuardの後継であるR8がAPKの圧縮(シュリンク)を試みます。これにより、サイズが30%から50%削減されることも珍しくありません。R8は、Manifestに記載されたActivityなどの「エントリポイント」から開始し、コードパスを辿って実際に使用されているものを特定します。
問題はリフレクションで発生します。文字列でクラスを参照している場合(例:Class.forName("MyClass"))、R8はその接続を「認識」できません。そのため、そのクラスは未使用であると判断され、削除されてしまいます。また、R8がサイズ節約のためにMyClassをa.bにリネームすることもありますが、JSONライブラリが元の名前を探し続けるため、結果としてClassNotFoundExceptionが発生します。
発生しやすいケース- JSONシリアライズ: GSONやMoshiはリフレクションを使用して、JSONのキーをKotlin/Javaのフィールドにマッピングします。- JNI & ネイティブコード: C++コードは、R8が追跡できないハードコードされたクラス文字列を使用してJavaメソッドを呼び出すことがよくあります。- WebViewインターフェース: @JavascriptInterfaceを介してJavaScriptに公開されるJavaクラス。- 依存関係の注入 (DI): DaggerやHiltなどのライブラリは通常これを処理しますが、カスタムセットアップでは生成されたコードが失われる可能性があります。## 解決方法### 1. @Keepアノテーションを使用するテキストファイルを編集する代わりに、AndroidXライブラリの@Keepアノテーションを使用できます。これは、特定のクラスやメソッドを保護するための最も読みやすい方法です。
@Keep
class UserProfile(
val username: String,
val email: String
)
2. proguard-rules.proを更新するアノテーションを追加できないサードパーティ製ライブラリの場合は、app/proguard-rules.proを使用します。以下に3つの一般的なパターンを示します。
特定のブリッジクラスを保護する:
-keep class com.example.app.NativeBridge { *; }
すべてのモデルを保持する(APIレスポンスに不可欠):
-keep class com.example.app.models.** { *; }
インターフェースを実装するクラスをすべて保持する:
-keep class * implements com.example.app.MyPlugin { *; }
3. 難読化されたスタックトレースをデコードするクラッシュログにClassNotFoundException: a.b.cのような意味不明な名前が表示される場合、それを人間が読めるクラス名に戻すためのマッピングファイルが必要です。このファイルはビルドのたびに生成されます。
確認場所: app/build/outputs/mapping/release/mapping.txt
そのテキストファイル内でa.b.cを検索してください。元の名前が見つかったら、それをkeepルールに追加します。

