Как обновить Уведомление с RemoteViews?
Я создаю уведомление с RemoteViews
из обычая Service
, который работает с уведомлением в режиме переднего плана (то есть служба будет оставаться активной, пока уведомление видимо пользователю). Уведомление установлено как Постоянное, поэтому пользователь не может его убрать.
Я хотел бы изменить, например, растровое изображение, показанное в ImageView
, содержащиеся в макете удаленного просмотра или изменить текстовое значение в TextView
, Макет в удаленном представлении устанавливается с помощью файла макета XML.
Моя проблема заключается в том, что после создания уведомления и видимости для пользователя, если я позвоню любой из RemoteViews
функции как setImageViewResource()
изменить Bitmap
показано в ImageView
, изменения не видны, если я не позвоню setImageViewResource()
Я звоню потом:
NotificationManager.notify( id, notification );
или же
Service.startForeground(id,notification);
Это не звучит правильно для меня, хотя Я не могу поверить, что обновить RemoteViews
Пользовательский интерфейс в уведомлении, которое уже создано, я должен повторно инициализировать уведомление. Если у меня есть Button
контролировать в уведомлении, он обновляет себя на ощупь и отпустить. Так что должен быть способ сделать это правильно, но я не знаю как.
Вот мой код, который создает уведомления внутри моего Service
пример:
this.notiRemoteViews = new MyRemoteViews(this,this.getApplicationContext().getPackageName(),R.layout.activity_noti1);
Notification.Builder notibuilder = new Notification.Builder(this.getApplicationContext());
notibuilder.setContentTitle("Test");
notibuilder.setContentText("test");
notibuilder.setSmallIcon(R.drawable.icon2);
notibuilder.setOngoing(true);
this.manager = (NotificationManager)this.getSystemService(Context.NOTIFICATION_SERVICE);
this.noti = notibuilder.build();
this.noti.contentView = this.notiRemoteViews;
this.noti.bigContentView = this.notiRemoteViews;
this.startForeground(NOTIFICATION_ID, this.noti);
И функция, которая "форсирует" изменения интерфейса в уведомлении:
public void updateNotiUI(){
this.startForeground(NOTIFICATION_ID, this.noti);
}
В MyRemoteViews
Класс, когда требуется, я делаю это, чтобы внести изменения в пользовательский интерфейс:
this.setImageViewResource(R.id.iconOFF, R.drawable.icon_off2);
this.ptMyService.updateNotiUI();
Может кто-нибудь сказать мне, что является правильным способом обновления компонентов пользовательского интерфейса RemoteViews
в уведомлении?
4 ответа
Вот подробный пример обновления уведомления с помощью RemoteViews
:
private static final int NOTIF_ID = 1234;
private NotificationCompat.Builder mBuilder;
private NotificationManager mNotificationManager;
private RemoteViews mRemoteViews;
private Notification mNotification;
...
// call this method to setup notification for the first time
private void setUpNotification(){
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// we need to build a basic notification first, then update it
Intent intentNotif = new Intent(this, MainActivity.class);
intentNotif.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendIntent = PendingIntent.getActivity(this, 0, intentNotif, PendingIntent.FLAG_UPDATE_CURRENT);
// notification's layout
mRemoteViews = new RemoteViews(getPackageName(), R.layout.custom_notification_small);
// notification's icon
mRemoteViews.setImageViewResource(R.id.notif_icon, R.drawable.ic_launcher);
// notification's title
mRemoteViews.setTextViewText(R.id.notif_title, getResources().getString(R.string.app_name));
// notification's content
mRemoteViews.setTextViewText(R.id.notif_content, getResources().getString(R.string.content_text));
mBuilder = new NotificationCompat.Builder(this);
CharSequence ticker = getResources().getString(R.string.ticker_text);
int apiVersion = Build.VERSION.SDK_INT;
if (apiVersion < VERSION_CODES.HONEYCOMB) {
mNotification = new Notification(R.drawable.ic_launcher, ticker, System.currentTimeMillis());
mNotification.contentView = mRemoteViews;
mNotification.contentIntent = pendIntent;
mNotification.flags |= Notification.FLAG_NO_CLEAR; //Do not clear the notification
mNotification.defaults |= Notification.DEFAULT_LIGHTS;
// starting service with notification in foreground mode
startForeground(NOTIF_ID, mNotification);
}else if (apiVersion >= VERSION_CODES.HONEYCOMB) {
mBuilder.setSmallIcon(R.drawable.ic_launcher)
.setAutoCancel(false)
.setOngoing(true)
.setContentIntent(pendIntent)
.setContent(mRemoteViews)
.setTicker(ticker);
// starting service with notification in foreground mode
startForeground(NOTIF_ID, mBuilder.build());
}
}
// use this method to update the Notification's UI
private void updateNotification(){
int api = Build.VERSION.SDK_INT;
// update the icon
mRemoteViews.setImageViewResource(R.id.notif_icon, R.drawable.icon_off2);
// update the title
mRemoteViews.setTextViewText(R.id.notif_title, getResources().getString(R.string.new_title));
// update the content
mRemoteViews.setTextViewText(R.id.notif_content, getResources().getString(R.string.new_content_text));
// update the notification
if (api < VERSION_CODES.HONEYCOMB) {
mNotificationManager.notify(NOTIF_ID, mNotification);
}else if (api >= VERSION_CODES.HONEYCOMB) {
mNotificationManager.notify(NOTIF_ID, mBuilder.build());
}
}
Макет для уведомления, т.е. res/layout/custom_notification_small.xml
:
<!-- We have to set the height to 64dp, this is the rule of the small notification -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:orientation="horizontal"
android:id="@+id/notif_small"
android:background="@drawable/notification_background">
<ImageView
android:id="@+id/notif_icon"
android:contentDescription="@string/notif_small_desc"
android:layout_width="47dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentLeft="true"
android:src="@drawable/ic_launcher"
android:layout_marginLeft="7dp"
android:layout_marginRight="9dp"/>
<TextView
android:id="@+id/notif_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/notif_icon"
android:singleLine="true"
android:paddingTop="8dp"
android:textSize="17sp"
android:textStyle="bold"
android:textColor="#000000"
android:text="@string/app_name"/>
<TextView
android:id="@+id/notif_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/notif_icon"
android:paddingBottom="9dp"
android:layout_alignParentBottom="true"
android:singleLine="true"
android:textSize="13sp"
android:textColor="#575757"
android:text="Content" />
</RelativeLayout>
Надеюсь, этот пример вам очень поможет!
ПРИМЕЧАНИЕ: вы не можете обновить пользовательский NotificationCompat
в pre-Honeycomb, поэтому я добавил альтернативный способ обновить его в pre-Honeycomb, то есть сначала проверить уровень API и использовать устаревший Notification
вместо.
ПРЕДУПРЕЖДЕНИЕ!
Единственный правильный способ обновить уведомление - это воссоздать RemoteViews перед каждым NotificationManager#notify. Зачем? Утечка памяти приводит к TransactionTooLargeException, о чем было сообщено в следующих вопросах:
- TransactionTooLargeException на Samsung S7
- TransactionTooLargeException при уведомлении уведомления
- Менеджер уведомлений Android TransactionTooLargeException
- и в моем случае
Каждый вызов RemoteViews, такой как setViewVisibility(...) и т. Д., Добавляет соответствующее действие в очередь действий. После уведомления удаленное представление раздувается, и действия фактически применяются. Но очередь не очищается!
Посмотрите на скриншот, сделанный во время отладки этого случая.
Там я обновляю уведомления аудиоплеера данными, поступающими из ViewModel. Приложение остановлено в строке #81, и вы можете увидеть экземпляр RemoteViews, который имеет массив действий с размером 51! Но я только дважды переключил звуковую дорожку и нажал паузу! Конечно, мне пришлось наблюдать сбой приложения через TransactionTooLargeException через некоторое время.
Мелкие исследования подтвердили, что не существует общедоступного API для прямой или косвенной очистки очереди действий, поэтому единственный способ обновить представление уведомлений - отдельно сохранить его состояние и воссоздать экземпляр RemoteViews, переданный в Notification.Builder, в любом случае это не сильно перегружает поток пользовательского интерфейса.
Вам бы пришлось позвонить NotificationManager.notify(id, notification)
уведомить систему уведомлений о том, что вы хотите обновить представление уведомлений. Вот ссылка на документы http://developer.android.com/training/notify-user/managing.html.
Есть метод, который возвращает объект уведомления.
private Notification getNotification(NotificationCompat.Builder mBuilder) {
RemoteViews mRemoteViews = new RemoteViews(getPackageName(), R.layout.notification_layout);
// Update your RemoteViews
mBuilder.setContent(mRemoteView);
Notification mNotification = mBuilder.build();
// set mNotification.bigContentView if you want to
return mNotification;
}
private void refreshNotification() {
mNotificationManager.notify(getNotification(mNotificationBuilder),
NOTIFICATION_ID);
// mNotificationBuilder is initialized already
}
Также обратите внимание, что bigContentView
а также RemoteViews
не полностью перерисованы. Если для некоторых элементов bigContentView видимость установлена на GONE
и если вы хотите показать его в следующий раз, вы должны явно установить видимость VISIBLE
,
Не хранить Notification
объект, но Notification.Builder
объект. Создавайте новые уведомления каждый раз, прежде чем отправлять их
NotificationManager.notify( id, notification );