Создание уведомления

Уведомления позволяют пользователям получать краткую и актуальную информацию о событиях в приложении, когда оно не используется. В этом документе показано, как создать уведомление с различными функциями. Чтобы узнать, как уведомления выглядят на устройствах Android, ознакомьтесь с общими сведениями об уведомлениях. Пример кода, в котором используются уведомления, можно найти в приложении SociaLite на GitHub.

В коде на этой странице используются API NotificationCompat из библиотеки AndroidX. Эти API позволяют добавлять функции, доступные только в новых версиях Android, сохраняя при этом совместимость с Android 9 (уровень API 28). Однако некоторые функции, например быстрый ответ, не будут работать в более ранних версиях.

Как создать базовое уведомление

В самом простом и компактном виде (также известном как свернутый вид) уведомление содержит значок, заголовок и небольшой объем текста. В этом разделе показано, как создать уведомление, при нажатии на которое пользователь может запустить действие в вашем приложении.

Рисунок 1. Уведомление со значком, заголовком и текстом.

Подробнее о структуре уведомлений…

Объявите динамическое разрешение

В Android 13 (API уровня 33) и более поздних версиях поддерживается динамическое разрешение на отправку уведомлений, не подпадающих под исключения (в том числе от активных служб).

Разрешение, которое необходимо объявить в файле манифеста приложения, показано в следующем фрагменте кода:

<manifest ...>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <application ...>
        ...
    </application>
</manifest>

Подробнее о динамических разрешениях на уведомления…

Задайте содержание уведомления

Для начала задайте контент и канал уведомления с помощью объекта NotificationCompat.Builder. В примере ниже показано, как создать уведомление со следующими параметрами:

  • Небольшой значок, заданный с помощью setSmallIcon(). Это единственный обязательный контент, который виден пользователям.

  • Название, заданное с помощью setContentTitle().

  • Основной текст, заданный с помощью setContentText().

  • Приоритет уведомления, заданный setPriority(). Приоритет определяет, насколько навязчивым будет уведомление на устройствах с Android 7.1 и более ранними версиями. Для Android 8.0 и более поздних версий вместо этого задайте важность канала, как показано в следующем разделе.

val textTitle = "Title"
val textContent = "Content"
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle(textTitle)
    .setContentText(textContent)
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

Конструктор NotificationCompat.Builder требует указать идентификатор канала. Это необходимо для совместимости с Android 8.0 (уровень API 26) и более поздними версиями, но игнорируется более ранними версиями.

По умолчанию текст уведомления усекается до одной строки. Вы можете показать дополнительную информацию, создав разворачивающееся уведомление.

Рисунок 2. Раскрывающееся уведомление в свернутом и развернутом виде.

Если вы хотите, чтобы уведомление было длиннее, вы можете включить разворачивающееся уведомление, добавив шаблон стиля с помощью setStyle(). Например, следующий код создает текстовое поле большего размера:

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Much longer text that cannot fit one line...")
    .setStyle(NotificationCompat.BigTextStyle()
        .bigText("Much longer text that cannot fit one line..."))
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)

Дополнительную информацию о других стилях больших уведомлений, в том числе о том, как добавить изображение и элементы управления воспроизведением медиаконтента, можно найти в статье Как создать раскрывающееся уведомление.

Как создать канал и задать уровень важности

Чтобы отправлять уведомления на устройствах с Android 8.0 и более поздних версий, зарегистрируйте канал уведомлений приложения в системе, передав экземпляр NotificationChannel в createNotificationChannel(). Следующий код заблокирован условием в версии SDK_INT:

fun createNotificationChannel(context: Context) {
    // Create the NotificationChannel, but only on API 26+ because
    // the NotificationChannel class is not in the Support Library.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val name = context.getString(R.string.channel_name)
        val descriptionText = context.getString(R.string.channel_description)
        val importance = NotificationManager.IMPORTANCE_DEFAULT
        val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
            description = descriptionText
        }
        // Register the channel with the system.
        val notificationManager: NotificationManager =
            context.getSystemService(NotificationManager::class.java) as NotificationManager
        notificationManager.createNotificationChannel(channel)
    }
}

