Notifications - это уведомления, которые пользователь видит в верхней части экрана, когда ему приходит новое письмо, сообщение, обновление и т.п. В ближайшие несколько уроков мы подробно разберем, какие возможности предоставлены разработчикам для показа уведомлений.
В этом уроке рассмотрим основы - отображение/обновление/удаление уведомления и обработка нажатия на него.
Отображение
Код создания простого уведомления выглядит так:
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Title")
.setContentText("Notification text");
Notification notification = builder.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
Используем билдер, в котором указываем иконку, заголовок и текст для уведомления. Методом build получаем готовое уведомление.
Далее используем NotificationManager и его метод notify, чтобы показать созданное уведомление. Кроме notification, требуется передать id. Это необходимо, чтобы в дальнейшем мы могли использовать этот id для обновления или удаления уведомления.
Конструктор new NotificationCompat.Builder(Context) будет помечен как Deprecated, если вы используете библиотеку appCompat версии 26 и выше. Так получилось потому, что в Android API 26 появился новый конструктор и рекомендуется использовать его. Пока не обращайте внимание на это. В одном из следующих уроков мы рассмотрим использование правильного конструктора.
Запустив этот код, мы увидим уведомление

Оно отображает иконку и два текста, которые мы указывали в билдере. Нажатие на него ни к чему не приведет, т.к. мы не реализовали обработчик нажатия. Мы это сделаем чуть позже.
Обновление
Мы отобразили уведомление и теперь хотим его обновить. Для этого нужно просто снова показать уведомление методом notify и использовать при этом тот же id.
Это будет выглядеть так:
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_dialog_email)
.setContentTitle("Title change")
.setContentText("Notification text change");
Notification notification = builder.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
Код полностью аналогичен коду, что мы использовали при отображении уведомления. Только в билдере используем другие тексты и иконку. Самое главное, что в методе notify мы снова используем id = 1. NotificationManager по этому id найдет уведомление, которое мы отобразили чуть раньше и заменит его новым

Несколько уведомлений
Чтобы показать новое уведомление, а не обновить уже существующее, надо использовать другой id в методе notify.
Первое уведомление
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Title")
.setContentText("Notification text");
Notification notification = builder.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
Второе уведомление
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.ic_dialog_email)
.setContentTitle("Title 2")
.setContentText("Notification text 2");
Notification notification = builder.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(2, notification);
Мы использовали разные id в методе notify и получили два разных уведомления

Удаление
Чтобы удалить уведомление, используем NotificationManager и его метод cancel с указанием id уведомления.
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancel(1);
Либо методом cancelAll можем удалить все уведомления сразу
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.cancelAll();
При удалении уведомления нет необходимости проверять, отображается оно или нет. Если уведомления по каким-то причинам уже нет, то просто ничего не произойдет.
Обработка нажатия
Чтобы выполнить какое-либо действие по нажатию на уведомление, необходимо использовать PendingIntent. PendingIntent - это контейнер для Intent. Этот контейнер может быть использован для последующего запуска вложенного в него Intent.
Мы будем создавать Intent для запуска, например, Activity, упаковывать этот Intent в PendingIntent и передавать PeningIntent в уведомление. По нажатию на уведомление, система достанет из него PedningIntent и использует вложенный в него Intent, чтобы запустить Activity.
Давайте посмотрим, как это выглядит на практике:
Intent resultIntent = new Intent(this, MainActivity.class);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
Создаем Intent для запуска Activity и упаковываем его в PedningIntent.
Подробно о PedningIntent и его параметрах вы можете почитать в Уроке 119. Там я подробно рассмотрел различные кейсы на примерах с уведомлением и вызовом BroadcastReceiver.
Созданный PendingIntent нам надо будет передать в билдер уведомления. Полный код создания уведомления будет выглядеть так:
// Create PendingIntent
Intent resultIntent = new Intent(this, MainActivity.class);
PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// Create Notification
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Title")
.setContentText("Notification text")
.setContentIntent(resultPendingIntent);
Notification notification = builder.build();
// Show Notification
NotificationManager notificationManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(1, notification);
Передаем PendingIntent в метод setContentIntent билдера уведомления.
По нажатию на уведомление откроется MainActivity

