Приветствую!
Балуюсь разработками приложений под Android, но до сих пор не использовал Builder для создания уведомлений, а делал это старым добрым методом, как описано, например, в данной статье. Однако данный метод не только уже устарел, но даже больше — он является deprecated. Кроме того, передо мной еще стояла задача выводить в каждом Notification-е свою картинку, которой при том нет в составе проекта и я не могу на нее сослаться через R.drawable, как, например, аватарка пользователя, которого я добавляю в процессе использования приложения и т.п. Если интересно — добро пожаловать под кат.
Builder для создания Notifications введен с АОС 3.0 и если минимальный уровень SDK для приложения ниже, как в моем случае, то необходимо использовать библиотеку совместимости v4, т.к. я использую для разработки AndroidStudio, то включение библиотеки в состав проекта состоит в добавлении ее в build.gradle в раздел зависимостей:
Тот, кто использует для разработки старый добрый Eclipse может найти соответствующую jar-ку в папке, где установлен Android SDK в папке /extras/android/support/v4/ и копирнуть ее в папку libs своего проекта.
Собственно для создания уведомлений я написал небольшой класс-хелпер — NotificationHelper. Обычно такого рода классы я наполняю public static методами, а если внутри требуется ссылка на Context, то инициализирую такой хелпер из класса Application, используя Context самого приложения, т.е. Application Context. Хранить данный контекст абсолютно безопасно даже в статик-поле, в отличии от того, который Activity — этот хранить в статиках нельзя, во избежание утечек, короче говоря, Activity Context (он же — Base Context) предпочитаю вообще нигде никогда не хранить. Итого NotificationHelper выглядит так:
Т.к. в методе инициализации я прописал appContext = context.getApplicationContext(), то инициализировать этот хелпер можно откуда угодно, главное чтобы был доступ к контексту, можно даже в активити передав саму активити в качестве параметра.
Метод, используемый для создания обычного, стандартного уведомления я создал такой:
Вызывается этот метод, например, из активити, так:
Это обычное стандартное уведомление. В принципе так как у меня во всех приложениях иконка приложения всегда называется именно ic_launcher, а не как-то еще, то данный хелпер универсален для меня. Т.е. его без изменений можно включать в состав любого приложения, где это требуется.
А вот вариант с картинкой, которой нет в составе проекта, как я уже отмечал выше, уже не получится сделать столь же универсальным, т.к. к нему требуется layout в придачу. Именно этот layout и дает возможность вывести что угодно, с него и начну, пожалуй (notification_layout.xml):
Думаю тут все ясно из самих наименований id-шек:
notification_image — место под аватарку
notification_message — место для отображения текста уведомления.
И собственно метод создания уведомления с картинкой, загруженной приложением во время работы:
Вызывается этот метод, например, из активити, так:
Собственно, вот и все. Ну почти…
Все же остаются ньюансы, например, если приложение состоит из нескольких активити, то тупо запускать определенную активити через уведомление — несовсем правильно. Ведь, во-первых, эта активити может как раз быть открыта в приложении, и таким образом через уведомление такая активити запустится еще раз. Это можно обойти, указав в манифесте для такой активити следующий флаг типа запуска:
Но это для исключения повторного запуска активной (topmost) активити, т.е. если активити запущена (в стеке), но «накрыта» какой-то еще, то способ не поможет. Можно попробовать подобрать еще какой-то из возможных параметров типа запуска.
А что делать, если приложение не запущено? Ведь нельзя же взять и запустить отдельную активити, которая обычно в приложении запускается по цепочке через другие активити, и ее нельзя запускать отдельно от других, да и выход из нее повлечет выход из приложения, вместо ожидаемого пользователем возврата на предыдущий «экран». А если в приложении стартовая активити — онлайн авторизация, что является обязательным условием для продолжения работы приложения, что тогда? Получится что запустим какую-то активити, минуя авторизацию, а это может привести к непредсказуемым последствиям. Можно ограничиться вызовом основной, стартовой активити из уведомления, но опять же, это хорошо, когда приложение не запущено, но когда пользователь уже в приложении, то к чему ему повторная авторизация? Здесь поможет разве что полезная нагрузка уведомления, т.е. передача в нем Bundle с некими параметрами.
Лично решил это следующим образом: создал активити-заглушку NotificationActivity, которая и вызывается из уведомления, при этом в бандл уведомления закидываю ту активити, что мне реально надо запустить, в NotificationActivity проверяю условия и выполняю соответствующие необходимые действия:
Здесь я комментировал вызовы метода isActivityRunning(), т.к. это требует еще одного разрешения в манифесте. Кому это некритично, тот спокойно может удалить вызов статического метода до комментария, и раскомментировать вызов этого метода. Я же решил, что лучше мансов с манифестом избежать и потому написал статические методы в обе активити:
Изменения, которые для этого потребовалось внести в хелпер уведомлений (в оба метода createNotification()):
Ну вот, кажется, и все…
Балуюсь разработками приложений под Android, но до сих пор не использовал Builder для создания уведомлений, а делал это старым добрым методом, как описано, например, в данной статье. Однако данный метод не только уже устарел, но даже больше — он является deprecated. Кроме того, передо мной еще стояла задача выводить в каждом Notification-е свою картинку, которой при том нет в составе проекта и я не могу на нее сослаться через R.drawable, как, например, аватарка пользователя, которого я добавляю в процессе использования приложения и т.п. Если интересно — добро пожаловать под кат.
Builder для создания Notifications введен с АОС 3.0 и если минимальный уровень SDK для приложения ниже, как в моем случае, то необходимо использовать библиотеку совместимости v4, т.к. я использую для разработки AndroidStudio, то включение библиотеки в состав проекта состоит в добавлении ее в build.gradle в раздел зависимостей:
dependencies { compile 'com.android.support:support-v4:20.0.0' }
Тот, кто использует для разработки старый добрый Eclipse может найти соответствующую jar-ку в папке, где установлен Android SDK в папке /extras/android/support/v4/ и копирнуть ее в папку libs своего проекта.
Собственно для создания уведомлений я написал небольшой класс-хелпер — NotificationHelper. Обычно такого рода классы я наполняю public static методами, а если внутри требуется ссылка на Context, то инициализирую такой хелпер из класса Application, используя Context самого приложения, т.е. Application Context. Хранить данный контекст абсолютно безопасно даже в статик-поле, в отличии от того, который Activity — этот хранить в статиках нельзя, во избежание утечек, короче говоря, Activity Context (он же — Base Context) предпочитаю вообще нигде никогда не хранить. Итого NotificationHelper выглядит так:
public class NotificationsHelper { private static Context appContext; // контекст приложения private static int lastNotificationId = 0; //уин последнего уведомления private static NotificationManager manager; // менеджер уведомлений // метод инциализации данного хелпера public static void init(Context context){ if(manager==null){ appContext = context.getApplicationContext(); // на случай инициализации Base Context-ом manager = (NotificationManager) appContext.getSystemService(Context.NOTIFICATION_SERVICE); } } /** * Создает и возвращает общий NotificationCompat.Builder * @return */ private static NotificationCompat.Builder getNotificationBuilder(){ final NotificationCompat.Builder nb = new NotificationCompat.Builder(appContext) .setAutoCancel(true) // чтобы уведомление закрылось после тапа по нему .setOnlyAlertOnce(true) // уведомить однократно .setWhen(System.currentTimeMillis()) // время создания уведомления, будет отображено в стандартном уведомлении справа .setContentTitle(appContext.getString(R.string.app_name)) //заголовок .setDefaults(Notification.DEFAULT_ALL); // alarm при выводе уведомления: звук, вибратор и диод-индикатор - по умолчанию return nb; } // удаляет все уведомления, созданные приложением public static void cancelAllNotifications(){ manager.cancelAll(); } // тут следуют методы, которые рассмотрим далее }
Т.к. в методе инициализации я прописал appContext = context.getApplicationContext(), то инициализировать этот хелпер можно откуда угодно, главное чтобы был доступ к контексту, можно даже в активити передав саму активити в качестве параметра.
Метод, используемый для создания обычного, стандартного уведомления я создал такой:
/** * * @param message - текст уведомления * @param targetActivityClass - класс целевой активити * @param iconResId - R.drawable необходимой иконки * @return */ public static int createNotification(final String message, final Class targetActivityClass, final int iconResId) { // некоторые проверки на null не помешают, зачем нам NPE? if (targetActivityClass==null){ new Exception("createNotification() targetActivity is null!").printStackTrace(); return -1; } if (manager==null){ new Exception("createNotification() NotificationUtils not initialized!").printStackTrace(); return -1; } final Intent notificationIntent = new Intent(appContext, targetActivityClass); // интент для запуска указанного Activity по тапу на уведомлении final NotificationCompat.Builder nb = getNotificationBuilder() // получаем из хелпера generic Builder, и далее донастраиваем его .setContentText(message) // сообщение, которое будет отображаться в самом уведомлении .setTicker(message) //сообщение, которое будет показано в статус-баре при создании уведомления, ставлю тот же .setSmallIcon(iconResId != 0 ? iconResId : R.drawable.ic_launcher) // иконка, если 0, то используется иконка самого аппа .setContentIntent(PendingIntent.getActivity(appContext, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT)); // создание PendingIntent-а final Notification notification = nb.build(); //генерируем уведомление, getNotification() - deprecated! manager.notify(lastNotificationId, notification); // "запускаем" уведомление return lastNotificationId++; }
Вызывается этот метод, например, из активити, так:
NotificationsHelper.createNotification("Achtung message!", MessagesActivity.class, 0);
Это обычное стандартное уведомление. В принципе так как у меня во всех приложениях иконка приложения всегда называется именно ic_launcher, а не как-то еще, то данный хелпер универсален для меня. Т.е. его без изменений можно включать в состав любого приложения, где это требуется.
А вот вариант с картинкой, которой нет в составе проекта, как я уже отмечал выше, уже не получится сделать столь же универсальным, т.к. к нему требуется layout в придачу. Именно этот layout и дает возможность вывести что угодно, с него и начну, пожалуй (notification_layout.xml):
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp"> <ImageView android:id="@+id/notification_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/ic_launcher" /> <TextView android:id="@+id/notification_message" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginLeft="10dp" /> </LinearLayout>
Думаю тут все ясно из самих наименований id-шек:
notification_image — место под аватарку
notification_message — место для отображения текста уведомления.
И собственно метод создания уведомления с картинкой, загруженной приложением во время работы:
/** * * @param message - сообщение * @param targetActivityClass - класс целевой Активити * @param icon - картинка (аватарка) * @return */ public static int createNotification(final String message, final Class targetActivityClass, final Bitmap icon){ // аналогичные же проверки на null if (targetActivityClass==null){ new Exception("createNotification() targetActivity is null!").printStackTrace(); return -1; } if (manager==null){ new Exception("createNotification() NotificationUtils not initialized!").printStackTrace(); return -1; } // именно класс RemoteViews предоставляет возможность использования своего лейаута для уведомлений final RemoteViews contentView = new RemoteViews(appContext.getPackageName(), R.layout.notification_layout); contentView.setTextViewText(R.id.notification_message, message); // сообщение уведомления contentView.setImageViewBitmap(R.id.notification_image, icon); // картинка для уведомления, та же аватарка, к примеру final Intent notificationIntent = new Intent(appContext, targetActivityClass); // интент для запуска указанного Activity по тапу на уведомлении final NotificationCompat.Builder nb = getNotificationBuilder() // получаем билдер-основу .setTicker(message) // сообщение, которое будет показано в статус-баре при создании уведомления .setContentIntent(PendingIntent.getActivity(appContext, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT)) .setSmallIcon(R.drawable.ic_launcher); // использую иконку приложения, без этого уведомление может не выводиться вообще //.setContent(contentView); // не работает на 2.3.*, у гугла - все как обычно // см. http://stackoverflow.com/questions/12574386/custom-notification-layout-dont-work-on-android-2-3-or-lower final Notification notification = nb.build(); //генерируем уведомление notification.contentView = contentView; // поскольку setContent() в билдере не всегда работает, ставим здесь manager.notify(lastNotificationId, notification); // "запускаем" уведомление return lastNotificationId++; }
Вызывается этот метод, например, из активити, так:
Bitmap avatar = getAvatarBitmap(); // предположим есть такой метод и возвращает он необходимый для уведомления Bitmap NotificationsHelper.createNotification("Achtung message", MessagesActivity.class, avatar);
Собственно, вот и все. Ну почти…
Все же остаются ньюансы, например, если приложение состоит из нескольких активити, то тупо запускать определенную активити через уведомление — несовсем правильно. Ведь, во-первых, эта активити может как раз быть открыта в приложении, и таким образом через уведомление такая активити запустится еще раз. Это можно обойти, указав в манифесте для такой активити следующий флаг типа запуска:
android:launchMode="singleTop"
Но это для исключения повторного запуска активной (topmost) активити, т.е. если активити запущена (в стеке), но «накрыта» какой-то еще, то способ не поможет. Можно попробовать подобрать еще какой-то из возможных параметров типа запуска.
А что делать, если приложение не запущено? Ведь нельзя же взять и запустить отдельную активити, которая обычно в приложении запускается по цепочке через другие активити, и ее нельзя запускать отдельно от других, да и выход из нее повлечет выход из приложения, вместо ожидаемого пользователем возврата на предыдущий «экран». А если в приложении стартовая активити — онлайн авторизация, что является обязательным условием для продолжения работы приложения, что тогда? Получится что запустим какую-то активити, минуя авторизацию, а это может привести к непредсказуемым последствиям. Можно ограничиться вызовом основной, стартовой активити из уведомления, но опять же, это хорошо, когда приложение не запущено, но когда пользователь уже в приложении, то к чему ему повторная авторизация? Здесь поможет разве что полезная нагрузка уведомления, т.е. передача в нем Bundle с некими параметрами.
Лично решил это следующим образом: создал активити-заглушку NotificationActivity, которая и вызывается из уведомления, при этом в бандл уведомления закидываю ту активити, что мне реально надо запустить, в NotificationActivity проверяю условия и выполняю соответствующие необходимые действия:
public class NotificationActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); final Bundle extras = getIntent().getExtras(); if (extras!=null && extras.containsKey(KEY_EXTRAS_TARGET_ACTIVITY)){ if (isAppRunning()) { if (LoginActivity.isRunning() /*isActivityRunning(LoginActivity.class)*/) { // } else if (MainActivity.isRunning() /*isActivityRunning(MainActivity.class)*/) { startActivity(new Intent(this, (Class) extras.getSerializable(KEY_EXTRAS_TARGET_ACTIVITY))); } else { final Intent intent = new Intent(getBaseContext(), LoginActivity.class); intent.putExtras(extras); startActivity(intent); } } else { final Intent intent = new Intent(this, LoginActivity.class); intent.putExtras(extras); startActivity(intent); } } finish(); return; } private boolean isAppRunning() { final String process = getPackageName(); final ActivityManager activityManager = (ActivityManager) getSystemService( ACTIVITY_SERVICE ); List<ActivityManager.RunningAppProcessInfo> procInfos = activityManager.getRunningAppProcesses(); for(int i = 0; i < procInfos.size(); i++){ if(procInfos.get(i).processName.equals(process)) { return true; } } return false; } // для использования этого метода придется добавлять в манифест разрешение: // <uses-permission android:name="android.permission.GET_TASKS"/> private boolean isActivityRunning(Class activityClass) { ActivityManager activityManager = (ActivityManager) getBaseContext().getSystemService(Context.ACTIVITY_SERVICE); List<ActivityManager.RunningTaskInfo> tasks = activityManager.getRunningTasks(Integer.MAX_VALUE); for (ActivityManager.RunningTaskInfo task : tasks) { if (activityClass.getCanonicalName().equalsIgnoreCase(task.baseActivity.getClassName())) return true; } return false; } }
Здесь я комментировал вызовы метода isActivityRunning(), т.к. это требует еще одного разрешения в манифесте. Кому это некритично, тот спокойно может удалить вызов статического метода до комментария, и раскомментировать вызов этого метода. Я же решил, что лучше мансов с манифестом избежать и потому написал статические методы в обе активити:
private static boolean isRunning; public static boolean isRunning(){return isRunning;} @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); isRunning = true; ... } @Override protected void onDestroy() { super.onDestroy(); isRunning = false; }
Изменения, которые для этого потребовалось внести в хелпер уведомлений (в оба метода createNotification()):
... final Intent notificationIntent = new Intent(appContext, NotificationActivity.class); notificationIntent.putExtra(KEY_EXTRAS_TARGET_ACTIVITY, targetActivityClass); ...
Ну вот, кажется, и все…