Поскольку канал уведомлений необходимо создать до публикации уведомлений в Android 8.0 и более поздних версиях, выполните этот код при запуске приложения. Повторные вызовы безопасны, поскольку создание существующего канала ни к чему не приводит.

Конструктор NotificationChannel требует указания уровня важности с помощью константы NotificationManager. Определяет, как прерывать пользователя. Чтобы обеспечить поддержку Android 7.1 и более ранних версий, также задайте приоритет с помощью setPriority(), как показано в примере выше.

Хотя вы должны задать важность или приоритет, система не гарантирует, что оповещение будет работать именно так, как вы настроили. Система может скорректировать его на основе других факторов, а пользователь может в любой момент изменить уровень важности канала.

Подробнее об уровнях важности уведомлений…

Как настроить действие при нажатии на уведомление

Каждое уведомление должно реагировать на нажатие, обычно открывая действие в вашем приложении, соответствующее уведомлению. Для этого укажите намерение контента, определенное с помощью объекта PendingIntent, и передайте его в setContentIntent().

В следующем фрагменте кода показано, как создать базовое намерение, чтобы при нажатии на уведомление открывалось действие:

// Create an explicit intent for an Activity in your app.
val intent = Intent(context, AlertDetails::class.java).apply {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
val pendingIntent: PendingIntent =
    PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    // Set the intent that fires when the user taps the notification.
    .setContentIntent(pendingIntent)
    .setAutoCancel(true)

Этот код вызывает метод setAutoCancel(), который автоматически удаляет уведомление, когда пользователь нажимает на него.

Флаги intent в предыдущем примере сохраняют ожидаемый пользователем способ навигации после того, как он открывает ваше приложение с помощью уведомления. Его можно использовать в зависимости от типа запускаемого действия, которое может быть одним из следующих:

  • Действие, которое существует исключительно для ответов на уведомление. При обычном использовании приложения у пользователя нет причин переходить к этому действию, поэтому оно запускает новую задачу, а не добавляется в существующую задачу и обратный стек приложения. Это тип намерения, созданный в предыдущем примере.

  • Действие, которое выполняется в приложении в обычном режиме. В этом случае при запуске действия создается обратный стек, чтобы кнопки "Назад" и "Вверх" работали так, как ожидает пользователь.

Показ уведомления

Чтобы уведомление появилось, вызовите NotificationManagerCompat.notify(), передав ему уникальный идентификатор уведомления и результат NotificationCompat.Builder.build(). Пример:

with(NotificationManagerCompat.from(context)) {
    if (ActivityCompat.checkSelfPermission(
            context,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        // TODO: Consider calling ActivityCompat#requestPermissions here
        // to request the missing permissions, and then overriding
        // public fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>,
        //                                        grantResults: IntArray)
        // to handle the case where the user grants the permission. See the documentation
        // for ActivityCompat#requestPermissions for more details.

        return@with
    }
    // notificationId is a unique int for each notification that you must define.
    notify(notificationId, builder.build())

Сохраните идентификатор уведомления, который вы передаете в NotificationManagerCompat.notify(), поскольку он понадобится вам, когда вы захотите обновить или удалить уведомление.

Кроме того, чтобы протестировать базовые уведомления на устройствах с Android 13 и более поздними версиями, включите уведомления вручную или создайте диалоговое окно для запроса уведомлений.

Добавьте кнопки действий

В уведомлении может быть до трех кнопок, позволяющих пользователю быстро выполнить действие, например отложить напоминание или ответить на текстовое сообщение. Однако эти командные кнопки не должны дублировать действие, которое выполняется, когда пользователь нажимает на уведомление.

Рисунок 3. Уведомление с одной командной кнопкой.

Чтобы добавить командную кнопку, передайте PendingIntent в метод addAction(). Это похоже на настройку действия по умолчанию при нажатии на уведомление, но вместо запуска действия вы можете сделать что-то другое, например запустить BroadcastReceiver, который выполняет задание в фоновом режиме, чтобы действие не прерывало работу уже открытого приложения.

Например, в следующем коде показано, как отправить широковещательное сообщение определенному получателю:

val ACTION_SNOOZE = "snooze"
val snoozeIntent = Intent(context, MyBroadcastReceiver::class.java).apply {
    action = ACTION_SNOOZE
    putExtra(EXTRA_NOTIFICATION_ID, 0)
}
val snoozePendingIntent: PendingIntent =
    PendingIntent.getBroadcast(context, 0, snoozeIntent, PendingIntent.FLAG_IMMUTABLE)
val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setContentIntent(pendingIntent)
    .addAction(R.drawable.snooze, context.getString(R.string.snooze),
        snoozePendingIntent)

Подробнее о том, как создать BroadcastReceiver для выполнения фоновых задач, рассказывается в обзоре трансляций.

Если вы хотите создать уведомление с кнопками управления воспроизведением, например для приостановки и пропуска треков, узнайте, как создать уведомление с элементами управления мультимедиа.

Добавьте действие для быстрого ответа

Функция быстрого ответа, представленная в Android 7.0 (уровень API 24), позволяет пользователям вводить текст прямо в уведомлении. Затем текст передается в приложение без открытия действия. Например, вы можете использовать действие быстрого ответа, чтобы пользователи могли отвечать на текстовые сообщения или обновлять списки задач прямо из уведомления.

Рисунок 4. Нажав кнопку "Ответить", вы откроете поле для ввода текста.

В уведомлении появляется дополнительная кнопка, которая открывает поле для ввода текста. Когда пользователь закончит вводить текст, система прикрепит текстовый ответ к намерению, которое вы указали для действия уведомления, и отправит намерение в ваше приложение.

Добавьте кнопку ответа

Чтобы создать действие уведомления, поддерживающее быстрый ответ, выполните следующие действия:

Создайте экземпляр RemoteInput.Builder, который можно добавить в действие уведомления. Конструктор этого класса принимает строку, которую система использует в качестве ключа для ввода текста. Затем приложение использует этот ключ для получения текста ввода.

// Key for the string that's delivered in the action's intent.
val replyLabel: String = context.resources.getString(R.string.reply_label)
val remoteInput: RemoteInput = RemoteInput.Builder(KEY_TEXT_REPLY).run {
    setLabel(replyLabel)
    build()
}

Создайте PendingIntent для действия ответа.

// Build a PendingIntent for the reply action to trigger.
val replyPendingIntent: PendingIntent =
    PendingIntent.getBroadcast(context,
        conversationId,
        getMessageReplyIntent(conversationId),
        PendingIntent.FLAG_MUTABLE)

Прикрепите объект RemoteInput к действию с помощью addRemoteInput().

// Create the reply action and add the remote input.
val action: NotificationCompat.Action =
    NotificationCompat.Action.Builder(R.drawable.reply,
        context.getString(R.string.reply_label), replyPendingIntent)
        .addRemoteInput(remoteInput)
        .build()

Применить действие к уведомлению и выдать уведомление.

// Build the notification and add the action.
val newMessageNotification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_message)
    .setContentTitle(context.getString(R.string.title))
    .setContentText(context.getString(R.string.content))
    .addAction(action)
    .build()

// Issue the notification.
NotificationManagerCompat.from(context).notify(notificationId, newMessageNotification)

Когда пользователь активирует действие уведомления, система предлагает ему ввести ответ, как показано на рисунке 4.

Получить пользовательский ввод из ответа

Чтобы получить введенные пользователем данные из интерфейса ответа на уведомление, вызовите метод RemoteInput.getResultsFromIntent(), передав ему объект Intent, полученный объектом BroadcastReceiver:

private fun getMessageText(intent: Intent): CharSequence? {
    return RemoteInput.getResultsFromIntent(intent)?.getCharSequence(KEY_TEXT_REPLY)
}

После обработки текста обновите уведомление, вызвав NotificationManagerCompat.notify() с тем же идентификатором и тегом, если он используется. Это необходимо, чтобы скрыть интерфейс быстрого ответа и подтвердить пользователю, что его ответ получен и обработан правильно.

// Build a new notification, which informs the user that the system
// handled their interaction with the previous notification.
val repliedNotification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.message)
    .setContentText(context.getString(R.string.replied))
    .build()

// Issue the new notification.
NotificationManagerCompat.from(context).notify(notificationId, repliedNotification)

Как получить другие данные

Обработка других типов данных в RemoteInput выполняется аналогично. В примере ниже в качестве входных данных используется изображение.

val replyLabel: String = context.resources.getString(R.string.reply_label)
val remoteInput: RemoteInput = RemoteInput.Builder(KEY_REPLY).run {
    setLabel(replyLabel)
    // Allow for image data types in the input.
    // This method can be used again to allow for other data types.
    setAllowDataType("image/*", true)
    build()
}

Вызовите RemoteInput#getDataResultsFromIntent и извлеките соответствующие данные.

class ReplyReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        val dataResults = RemoteInput.getDataResultsFromIntent(intent, KEY_REPLY)
        val imageUri: Uri? = dataResults?.get("image/*") as? Uri

        if (imageUri != null) {
            // Extract the image
            context.contentResolver.openInputStream(imageUri)?.use { inputStream ->
                val bitmap = BitmapFactory.decodeStream(inputStream)
                // Display the image
                // ...
            }
        }
    }

    companion object {
        const val KEY_REPLY = "key_reply"
        const val KEY_TEXT_REPLY = "key_text_reply"
    }
}

