Приветствую!
Балуюсь разработками приложений под 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);
...
Ну вот, кажется, и все…
Автор: StanKo