- PVSM.RU - https://www.pvsm.ru -

Android Notifications. Оповещения через Status Bar

Добрый день. Давно занимаюсь разработкой под Android и хотелось бы рассказать сообществу о правильном подходе к созданию уведомлений.

image

На хабре уже есть статья по уведомлениям в статус баре для андроид [1]. В ней рассматриваются основы отображения стандартного и конфигурируемого layout в статус баре.

Ниже, помимо описанного ранее, мы рассмотрим добавление прогрессбара, обработку события по нажатию на уведомлений, различные варианты состояний уведомлений. Рассмотрим добавленный на днях в Compatibility library Notification.Builder. А также поговорим о рекомендациям по UI (design guidlines [2]), которые гугл рекомендует соблюдать при создании уведомлений.

Guidlines
Как советуют разработчики Android в официальном гайдлайне [3]

Когда показывать уведомления:

  • Мы показываем уведомления, когда не хотим отвлекать пользователя, перекрывая ему экран нашими диалогами [4] или переходом на экран уведомления. Мы не отвлекаем пользователя, но при этом не лишаем его возможности узнать содержание нашего уведомления в любой момент.
  • Чаще всего уведомления не всплывают спонтанно, а появляются в моменты, когда пользователь ожидает реакции от приложения.
  • В первую очередь уведомления должны отражать события, зависящие от времени. Как то: события календаря, входящие сообщения, запросы из социальных сетей.
Когда не стоит показывать уведомления:

  1. Не нужно показывать уведомления для не важных псевдо-зависящих от времени событий. Например, новости из социальных сетей.
  2. Нет необходимости показывать то, что уже отображено в UI приложения.
  3. Не стоит отображать ход низкоуровневых операций, вроде обращения к БД.
  4. Если приложение быстро само исправляет ошибку, то не нужно вовсе показывать эту ошибку, тем более уведомлением.
  5. Не показывайте уведомления о сервисах, которые пользователь не может контролировать.
  6. Плохим подходом является создание большого числа уведомлений, с целью напоминать пользователю о приложении, показывая постоянно его иконку и имя.
Хорошая практика:

  1. По клику на уведомление, пользователю должен открываться соответствующий экран приложения. В некоторых случаях достаточно, чтобы по клику уведомление просто убиралось.
  2. Указание времени события в уведомлении, также является хорошим подходом.
  3. Рекомендуется схожие события складывать в одно уведомление, а не отображать на каждое событие своё.
  4. Всегда убирать из статус-бара уведомления, с которыми пользователь уже ознакомился и произвел соответствующие действия.
  5. Показывать маленькое превью уведомления при его создании в свёрнутом статус-бареimage
  6. Позволять пользователю отключать уведомления в настройках приложения.
  7. Использовать иконки, обозначающие принадлежность уведомления определённому приложению. Иконки делать монохромными. Для этого рекомндуется воспользоваться специальным онлайн-редактором [5]
  8. В случае, если событие требует непосредственной реакции пользователя — вместо уведомлений использовать диалоги.
Архитектура:

В качестве утилитки, отвечающей за уведомления, я в своих приложениях использую singleton [6], к которому можно обратиться из любого класса приложения, нужно лишь иметь ссылку на context [7].

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

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

public class NotificationUtils {

  private static final String TAG = NotificationUtils.class.getSimpleName();
     
  private static NotificationUtils instance;

  private Context context;
  private NotificationManager manager; // Системная утилита, упарляющая уведомлениями
  private int lastId = 0; //постоянно увеличивающееся поле, уникальный номер каждого уведомления
  private HashMap<Integer, Notification> notifications; //массив ключ-значение на все отображаемые пользователю уведомления
 
 
  //приватный контструктор для Singleton
  private NotificationUtils(Context context){
    this.context = context;
    manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    notifications = new HashMap<Integer, Notification>();
  }
/**
* Получение ссылки на синглтон
*/

  public static NotificationUtils getInstance(Context context){
    if(instance==null){
        instance = new NotificationUtils(context);
    }
    return instance;
  }

* This source code was highlighted with Source Code Highlighter [8].

Создание уведомления с помощью NotificationCompat.Builder:
Для того чтобы воспользоваться классами, входящими в библиотеку поддержки прошлых версий (Compatibility library) [9], нужно добавить в проект библиотеку из папки /extras/android/support/v4/android-support-v4.jar
Если же проект нацелен на Android 3.0 и выше, то добавлять ничего не нужно достаточно обратиться к Notification.Builder

public int createInfoNotification(String message){
    Intent notificationIntent = new Intent(context, HomeActivity.class); // по клику на уведомлении откроется HomeActivity
    NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
//NotificationCompat.Builder nb = new NotificationBuilder(context) //для версии Android > 3.0
        .setSmallIcon(R.drawable.ic_action_picture) //иконка уведомления
        .setAutoCancel(true) //уведомление закроется по клику на него
        .setTicker(message) //текст, который отобразится вверху статус-бара при создании уведомления
        .setContentText(message) // Основной текст уведомления
        .setContentIntent(PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT))
        .setWhen(System.currentTimeMillis()) //отображаемое время уведомления
        .setContentTitle("AppName") //заголовок уведомления
        .setDefaults(Notification.DEFAULT_ALL); // звук, вибро и диодный индикатор выставляются по умолчанию