Обратите внимание, что уведомление не удаляется автоматически после нажатия на него. Чтобы исправить это, можно в билдере уведомления использовать включить параметр autoCancel
setAutoCancel(true)
Уведомление, созданное с этим флагом будет закрываться после нажатия на него.

Билдер уведомления имеет еще несколько методов, которые могут быть полезны.
setNumber - позволяет добавить число в уведомление

setContentInfo - добавит текст справа
В старых версиях это выглядит так

В последних версиях он переехал в верхнюю часть уведомления

setColor - добавит фоновый цвет к иконке

setWhen - можно указать свое время для уведомления (время when). По умолчанию when = времени создания уведомления
setShowWhen - показывать ли время в уведомлении
setUsesChronometer - вместо статичного времени в уведомлении будет отображаться счетчик (00:00), показывающий сколько прошло от времени when. Может быть полезно для уведомления секундомера или звонка.
setOngoing - такое уведомление пользователь не сможет закрыть или смахнуть. Оно будет отображаться поверх обычных уведомлений.
setVibrate, setSound, setLights - настройки вибры, звука и LED индикатора устройства
setPriority - возможность установить приоритет. Доступные значения от -2 (NotificationCompat.PRIORITY_MIN) до 2 (NotificationCompat.PRIORITY_MAX). Поведение может отличаться на разных версиях Android, но общий смысл одинаков - чем выше приоритет, тем выше вероятность того, что пользователь увидит ваше уведомление.
setTimeoutAfter - возможность установить таймаут (в мсек), после которого уведомление само удалится. Добавлен в API 26.
setLargeIcon - возможность задать свою картинку в качестве иконки уведомления.
BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.startandroid, options);
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Title")
.setContentText("Notification text")
.setLargeIcon(bitmap);
Иконка из setSmallIcon будет видна в статусбаре, когда панель уведомлений не раскрыта.
А само уведомление будет выглядеть так:

setProgress - возможность отобразить прогрессбар в уведомлении
У метода три параметра:
max - максимальное значение прогрессбара. Укажите 0, если надо скрыть прогрессбар.
progress - текущее значение прогрессбара. Может быть от 0 до max.
indeterminate - если true, то будет показан "бесконечный" прогрессбар
Рассмотрим пример:
int max = 100;
// show notification with indeterminate progressbar
builder = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Some operation")
.setContentText("Preparing")
.setProgress(max,0, true);
notificationManager.notify(1, builder.build());
new Thread(new Runnable() {
@Override
public void run() {
try {TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
int progress = 0;
while (progress < max) {
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
progress += 10;
// show notification with current progress
builder.setProgress(max, progress, false)
.setContentText(progress + " of " + max);
notificationManager.notify(1, builder.build());
}
// show notification without progressbar
builder.setProgress(0, 10, false)
.setContentText("Completed");
notificationManager.notify(1, builder.build());
}
}).start();
Сначала отображаем бесконечный прогрессбар и текст Preparing. Т.е. делаем вид, что идет подготовка к выполнению операции.
Затем в отдельном потоке имитируем выполнение операции. Каждые 300 мсек увеличиваем значение progress и обновляем уведомление, чтобы прогрессбар показал текущий прогресс. А также в тексте показываем значение прогресса и максимума.
После выполнения операции скрываем прогрессбар и показываем текст Completed.

Повторюсь, очень рекомендую прочесть и понять Урок 119. В нем я подробно рассматриваю, почему PendingIntent последнего уведомления заменяет PendingIntent предыдущих уведомлений, и как этого можно избежать, используя, например, requestCode.
Присоединяйтесь к нам в Telegram:
- в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
- в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
- ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня

