2026年9月11日金曜日

AGP 9 にしたら ML Kit のバーコードスキャンがリリースビルドでクラッシュするようになった

ユーザーからバーコード読み取り画面を開こうとするとクラッシュするという問い合わせがありました。手元で確認すると、リリースビルドでのみクラッシュし、デバッグビルドでは再現しませんでした。

スタックトレースを確認すると次のようになっていました。 java.lang.NullPointerException: Attempt to read from field '...zzi zzg.zza' on a null object reference at com.google.mlkit.vision.barcode.BarcodeScanning.getClient(play-services-mlkit-barcode-scanning@@18.3.1:3) at ... ML Kit の BarcodeScanning.getClient() の中で NullPointerException になっています。
R8 をかけたビルドでのみ発生するので、R8 が削除したものの一覧である app/build/outputs/mapping/release/usage.txt を見ると、BarcodeRegistrar の引数なしコンストラクタが削除されていました。 com.google.mlkit.vision.barcode.internal.BarcodeRegistrar: public void <init>() BarcodeRegistrar は ComponentRegistrar の実装クラスです。Firebase Components は AndroidManifest.xml の meta-data に書かれたクラス名から Class.forName し、引数なしコンストラクタで ComponentRegistrar の実装クラスを生成します。
コンストラクタが削除されるとレジストラを生成できず、barcode のコンポーネントが登録されないまま getClient() が呼ばれて NullPointerException になります。

firebase-components 16.1.0 に同梱されている keep rule は以下です。 -keep class * implements com.google.firebase.components.ComponentRegistrar メンバーの指定がないため、クラスは残りますがコンストラクタは残りません。

このルールは変わっていないのに以前のリリースビルドでは問題が起きていませんでした。 R8 のバージョンが変わった、つまり AGP の更新が原因ではないかと考え、AGP を 9.3.2 と 8.13.2 で切り替えてリリースビルドを比較しました。

firebase-components のバージョンも適用されるルールも同じですが、AGP 8.13.2 では BarcodeRegistrar.<init>() が残り、AGP 9.3.2 では削除されていました。なお android.enableR8.fullMode は設定したことがなく、full mode は AGP 8.0 からデフォルトでオンなので今回は関係ありません。

app の proguard-rules.pro に引数なしコンストラクタを明示した keep rule を追加しました。 -keep class * implements com.google.firebase.components.ComponentRegistrar { <init>(); } firebase-components は 19.0.0 で同じ内容にルールが修正されています。ただし com.google.mlkit:barcode-scanning は 17.3.0 が最新で、そこから引かれる firebase-components は 16.1.0 のため、ML Kit が新しい firebase-components を引くようになるまではアプリ側で補う必要があります。

修正後のリリースビルドでは usage.txt からコンストラクタの削除が消え、バーコードスキャン画面が正常に開くようになりました。

同じように AndroidManifest.xml の meta-data からリフレクションで生成される androidx.startup の Initializer について確認しましたが、startup-runtime 1.2.0 の consumer rules には -keepnames class * extends androidx.startup.Initializer に加えて引数なしコンストラクタを明示した -keep class * extends androidx.startup.Initializer { <init>(); } が含まれており、usage.txt でもコンストラクタは残っていました。