Как обновить уведомление с помощью RemoteViews?

Я создаю уведомление с RemoteViews из пользовательского Service, который работает с уведомление в режиме переднего плана (то есть служба будет оставаться активной до тех пор, пока уведомление видно пользователю). Уведомление настроено как текущее, поэтому пользователь не может его отключить.

Я хотел бы изменить, например, растровое изображение, показанное в ImageView, содержащееся в макете удаленного просмотра, или изменить текстовое значение в файле TextView. Макет в удаленном просмотре задается файлом макета XML.

Моя проблема заключается в том, что после создания уведомления и его отображения пользователю, если я вызываю любую из функций RemoteViews, например setImageViewResource() для изменения Bitmap, показанного в ImageView, изменение не видно, пока я не вызову setImageViewResource(). Я вызываю потом:

NotificationManager.notify( id, notification );

or

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 в уведомлении?


person Sinisa    schedule 01.04.2014    source источник


Ответы (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 в версии до Honeycomb, поэтому я добавил альтернативный способ обновить его в версии pre-Honeycomb, т. е. сначала проверить уровень API, а вместо этого использовать устаревший Notification.

person Anggrayudi H    schedule 11.06.2015
comment
Ваш ответ правильный, но updateNotification(), где нам нужно позвонить - person Dilip; 27.09.2015
comment
@Dilip, посмотри мой ответ еще раз. Я обновил его, чтобы вы могли понять больше. - person Anggrayudi H; 28.09.2015
comment
единственное работающее решение для обновления уведомлений startForeground() RemoteView. Потрясающий ответ. - person Gal Rom; 05.09.2016
comment
@AnggrayudiH Эй, это выдает мне эту ошибку android.app.RemoteServiceException: Плохое уведомление отправлено из пакета com.areel.android: Не удалось развернуть RemoteViews для: StatusBarNotification(pkg=com.areel.android user=UserHandle{0} id=1234 tag=null key=0|com.areel.android|1234|null|10432: Notification(pri=0 contentView=com.areel.android/0x7f0b0044 vibrate=null sound=null tick defaults=0x0 flags=0x62 color=0x00000000 vis =ЧАСТНОЕ)) - person Akash Dubey; 07.12.2017
comment
Почему вы не настраиваете вещи в onStartCommand? Просто любопытно, так как я только начинаю работать с сервисами переднего плана, и все примеры, которые я нашел, используют @Override onStartCommand - person therealone; 17.12.2017
comment
Есть ли какая-либо документация, предлагающая только этот способ, потому что никакой другой способ не работает для изображений, и в документации нет никаких гильдий. Это сводило меня с ума на один день и решило мою проблему только сейчас. - person Ajay Naredla; 03.01.2020
comment
где вызвать updateNotification() ?? - person Jocelin; 18.03.2021

ПРЕДУПРЕЖДЕНИЕ!

Единственный правильный способ обновить уведомление — повторно создать RemoteView перед каждым NotificationManager#notify. Почему? Существует утечка памяти, ведущая к TransactionTooLargeException, как было сообщено в этих вопросах:

Каждый вызов RemoteViews, например setViewVisibility(...) и т. д., добавляет соответствующее действие в очередь действий, которые должны быть применены. После уведомления удаленное представление расширяется, и действия фактически применяются. Но очередь не очищается!

Взгляните на снимок экрана, сделанный во время отладки этого случая.

введите изображение здесь

Там я обновляю уведомление аудиоплеера данными, поступающими из ViewModel. Приложение остановлено на строке #81, и вы можете увидеть экземпляр RemoteViews с массивом действий размером 51! Но я только два раза переключал звуковую дорожку и нажимал на паузу! Конечно, через некоторое время мне пришлось наблюдать падение приложения с TransactionTooLargeException.

Неглубокое исследование подтвердило, что нет общедоступного API для прямой или косвенной очистки очереди действий, поэтому единственный способ обновить представление уведомлений — отдельно сохранить его состояние и воссоздать экземпляр RemoteViews, переданный в Notification.Builder, в любом случае это не сильно перегружает поток пользовательского интерфейса.

person Max Elkin    schedule 09.11.2018
comment
Чувак, ты спас меня день! Спасибо. Я думал, что реализовал крутую оптимизацию, которая могла бы сэкономить память. Но вреда от этого было больше. - person Oleksandr Albul; 09.04.2019
comment
@Max Elkin: что это значит? мы должны сделать startForeground(ID, уведомление); вместо NotificationManager.notify(ID, уведомление)? - person user1090751; 09.10.2020

Вам нужно будет позвонить 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.

person Froyo    schedule 11.06.2015
comment
Ваши оба ответа, наконец, помогли мне, спасибо. @Anggrayudi H ответил первым, так что он заслуживает 50 баллов репутации. Ответ Froyo просто приятный, спасибо. - person Rafael; 11.06.2015

Сохраняйте не объект Notification, а объект Notification.Builder. Создавать новое уведомление каждый раз, прежде чем отправить его на

NotificationManager.notify( id, notification );
person dykzei eleeot    schedule 05.02.2015