Android 12で「Targeting S+にはFLAG_IMMUTABLEまたはFLAG_MUTABLEが必要」を修正する

intermediate📱 Android2026-06-03| Android 12(API 31以上)、Java/Kotlin、minSdkまたはtargetSdkを31に引き上げた環境

Error Message

java.lang.IllegalArgumentException: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
#android 12#pendingintent#マイグレーション

targetSdkを31に上げた瞬間にクラッシュする

Play Storeの期限に合わせてtargetSdkVersionを31に更新してビルドをプッシュすると、数分以内にクラッシュレポートが殺到します:

java.lang.IllegalArgumentException: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent.
    at android.app.PendingIntent.checkFlags(PendingIntent.java:375)
    at android.app.PendingIntent.getBroadcast(PendingIntent.java:642)
    at com.yourapp.notifications.NotificationHelper.buildIntent(NotificationHelper.java:88)

FLAG_IMMUTABLEまたはFLAG_MUTABLEを含まないPendingIntentの呼び出しは、Android 12でハードクラッシュするようになりました。警告も、非推奨の猶予期間もなく、ランタイムで純粋なIllegalArgumentExceptionが発生します。

問題のある呼び出しをすべて見つける

コードに触れる前に、影響範囲を把握しましょう。プロジェクト全体でPendingIntentのファクトリメソッドを検索します:

# Android Studio: Edit → Find → Find in Files
PendingIntent.getActivity(
PendingIntent.getBroadcast(
PendingIntent.getService(
PendingIntent.getForegroundService(

またはターミナルから:

grep -rn "PendingIntent\.get" app/src/ --include="*.java" --include="*.kt"

すべての一致箇所が対象候補です。どれかが安全だと思い込まないでください。変更可能性フラグが欠けていれば、API 31以降でクラッシュします。

どちらのフラグを使うべきかを理解する

やみくもに修正する前に、ルールを把握しましょう:

  • FLAG_IMMUTABLEを使用するのは95%のケース — 通知、ディープリンク、スケジュール済みの処理、メディアセッションコールバック。PendingIntentに埋め込まれたIntentは作成後に変更されません。
  • FLAG_MUTABLEを使用するのは、システムがIntentに書き戻す必要がある場合のみ — 具体的には正確なアラーム用のcreatePendingIntentを使ったAlarmManager、またはPendingIntentを返信スロットとして使う場合(例:RemoteInputを使ったインライン通知返信)です。

迷った場合はFLAG_IMMUTABLEから始めましょう。何か壊れた場合(アラームが発火しない、返信アクションが失敗するなど)、その特定のものだけFLAG_MUTABLEに切り替えてください。

修正方法

Java — 修正前と修正後

// BEFORE — Android 12でクラッシュする
PendingIntent pi = PendingIntent.getBroadcast(
    context,
    requestCode,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT  // 変更可能性フラグが欠けている
);

// AFTER — 正しい書き方
PendingIntent pi = PendingIntent.getBroadcast(
    context,
    requestCode,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
);

Kotlin — 修正前と修正後

// BEFORE
val pi = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT
)

// AFTER
val pi = PendingIntent.getActivity(
    context,
    0,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

compatヘルパーを使う(新規コードに推奨)

API 23未満をサポートしていてよりクリーンなコードを求めるなら、AndroidX Core 1.8+のPendingIntentCompatを使いましょう:

// build.gradle
implementation "androidx.core:core:1.8.0"

// Kotlin
val pi = PendingIntentCompat.getBroadcast(
    context,
    requestCode,
    intent,
    PendingIntent.FLAG_UPDATE_CURRENT,
    false  // isMutable = false → API 23以降でFLAG_IMMUTABLE
)

バージョンチェックを内部で処理してくれるため、if (Build.VERSION.SDK_INT >= 23)のガードを各所に散らばらせる必要がありません。

常にこの修正が必要な典型的な箇所

// 通知アクションボタン
val actionIntent = PendingIntent.getBroadcast(
    context, 0, intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

// AlarmManagerの正確なアラーム(システムがelapsedRealtimeを書き込むためFLAG_MUTABLEが必要)
alarmManager.setExactAndAllowWhileIdle(
    AlarmManager.RTC_WAKEUP,
    triggerAtMillis,
    PendingIntent.getBroadcast(
        context, alarmId, intent,
        PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
    )
)

// 通知からのService起動
val serviceIntent = PendingIntent.getForegroundService(
    context, 0, intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

サードパーティライブラリもクラッシュする場合

クラッシュのスタックトレースが、制御できないライブラリ — 古いバージョンのFirebase Messaging、WorkManager、または通知ライブラリ — を指していることがあります。まずライブラリのバージョンを確認しましょう:

# 依存関係レポートを実行
./gradlew app:dependencies | grep -E "firebase-messaging|work-runtime|androidx.core"

Android 12での既知の安全な最低バージョン:

  • firebase-messaging: 22.0.0以上
  • androidx.work:work-runtime: 2.7.0以上
  • androidx.core:core: 1.7.0以上
  • com.google.android.gms:play-services-location: 19.0.0以上

まずこれらをアップグレードしましょう。ライブラリにおけるAndroid 12のPendingIntentクラッシュのほとんどは2021年末頃にパッチ済みです。

修正を確認する

コンパイルしてリリースするだけで済ませないでください。実際のAPI 31以降のデバイスまたはエミュレーターでテストしましょう:

# コマンドラインでAPI 31のAVDを作成
avdmanager create avd -n test31 -k "system-images;android-31;google_apis;x86_64"

# エミュレーターを起動
emulator -avd test31

# インストールして特定の例外のlogcatを監視
adb logcat | grep -E "IllegalArgumentException|PendingIntent"

PendingIntentを使用するすべての機能を一通り確認しましょう:すべての通知アクションをタップし、スケジュールされたアラームをトリガーし、ディープリンクをテストします。logcatがクリーンなままで機能が正常に動作すれば完了です。

また、UIテストも実行しましょう:

./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=com.yourapp.NotificationTest

深夜2時の緊急対応から学んだこと

このクラッシュが厄介なのは、targetSdkVersionを変更するまで完全に見えないからです。もっと早く検出できた方法が3つあります:

  • Lintルール — Android StudioにはAGP 7.0以降このチェック用のlintが含まれています。targetSdkを上げる前に./gradlew lintを実行してUnspecifiedImmutableFlagの警告を確認しましょう。
  • リリース前にAPI 31エミュレーターでテストする — 後から考えると当然ですが、古いOSを搭載した物理デバイスだけでテストするチームも多くいます。
  • バージョンアップ前にgrepするgrep -rn "PendingIntent.get" app/src/に5分かけることで、深夜3時間の作業を節約できます。

フラグの要件は任意ではありません。Android 12は、アプリがPendingIntentをIntent Redirectionによって悪用可能な状態のままにしてしまうことを防ぐため、OS レベルでこれを強制しています。API 31以降をターゲットにする際は、必須の衛生管理として扱いましょう。

Related Error Notes