При работе с этим новым уведомлением используйте контекст, который передается методу onReceive() получателя.

Добавьте ответ в нижнюю часть уведомления, вызвав setRemoteInputHistory(). Однако если вы создаете мессенджер, создайте уведомление в стиле сообщения и добавьте новое сообщение в чат.

Дополнительные советы по уведомлениям из мессенджеров можно найти в разделе Рекомендации для мессенджеров.

Показать срочное сообщение

Приложению может потребоваться показать срочное сообщение, например о входящем вызове или звонке будильника. В таких случаях вы можете связать с уведомлением полноэкранный интент.

Когда уведомление вызывается, пользователи видят одно из следующих сообщений в зависимости от статуса блокировки устройства:

  • Если устройство пользователя заблокировано, появляется полноэкранное действие, закрывающее экран блокировки.
  • Если устройство пользователя разблокировано, уведомление появляется в развернутом виде с вариантами действий.

В следующем фрагменте кода показано, как связать уведомление с полноэкранным интентом:

val fullScreenIntent = Intent(context, ImportantActivity::class.java)
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0,
    fullScreenIntent, PendingIntent.FLAG_IMMUTABLE)

val builder = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setContentTitle("My notification")
    .setContentText("Hello World!")
    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
    .setFullScreenIntent(fullScreenPendingIntent, true)