        Notification notification = nb.getNotification(); //генерируем уведомление
        manager.notify(lastId, notification); // отображаем его пользователю.
      notifications.put(lastId, notification); //теперь мы можем обращаться к нему по id
    return lastId++;
  }

* This source code was highlighted with Source Code Highlighter [8].

Создание уведомления с произвольным отображением (Custom layout):

/**
  * Создание уведомления с прогрессбаром о загрузке
  * @param fileName - текст, отображённый в заголовке уведомления.
  */
  public int createDownloadNotification(String fileName){
    String text = context.getString(R.string.notification_downloading).concat(" ").concat(fileName); //текст уведомления
    RemoteViews contentView = createProgressNotification(text, context.getString(R.string.notification_downloading)); //View уведомления
    contentView.setImageViewResource(R.id.notification_download_layout_image, R.drawable.ic_stat_example); // иконка уведомления
    return lastId++; //увеличиваем id, которое будет соответствовать следующему уведомлению
   }

/**
  * генерация уведомления с ProgressBar, иконкой и заголовком
  *
  * @param text заголовок уведомления
  * @param topMessage сообщение, уотображаемое в закрытом статус-баре при появлении уведомления
  * @return View уведомления.
  */
  private RemoteViews createProgressNotification(String text, String topMessage) {
    Notification notification = new Notification(R.drawable.ic_stat_example, topMessage, System.currentTimeMillis());
    RemoteViews contentView = new RemoteViews(context.getPackageName(), R.layout.notification_download_layout);
    contentView.setProgressBar(R.id.notification_download_layout_progressbar, 100, 0, false);    
    contentView.setTextViewText(R.id.notification_download_layout_title, text);
   
    notification.contentView = contentView;
    notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT | Notification.FLAG_ONLY_ALERT_ONCE;

    Intent notificationIntent = new Intent(context, NotificationUtils.class);
    PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
    notification.contentIntent = contentIntent;
   
    manager.notify(lastId, notification);
    notifications.put(lastId, notification);
    return contentView;
  }

notification_download_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="65sp"
  android:padding="10dp"
  android:orientation="vertical" >

  <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <ImageView
      android:id="@+id/notification_download_layout_image"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_stat_example"
      android:layout_gravity="center_vertical" />

    <TextView
      android:id="@+id/notification_download_layout_title"
      style="@style/NotificationTitle"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_alignParentTop="true"
      android:layout_marginLeft="10dip"
      android:singleLine="true"
      android:text="notification_download_layout_title"
      android:layout_gravity="center_vertical" />
  </LinearLayout>

  <ProgressBar
    android:id="@+id/notification_download_layout_progressbar"
    style="?android:attr/progressBarStyleHorizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="4dp"
    android:progress="0" />

</LinearLayout>

в андроид 2.3 и выше ( API >10) был создан специальный ресурс, в котором системная тема указывает цвета текста уведомений. Из-за этого в старых версиях приходится использовать костыль:

В файл res/values/styles.xml прописываем:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <style name="NotificationText">
   <item name="android:textColor">?android:attr/textColorPrimary</item>
  </style>
  <style name="NotificationTitle">
   <item name="android:textColor">?android:attr/textColorPrimary</item>
   <item name="android:textStyle">bold</item>
  </style>
 
</resources>

А для поддержки API >10 Создаем файл res/values-v9/styles.xml и вписываем:

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <style name="NotificationText" parent="android:TextAppearance.StatusBar.EventContent" />
  <style name="NotificationTitle" parent="android:TextAppearance.StatusBar.EventContent.Title" />
</resources>

Теперь из кода нашего приложения обращаемся к утилите:

    NotificationUtils n = NotificationUtils.getInstance(getActivity());
    n.createInfoNotification(
"info notification");

Создаем уведомление с прогресс-баром:

int pbId = NotificationUtils.getInstance(getActivity()).createDownloadNotification("downloading video");

И во время выполнения потока постоянно обновляем прогресс вызовом:

NotificationUtils.getInstance(getActivity()).updateProgress(pbId, YOUR_PROGRESS);

В итоге получаем:
image

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

И напоследок маленькая хитрость:

Если не хотите дублирования в стеке одних и тех же Activity — поставьте в манифесте к нужной activity
android:launchMode="singleTop"

Автор: nekdenis


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/android-development/4461

Ссылки в тексте:

[1] уведомлениям в статус баре для андроид : http://habrahabr.ru/blogs/android_development/111238

[2] design guidlines: http://developer.android.com/design/index.html

[3] официальном гайдлайне: http://developer.android.com/design/patterns/notifications.html

[4] диалогами: http://developer.android.com/guide/topics/ui/dialogs.html

[5] онлайн-редактором: http://android-ui-utils.googlecode.com/hg/asset-studio/dist/index.html#group-icon-generators

[6] singleton: http://habrahabr.ru/post/129494/

[7] context: http://developer.android.com/reference/android/content/Context.html

[8] Source Code Highlighter: http://virtser.net/blog/post/source-code-highlighter.aspx

[9] библиотеку поддержки прошлых версий (Compatibility library): http://developer.android.com/sdk/compatibility-library.html