42

我正在使用RemoteViewscustom创建一个通知Service,该通知在前台模式下与通知一起运行(也就是说,只要通知对用户可见,服务就会保持活动状态)。通知设置为进行中,因此用户无法将其关闭。

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

我的问题是,一旦创建通知并且对用户可见,如果我调用任何RemoteViews's 函数,例如setImageViewResource()更改Bitmap显示在 anImageView中,更改是不可见的,除非我调用setImageViewResource()我之后调用:

NotificationManager.notify( id, notification );

或者

Service.startForeground(id,notification);

不过,这对我来说并不正确。我不敢相信要RemoteViews在已创建的通知中更新 UI,我必须重新初始化通知。如果我可以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);

以及“强制” UI 更改为通知的功能:

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

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

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

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

4

4 回答 4

63

这是一个详细示例,您可以使用以下方法更新通知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

于 2015-06-11T04:09:57.130 回答
15

警告!

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

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

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

在此处输入图像

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

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

于 2018-11-09T09:20:20.700 回答
4

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

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

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

于 2015-06-11T06:21:04.843 回答
1

不要存储Notification对象,而是Notification.Builder对象。每次推送之前都会产生新的通知

NotificationManager.notify( id, notification );
于 2015-02-05T05:58:51.657 回答