Как настроить видимость уведомлений на заблокированном экране

Чтобы указать, сколько информации будет видно в уведомлении на заблокированном экране, вызовите setVisibility() и укажите одно из следующих значений:

  • VISIBILITY_PUBLIC – уведомления будут полностью видны на заблокированном экране.

  • VISIBILITY_SECRET – уведомления не показываются на заблокированном экране.

  • VISIBILITY_PRIVATE – на заблокированном экране показывается только основная информация, например значок уведомления и заголовок контента. Полный текст уведомления не отображается.

Если вы зададите значение VISIBILITY_PRIVATE, то сможете также указать альтернативную версию уведомления, в которой будут скрыты некоторые сведения. Например, приложение для обмена SMS может показывать уведомление "У вас 3 новых текстовых сообщения", но скрывать их содержимое и отправителей. Чтобы предоставить такое альтернативное уведомление, сначала создайте его с помощью NotificationCompat.Builder, как обычно. Затем прикрепите альтернативное уведомление к обычному с помощью setPublicVersion().

Помните, что пользователь всегда имеет полный контроль над тем, видны ли его уведомления на заблокированном экране, и может управлять ими на основе каналов уведомлений вашего приложения.

Как изменить уведомление

Чтобы обновить уведомление после его отправки, снова вызовите NotificationManagerCompat.notify(), передав ему тот же идентификатор, который вы использовали ранее. Если предыдущее уведомление было закрыто, создается новое.

