Avatar ·

Ошибка PendingIntent: FLAG_IMMUTABLE или FLAG_MUTABLE обязательны для Android 12+

Всем привет! Я новичок в Android-разработке на Kotlin, и столкнулся с проблемой при переходе на targetSdk 31 (Android 12). Использую MediaSessionCompat для управления медиаплеером. При создании PendingIntent для кнопки "Play/Pause" в уведомлении (Notification) вылетает ошибка: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent. Мой код выглядит так: ```kotlin val intent = Intent(this, MainActivity::class.java) val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0) val playAction = NotificationCompat.Action( R.drawable.ic_play, "Play", pendingIntent ) ``` Я понимаю, что нужно добавить флаг, но не уверен, какой именно выбрать и как это повлияет на работу уведомлений. Также слышал, что есть ещё проблема с BroadcastReceiver для MediaButtonReceiver. Подскажите, пожалуйста, как правильно исправить эту ошибку для всех PendingIntent, связанных с MediaSession? Заранее спасибо! P.S. Использую compileSdk 33, targetSdk 33. Ошибка появляется только на устройствах с Android 12+. <<>>

Решение проблемы: добавляем флаги FLAG_IMMUTABLE или FLAG_MUTABLE

Ошибка возникает из-за изменений в Android 12 (API 31): все PendingIntent должны явно указывать свою изменяемость. Это повышает безопасность приложений. Вот как правильно исправить ваш код:

1. Для обычных Intent (Activity, Service)

В большинстве случаев используйте FLAG_IMMUTABLE — он безопаснее и рекомендуется Google:

val pendingIntent = PendingIntent.getActivity(
    this,
    0,
    intent,
    PendingIntent.FLAG_IMMUTABLE
)

2. Для MediaSession и уведомлений

Особенно важно правильно настроить PendingIntent для медиа-сессии. Вот полный пример для MediaSessionCompat:

// Создаём Intent для медиа-кнопок
val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON)
mediaButtonIntent.component = ComponentName(this, MediaButtonReceiver::class.java)

val mediaPendingIntent = PendingIntent.getBroadcast(
    this,
    0,
    mediaButtonIntent,
    PendingIntent.FLAG_IMMUTABLE
)

// Настраиваем MediaSession
val mediaSession = MediaSessionCompat(this, "MyMediaSession")
mediaSession.setMediaButtonReceiver(mediaPendingIntent)

// Для уведомления (Notification) также используем FLAG_IMMUTABLE
val playAction = NotificationCompat.Action(
    R.drawable.ic_play,
    "Play",
    mediaPendingIntent
)

3. Для MediaStyle уведомления

Если используете MediaStyle, добавьте setPendingIntentShowUi:

val contentIntent = PendingIntent.getActivity(
    this,
    0,
    Intent(this, MainActivity::class.java),
    PendingIntent.FLAG_IMMUTABLE
)

val notification = NotificationCompat.Builder(this, CHANNEL_ID)
    .setStyle(androidx.media.app.NotificationCompat.MediaStyle()
        .setMediaSession(mediaSession.sessionToken)
        .setShowActionsInCompactView(0, 1, 2))
    .setContentIntent(contentIntent)
    .build()

4. Когда использовать FLAG_MUTABLE?

FLAG_MUTABLE нужен только в редких случаях, когда вы планируете изменять параметры Intent после создания PendingIntent (например, для динамических данных). Для медиа-уведомлений он почти никогда не требуется.

5. Важные моменты для Android 12+

  • Все PendingIntent в вашем приложении должны иметь флаг — не только для медиа.
  • Если используете AlarmManager, NotificationManager или JobScheduler — проверьте их PendingIntent.
  • Для BroadcastReceiver с неявными Intent (например, ACTION_MEDIA_BUTTON) обязательно используйте FLAG_IMMUTABLE.

6. Полный рабочий пример

class MyMediaPlayerService : Service() {
    private lateinit var mediaSession: MediaSessionCompat
    
    override fun onCreate() {
        super.onCreate()
        
        // Создаём PendingIntent для кнопок
        val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON).apply {
            component = ComponentName(this@MyMediaPlayerService, MediaButtonReceiver::class.java)
        }
        val mediaPendingIntent = PendingIntent.getBroadcast(
            this, 0, mediaButtonIntent, PendingIntent.FLAG_IMMUTABLE
        )
        
        // Создаём MediaSession
        mediaSession = MediaSessionCompat(this, "MyService").apply {
            setMediaButtonReceiver(mediaPendingIntent)
            isActive = true
        }
        
        // Создаём уведомление
        val notification = createNotification(mediaPendingIntent)
        startForeground(NOTIFICATION_ID, notification)
    }
    
    private fun createNotification(mediaPendingIntent: PendingIntent): Notification {
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle("My Media Player")
            .setContentText("Playing...")
            .addAction(R.drawable.ic_play, "Play", mediaPendingIntent)
            .setStyle(androidx.media.app.NotificationCompat.MediaStyle()
                .setMediaSession(mediaSession.sessionToken))
            .build()
    }
}

После добавления флагов ошибка исчезнет. Рекомендую проверить весь проект на наличие других PendingIntent без флагов — Android Studio подсвечивает такие места.

<<>>

Альтернативное решение: использование MediaSessionCompat.setMediaButtonReceiver()

Если вы используете MediaSessionCompat с MediaButtonReceiver, можно упростить код, используя готовый метод, который сам создаёт правильный PendingIntent:

// Вместо ручного создания PendingIntent для медиа-кнопок
val mediaSession = MediaSessionCompat(this, "MySession")

// Используем встроенный метод (он сам добавит правильные флаги)
mediaSession.setMediaButtonReceiver(
    PendingIntent.getBroadcast(
        this,
        0,
        Intent(Intent.ACTION_MEDIA_BUTTON).apply {
            component = ComponentName(this, MediaButtonReceiver::class.java)
        },
        PendingIntent.FLAG_IMMUTABLE
    )
)

Дополнительная рекомендация

Для уведомлений с MediaStyle можно использовать NotificationCompat.MediaStyle.setMediaSession(), который автоматически связывает уведомление с сессией, и вам не нужно создавать отдельный PendingIntent для кнопок — система сама обработает нажатия через MediaSession.

Также проверьте, что ваш AndroidManifest.xml содержит правильную декларацию MediaButtonReceiver:

<receiver android:name="androidx.media.session.MediaButtonReceiver">
    <intent-filter>
        <action android:name="android.intent.action.MEDIA_BUTTON" />
    </intent-filter>
</receiver>

Этот подход минимизирует ручное создание PendingIntent и снижает риск ошибок с флагами.