Android 如何使用 RemoteViews 更新通知?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22789588/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 06:27:26  来源:igfitidea点击:

How to update Notification with RemoteViews?

androidandroid-notificationsandroid-remoteview

提问by Sinisa

I'm creating a notification with RemoteViewsfrom a custom Service, which is running with notification in a foreground mode (that is, service will remain active as long as notification is visible to user). Notification is set as Ongoing so user cannot swipe it off.

我正在创建一个RemoteViews来自 custom的通知Service,它在前台模式下与通知一起运行(也就是说,只要通知对用户可见,服务就会保持活动状态)。通知设置为正在进行,因此用户无法将其刷掉。

I'd like to change for example bitmap shown in ImageView, contained within remote view's layout or change text value in a TextView. Layout in remote view is set with XML layout file.

例如,我想更改ImageView包含在远程视图布局中的位图,或更改TextView. 远程视图中的布局使用 XML 布局文件设置。

My problem is that once notification is created and visible to user, if I call any of RemoteViews's functions like setImageViewResource()to change Bitmapshown in an ImageView, the change is not visible, unless I do call setImageViewResource()I call afterwards:

我的问题是,一旦通知被创建和看得见的用户,如果我调用任何RemoteViews的功能,如setImageViewResource()对变化Bitmap中的显示ImageView,该变化是不可见的,除非我不叫setImageViewResource()我打电话算账:

NotificationManager.notify( id, notification );

or

或者

Service.startForeground(id,notification);

This doesn't sound right to me though. I can't believe that to update RemoteViewsUI in a notification that is already created, I have to re-initialize notification. If I have Buttoncontrol in a notification, it updates itself on touch and release. So there's gotta be a way to do this properly, but I don't know how.

但这对我来说听起来不对。我不敢相信要RemoteViews在已创建的通知中更新UI,我必须重新初始化通知。如果我Button在通知中拥有控制权,它会在触摸和释放时自行更新。所以必须有一种方法来正确地做到这一点,但我不知道如何。

Here is my code which creates notification inside my Serviceinstance:

这是我的代码,它在我的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);

And function that 'forces' UI changes to notification:

以及“强制”用户界面更改为通知的功能:

public void updateNotiUI(){
    this.startForeground(NOTIFICATION_ID, this.noti);
}

Within MyRemoteViewsclass, when required, I do this to make changes to UI:

MyRemoteViews课堂上,当需要时,我这样做是为了对 UI 进行更改:

this.setImageViewResource(R.id.iconOFF, R.drawable.icon_off2);
this.ptMyService.updateNotiUI();

Can anyone tell me what is the correct way of updating UI components of a RemoteViewsin the notification?

谁能告诉我RemoteViews在通知中更新 UI 组件的正确方法是什么?

回答by Anggrayudi H

Here's a detail example for you to update the notification using RemoteViews:

这是您使用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());
    }
}

Layout for the Notification, i.e. res/layout/custom_notification_small.xml:

通知的布局,即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>

Hope this example helps you a lot!

希望这个例子对你有很大帮助!

NOTE: You can't update the custom NotificationCompaton pre-Honeycomb, so I added an alternative way to update it on pre-Honeycomb, i.e. checking the API level first and use the deprecated Notificationinstead.

注意:您无法NotificationCompat在蜂窝前版本上更新自定义,因此我添加了一种在蜂窝前版本上更新它的替代方法,即首先检查 API 级别并使用已弃用的版本Notification

回答by Max Elkin

WARNING!

警告!

The only correct way to update notification is to recreate RemoteViews before each NotificationManager#notify. Why? There's a memory leak leading to TransactionTooLargeException, as it has been reported in those questions:

更新通知的唯一正确方法是在每个 NotificationManager#notify 之前重新创建 RemoteViews。为什么?存在导致 TransactionTooLargeException 的内存泄漏,正如这些问题中所报告的那样:

Each call on RemoteViews such as setViewVisibility(...) and so on adds corresponding action to queue of actions are to be applied. After notify the remote view is inflated and actions are actually applied. But the queue is not cleared!

对 RemoteViews 的每次调用,例如 setViewVisibility(...) 等,都会将相应的动作添加到要应用的动作队列中。通知后,远程视图膨胀并实际应用操作。但是队列没有被清除!

Take a look at screenshot taken during debugging this case.

看看在调试这个案例时截取的屏幕截图。

enter image  here

在此处输入图片

There I'm updating audio player notification with data coming from ViewModel. Application is stopped on line #81 and you can see the RemoteViews instance that has array of actions with size 51! But I only switched audio track twice and pressed pause! Of course I had to observe application crash with TransactionTooLargeException after a while.

在那里,我正在使用来自 ViewModel 的数据更新音频播放器通知。应用程序在第 81 行停止,您可以看到具有大小为 51 的操作数组的 RemoteViews 实例!但是我只切换了两次音轨并按下了暂停!当然,我不得不在一段时间后观察到 TransactionTooLargeException 的应用程序崩溃。

Shallow research confirmed there's no public API to directly or indirectly clear actions queue, so the only way to update notification view is to hold its state separately and recreate RemoteViews instance passed to Notification.Builder, anyway this doesn't overload UI thread a lot

浅层研究证实没有公共 API 可以直接或间接清除操作队列,因此更新通知视图的唯一方法是单独保持其状态并重新创建传递给 Notification.Builder 的 RemoteViews 实例,无论如何这不会使 UI 线程过载很多

回答by Froyo

You would have to call NotificationManager.notify(id, notification)to let Notification System know that you want to update the notification view. Here's the docs link http://developer.android.com/training/notify-user/managing.html.

您必须打电话NotificationManager.notify(id, notification)让通知系统知道您要更新通知视图。这是文档链接http://developer.android.com/training/notify-user/managing.html

Have a method which returns Notification object.

有一个返回 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
}

Also, note that bigContentViewand RemoteViewsare not completely redrawn. If some of the elements of bigContentView has visibility set to GONE, and if you want to show it the next time, you have to explicitly set visibility to VISIBLE.

另外,请注意bigContentViewRemoteViews并没有完全重绘。如果 bigContentView 的某些元素的可见性设置为GONE,并且如果您想下次显示它,则必须将可见性显式设置为VISIBLE

回答by dykzei eleeot

Don't store Notificationobject, but the Notification.Builderobject. Produce new notification every time before pushing it to

不要存储Notification对象,而是存储Notification.Builder对象。每次推送前生成新通知

NotificationManager.notify( id, notification );