Je crée une notification avec RemoteViews
à partir d'un Service
personnalisé, qui s'exécute avec une notification en mode avant-plan (le service restera actif tant que la notification sera visible par l'utilisateur). La notification est définie sur En cours pour que l'utilisateur ne puisse pas l'extraire.
J'aimerais modifier, par exemple, le bitmap affiché dans ImageView
, contenu dans la présentation de la vue distante ou modifier le texte dans un TextView
. La mise en page dans la vue distante est définie avec un fichier de mise en page XML.
Mon problème est qu'une fois que la notification est créée et visible pour l'utilisateur, si j'appelle une fonction de RemoteViews
telle que setImageViewResource()
pour changer Bitmap
affiché dans un ImageView
, la modification n'est pas visible, sauf si j'appelle setImageViewResource()
.
NotificationManager.notify( id, notification );
ou
Service.startForeground(id,notification);
Cela ne me semble pas correct cependant. Je ne peux pas croire que pour mettre à jour l'interface utilisateur RemoteViews
dans une notification déjà créée, je dois réinitialiser la notification. Si j'ai le contrôle Button
dans une notification, il se met à jour de manière tactile et relâchée. Il doit donc y avoir un moyen de faire cela correctement, mais je ne sais pas comment.
Voici mon code qui crée une notification dans mon instance 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);
Et la fonction qui «force» les modifications de l'interface utilisateur à la notification:
public void updateNotiUI(){
this.startForeground(NOTIFICATION_ID, this.noti);
}
Dans la classe MyRemoteViews
, lorsque cela est nécessaire, je le fais pour apporter des modifications à l'interface utilisateur:
this.setImageViewResource(R.id.iconOFF, R.drawable.icon_off2);
this.ptMyService.updateNotiUI();
Quelqu'un peut-il me dire quel est le bon moyen de mettre à jour les composants d'interface utilisateur d'un RemoteViews
dans la notification?
Voici un exemple détaillé pour vous permettant de mettre à jour la notification en utilisant 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());
}
}
Disposition pour la notification, c.-à-d. 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>
J'espère que cet exemple vous aide beaucoup!
REMARQUE: Vous ne pouvez pas mettre à jour la variable NotificationCompat
personnalisée avant Honeycomb. J'ai donc ajouté un autre moyen de le mettre à jour avant Honeycomb, c'est-à-dire vérifier le niveau de l'API en premier et utiliser plutôt la Notification
obsolète.
Vous devez appeler NotificationManager.notify(id, notification)
pour que Notification System sache que vous souhaitez mettre à jour la vue des notifications. Voici le lien de la documentation http://developer.Android.com/training/notify-user/managing.html .
Avoir une méthode qui retourne l'objet Notification.
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
}
Notez également que bigContentView
et RemoteViews
ne sont pas complètement redessinés. Si la visibilité de certains éléments de bigContentView est définie sur GONE
et si vous souhaitez l'afficher la prochaine fois, vous devez définir explicitement la visibilité sur VISIBLE
.
ATTENTION!
Le seul moyen correct de mettre à jour la notification consiste à recréer RemoteViews avant chaque notification NotificationManager #. Pourquoi? Il y a une fuite de mémoire menant à TransactionTooLargeException, comme indiqué dans ces questions:
Chaque appel sur RemoteViews tel que setViewVisibility (...), etc., ajoute l'action correspondante à la file d'attente d'actions à appliquer. Après notification, la vue distante est gonflée et les actions sont réellement appliquées. Mais la file d'attente n'est pas effacée!
Jetez un coup d'œil à la capture d'écran prise lors du débogage de ce cas.
Là, je mets à jour la notification du lecteur audio avec les données provenant de ViewModel. L'application est arrêtée sur la ligne 81 et vous pouvez voir l'instance RemoteViews qui contient un tableau d'actions de taille 51! Mais je n'ai changé que deux fois de piste audio et appuyé sur pause! Bien sûr, j'ai dû observer un blocage d'application avec TransactionTooLargeException après un certain temps.
Une recherche superficielle a confirmé qu'aucune API publique ne pouvait effacer directement ou indirectement la file d'attente d'actions. Le seul moyen de mettre à jour la vue de notification consiste donc à conserver son état séparément et à recréer l'instance RemoteViews transmise à Notification.Builder.
Ne stockez pas l'objet Notification
, mais l'objet Notification.Builder
. Produire une nouvelle notification à chaque fois avant de la pousser à
NotificationManager.notify( id, notification );