Вы можете вызвать метод setOnlyAlertOnce(), чтобы уведомление прерывало пользователя (звуком, вибрацией или визуальными подсказками) только при первом появлении, а не при последующих обновлениях.

Как удалить уведомление

Уведомления остаются видимыми, пока не произойдет одно из следующих событий:

  • Пользователь закрывает уведомление.
  • Пользователь нажимает на уведомление, если вы вызываете setAutoCancel() при создании уведомления.
  • Вы вызываете cancel() для определенного идентификатора уведомления. Этот метод также удаляет текущие уведомления.
  • Вы вызываете метод cancelAll(), который удаляет все ранее отправленные уведомления.
  • Истекло время, заданное при создании уведомления с помощью setTimeoutAfter(). При необходимости вы можете отменить уведомление до истечения указанного времени ожидания.

Рекомендации для мессенджеров

При создании уведомлений для приложений обмена сообщениями и чатов учитывайте приведенные здесь рекомендации.

Используйте MessagingStyle

Начиная с Android 7.0 (уровень API 24) в Android доступен шаблон стиля уведомлений, предназначенный специально для сообщений. С помощью класса NotificationCompat.MessagingStyle можно изменить несколько ярлыков, которые показываются в уведомлении, в том числе название чата, дополнительные сообщения и контент.

В следующем фрагменте кода показано, как настроить стиль уведомления с помощью класса MessagingStyle.

val message1 = NotificationCompat.MessagingStyle.Message(
    messages[0].text,
    messages[0].time,
    messages[0].sender
)
val message2 = NotificationCompat.MessagingStyle.Message(
    messages[1].text,
    messages[1].time,
    messages[1].sender
)
notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setSmallIcon(R.drawable.ic_logo)
    .setStyle(
        NotificationCompat.MessagingStyle(Person.Builder().setName("Me").build())
            .addMessage(message1)
            .addMessage(message2)
    )
    .build()

Начиная с Android 9.0 (уровень API 28), для оптимального отображения уведомления и аватаров также необходимо использовать класс Person.

При использовании NotificationCompat.MessagingStyle выполните следующие действия:

  • Вызовите MessagingStyle.setConversationTitle(), чтобы задать название для групповых чатов, в которых больше двух участников. В качестве названия можно использовать название группового чата или, если его нет, список участников чата. В противном случае сообщение может быть ошибочно принято за часть личной переписки с отправителем последнего сообщения в чате.
  • Используйте метод MessagingStyle.setData(), чтобы включить медиасообщения, например изображения. Поддерживаются MIME-типы, соответствующие шаблону image/*.

Как использовать быстрый ответ

Функция быстрого ответа позволяет пользователю ответить на сообщение прямо в уведомлении.

  • После того как пользователь ответит на сообщение, используйте MessagingStyle.addMessage(), чтобы обновить уведомление MessagingStyle. Не отменяйте и не удаляйте его. Если не отменять уведомление, пользователь сможет отправить несколько ответов.
  • Чтобы сделать действие быстрого ответа совместимым с Wear OS, вызовите Action.WearableExtender.setHintDisplayInlineAction(true).
  • Используйте метод addHistoricMessage(), чтобы добавить в уведомление предыдущие сообщения и предоставить пользователю контекст для быстрого ответа.

Включить быстрые ответы

  • Чтобы включить быстрые ответы, вызовите setAllowGeneratedResponses(true) для действия ответа. В результате быстрые ответы будут доступны пользователям, когда уведомление будет передано на устройство Wear OS. Быстрые ответы генерируются моделью машинного обучения, которая работает только на часах и использует контекст уведомления NotificationCompat.MessagingStyle. Для генерации ответов данные в интернет не загружаются.

Добавьте метаданные уведомления

  • Назначьте метаданные уведомлений, чтобы указать системе, как обрабатывать уведомления приложения, когда на устройстве включен режим "Не беспокоить". Например, используйте метод addPerson() или setCategory(Notification.CATEGORY_MESSAGE), чтобы обойти режим "Не беспокоить".