PC パソコン

Android Studio「expected to be within a catch-all for an instruction where a monitor is held」の対処法【R8・AAR】

R8で縮小済みのAARを、アプリ側でもR8を有効にしてReleaseビルドすると、APKの生成には成功しても、実行時にARTがクラスを拒否してjava.lang.VerifyErrorでクラッシュすることがあります。本記事では、Google Issue Trackerの事例を基に、expected to be within a catch-all for an instruction where a monitor is heldの原因、対象構成、現時点での安全な切り分けと暫定対応を解説します。

代表的なエラーログ

java.lang.VerifyError: Verifier rejected class ...
... [0x34] expected to be within a catch-all for an instruction
where a monitor is held
(declaration of '...' appears in .../base.apk)

ビルド時のコンパイルエラーではなく、該当クラスが端末上で初めて読み込まれた時に発生する検証エラーです。そのため、Release APK/AABの作成成功だけでは問題を検出できません。

原因

報告例では、Kotlinのsynchronized内にあるチェックとエラーメッセージ生成を含むコードが、まず古いR8で縮小済みAARへ変換され、そのAARを新しいR8がアプリのReleaseビルドで再処理していました。生成されたDEXでは、ロック保持中に実行されるconst-string命令がcatch-allハンドラの範囲外に置かれます。ARTは、monitorを保持した状態で例外を送出し得る命令には、monitorを確実に解放するcatch-all経路が必要だと検証するため、クラスを拒否します。

重要:Issueでは不正な例外範囲の隙間がすでにproducer側AARのクラスファイルに存在すると確認されていますが、最終的な失敗を生むproducer/consumer最適化の正確な組み合わせはまだ特定されていません。2026年10月2日時点でIssue #559396690はAssignedで、公式の修正版や修正バージョンは公表されていません。

発生条件・確認されているバージョン

  • ライブラリをminifyEnabled trueでRelease AARとして作成する
  • その縮小済みAARをアプリへ組み込み、アプリもminifyEnabled trueでReleaseビルドする
  • 実行時に、影響を受けたライブラリの処理コンポーネントを生成または呼び出す

公式Issueの代表環境は、producerがAGP 4.2.2/R8 2.2.71/Kotlin 1.6.0/JDK 11、consumerがAGP 8.13.2/R8 8.13.19/JDK 17/minSdk 23です。Android 6(API 23)とAndroid 14の実機・エミュレータで再現しています。同じAARはconsumer側AGP 4.2.2、7.4.2、8.1.4では通過し、8.13.2と9.0.1では失敗しました。ただし、最初に影響を受けたバージョンは未特定です。consumer R8を8.13.23または9.4.17へ差し替えても再現したため、単純なR8更新だけで直るとは確認されていません。

推奨する対処手順

1. AARが事前縮小済みか確認する

自社ライブラリなら、libraryモジュールのRelease build typeで縮小していないか確認します。第三者製AARなら、提供元へ最適化済みか問い合わせます。問題がアプリ側R8だけで起きていると決めつけず、producer側の成果物も確認してください。

2. 可能なら未縮小AARを配布し、consumer rulesを同梱する

Android Developersは、通常のAARではライブラリを別工程で縮小する必要はなく、必要な保持規則をconsumerProguardFilesでアプリ側へ渡す方法を案内しています。

android {
    defaultConfig {
        consumerProguardFiles("consumer-rules.pro")
    }
    buildTypes {
        release {
            isMinifyEnabled = false
        }
    }
}

アプリの最終ビルドで全体最適化を一度だけ行う形にすると、「古いR8で縮小したAARを新しいR8が再処理する」経路を避けられます。

3. 影響コードを変更できる場合は、例外を起こし得る処理をsynchronizedの外へ移す

Issue報告者は、失敗していたチェックとメッセージ生成をsynchronizedの外へ移し、状態確認・登録・カウンタ更新だけをロック内に残すことで、代表的なAndroid 14の手動フローが動作したと報告しています。

// 概念例:実際の不変条件と競合条件を再検討すること
val error = validateAndBuildMessage(input)
synchronized(lock) {
    checkStateWithoutAllocatingMessage(error)
    register(input)
}

これはIssue報告者の暫定回避策です。全バージョン行列での再検証は未完了で、並行処理の意味を変える可能性があります。単純にコードを移動せず、ロックが守る不変条件と競合条件をレビューしてください。

4. 第三者製AARなら未縮小版または修正版を依頼する

アプリ開発者がAAR内部を変更できない場合は、提供元へ完全なスタックトレース、AGP/R8バージョン、再現端末、minifyEnabledの条件を添えて報告し、未縮小版または修正版を依頼します。アプリ全体の縮小を無効化すると原因切り分けには使えますが、サイズや最適化への影響があるため恒久策としては慎重に判断してください。

5. R8の入力ダンプを取得して報告する

再現プロジェクトを共有できない場合でも、R8のcompiler input dumpを取得できます。

mkdir -p /tmp/r8
./gradlew assembleRelease   -Dcom.android.tools.r8.dumpinputtodirectory=/tmp/r8   --no-daemon

ダンプにはアプリ入力、keep rulesなどが含まれるため、機密情報を確認し、公開添付ではなくIssue担当者が指定する非公開経路で共有してください。

代替策と避けたい対応

  • 切り分け:アプリ側またはライブラリ側の縮小を一時的に無効化し、二重最適化との関連を確認する
  • 代替:影響のない旧consumer AGPへ一時的に戻す。ただしIssueでは最初の影響版が未確定なので、必ず自分のRelease成果物で検証する
  • 非推奨:ライブラリ全体への広範な-keepを恒久追加する。既に不正な例外範囲を含むAARを直せない場合があり、最適化とサイズも悪化する
  • 注意:consumer R8 8.13.23/9.4.17への単純な差し替えは、公式Issueの条件では解消しなかった

修正確認の方法

  1. ReleaseでAARとアプリをクリーンビルドする
  2. 生成APK/AABへ実際に同じAARが入っていることを確認する
  3. 少なくとも報告されたAPI 23とAPI 34相当で、問題のクラスを初めてロードする操作まで実行する
  4. LogcatにVerifyErrorとexpected to be within a catch-allが出ないことを確認する
  5. 回避策で同期範囲を変更した場合は、負荷テストと並行実行テストも追加する

CIでは「Releaseビルド成功」だけでなく、縮小有効の成果物をインストールして影響コードを起動するsmoke testを入れると再発を検出しやすくなります。

まとめ

このエラーは、R8で事前最適化されたAARをアプリ側で再び縮小した際、monitor保持中の命令に必要なcatch-all保護が欠け、ART検証に失敗する事例です。現時点で公式修正版は未公開です。まず未縮小AAR+consumer rulesへ切り替えられるか確認し、コードを所有している場合のみ同期範囲を慎重に見直してください。最後にRelease端末テストで確認し、必要ならR8入力ダンプを添えて公式Issueへ情報を提供します。

参照した公式情報