MediaSessionCompat FLAG_IMMUTABLE/FLAG_MUTABLE: полное решение на Kotlin

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: «Поломанный» плеер на Android 12 и выше

Представьте: вы с гордостью публикуете обновление своего приложения-плеера или подкаст-клиента. Проходит несколько дней, и в разделе отзывов появляются гневные комментарии: «После обновления музыка перестала воспроизводиться!», «Кнопки на заблокированном экране не работают!», «Приложение вылетает при попытке запустить трек!». Звучит знакомо? Скорее всего, вы столкнулись с той самой ошибкой, которая мучает тысячи разработчиков по всему миру:

MediaSessionCompat: Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent

Эта проблема — один из самых популярных вопросов на Stack Overflow (десятки тысяч просмотров), и она напрямую связана с изменениями в Android 12 (API level 31). Хорошая новость: исправление простое и займет у вас не больше 10 минут. В этой статье мы разберем причины ошибки, правильное решение на Kotlin и лучшие практики, чтобы ваше приложение снова работало как часы.

Почему возникает ошибка? Разбираемся с PendingIntent

Чтобы понять суть проблемы, нужно вспомнить, как работает MediaSessionCompat. Это компонент, который позволяет вашему приложению взаимодействовать с системными элементами управления медиа: кнопками на гарнитуре, экраном блокировки, панелью уведомлений. Для этого он создает PendingIntent — своего рода «отложенный вызов» для системных сервисов. Система использует этот Intent, чтобы сказать вашему приложению: «Пользователь нажал Play/Pause/Next».

Начиная с Android 12 (API 31), Google ужесточил требования к безопасности PendingIntent. Раньше разработчики могли не указывать флаг FLAG_IMMUTABLE или FLAG_MUTABLE, и система по умолчанию использовала изменяемый (mutable) вариант. Теперь же вы обязаны явно указать, может ли система изменять содержимое вашего Intent после его создания. Если вы этого не сделаете — получите краш приложения. Android 12+ просто не позволит создать такой PendingIntent.

FLAG_IMMUTABLE vs FLAG_MUTABLE: что выбрать?

  • FLAG_IMMUTABLE (рекомендуется): Intent нельзя изменить после создания. Это безопаснее, так как злоумышленник не сможет подменить данные. Используйте этот флаг в 90% случаев, в том числе для MediaSessionCompat.
  • FLAG_MUTABLE: Intent можно изменить. Нужен только в особых случаях, например, если вы используете NotificationCompat с действиями, которые требуют динамических данных, или при работе с AlarmManager.

Для MediaSessionCompat и медиа-уведомлений всегда используйте FLAG_IMMUTABLE. Это повышает безопасность и стабильность вашего приложения.

Пошаговое исправление на Kotlin

Давайте посмотрим, как выглядит типичный код, который вызывает ошибку, и как его исправить. Чаще всего проблема возникает в методе onCreate() вашего сервиса или медиа-браузера.

Неправильный код (вызовет краш на Android 12+)

// Старый способ — без указания флага
val pendingIntent = PendingIntent.getActivity(
    this,
    0,
    Intent(this, MainActivity::class.java),
    0 // <-- Ошибка! Нет FLAG_IMMUTABLE или FLAG_MUTABLE
)

Правильный код на Kotlin (исправление)

Просто добавьте PendingIntent.FLAG_IMMUTABLE в последний параметр:

val pendingIntent = PendingIntent.getActivity(
    this,
    0,
    Intent(this, MainActivity::class.java),
    PendingIntent.FLAG_IMMUTABLE // <-- Решение!
)

Если ваше приложение поддерживает более старые версии Android (ниже API 31), используйте условную проверку, чтобы избежать краша на старых устройствах:

val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    PendingIntent.FLAG_IMMUTABLE
} else {
    0
}

val pendingIntent = PendingIntent.getActivity(
    this,
    0,
    Intent(this, MainActivity::class.java),
    flags
)

Исправление для MediaSessionCompat (полный пример)

В контексте MediaSessionCompat ошибка обычно проявляется при создании MediaButtonReceiver или в коде вашего MediaBrowserService. Вот как должен выглядеть правильный код инициализации:

class MyMediaService : MediaBrowserServiceCompat() {
    private lateinit var mediaSession: MediaSessionCompat

    override fun onCreate() {
        super.onCreate()

        mediaSession = MediaSessionCompat(this, "MyMediaSession")

        // Создаем PendingIntent для MediaButtonReceiver
        val mediaButtonIntent = Intent(Intent.ACTION_MEDIA_BUTTON, null, this,
                                       MediaButtonReceiver::class.java)
        val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_IMMUTABLE
        } else {
            0
        }
        val mediaButtonPendingIntent = PendingIntent.getBroadcast(
            this,
            0,
            mediaButtonIntent,
            flags
        )

        mediaSession.setMediaButtonReceiver(mediaButtonPendingIntent)

        // Также исправляем PendingIntent для Activity (если используете)
        val sessionActivityIntent = Intent(this, MainActivity::class.java)
        val sessionActivityPendingIntent = PendingIntent.getActivity(
            this,
            0,
            sessionActivityIntent,
            flags
        )
        mediaSession.setSessionActivity(sessionActivityPendingIntent)

        sessionToken = mediaSession.sessionToken
    }
}

Обратите внимание: мы используем Build.VERSION.SDK_INT для обратной совместимости. На устройствах с Android 11 и ниже флаг FLAG_IMMUTABLE не требуется и может быть проигнорирован, но его передача не вызовет ошибки. Однако для чистоты кода лучше использовать условную логику.

Где еще может возникнуть эта ошибка?

Проблема с PendingIntent не ограничивается только MediaSessionCompat. Ошибка может появиться в любом месте, где вы создаете PendingIntent без указания флага. Вот типичные сценарии:

  • Уведомления (Notifications): при создании NotificationCompat.Action.
  • Будильники (AlarmManager): при планировании отложенных задач.
  • Виджеты (App Widgets): при обработке нажатий на элементы виджета.
  • BroadcastReceiver: при регистрации динамических ресиверов.

Проверьте весь код, где используется PendingIntent.getActivity(), PendingIntent.getService(), PendingIntent.getBroadcast() и PendingIntent.getForegroundService(). Если вы пропустили хотя бы один вызов — приложение упадет на Android 12+.

Лучшие практики и «грабли»

Чтобы больше не возвращаться к этой проблеме, запомните несколько правил:

<

Блоги

Рекомендаций по книгам