如何避免在更改按钮时闪烁通知更新?

28

我有一个通知,支持播放、暂停、快进和后退。

private static Notification createNotification(String interpret, String title, boolean paused) {
//  if (builder == null)
       builder = new NotificationCompat.Builder(context);

    builder.setPriority(Notification.PRIORITY_MAX);
    builder.setAutoCancel(false);
    builder.setContentTitle(title);
    builder.setContentText(interpret);
    builder.setOngoing(true);
    builder.setOnlyAlertOnce(true);
    builder.setSmallIcon(R.drawable.ic_launcher);
    builder.setContentIntent(PendingIntent.getActivity(context, 9, new Intent(context, ApplicationActivity.class), Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT));
    builder.addAction(R.drawable.av_previous, "", PendingIntent.getBroadcast(context.getApplicationContext(), 0, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PREVIOUS), PendingIntent.FLAG_CANCEL_CURRENT));

    if (paused)
        builder.addAction(R.drawable.av_play, "", PendingIntent.getBroadcast(context.getApplicationContext(), 2, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PLAY), PendingIntent.FLAG_CANCEL_CURRENT));
    else
        builder.addAction(R.drawable.av_pause, "", PendingIntent.getBroadcast(context.getApplicationContext(), 3, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.PAUSE), PendingIntent.FLAG_CANCEL_CURRENT));

    builder.addAction(R.drawable.av_next, "", PendingIntent.getBroadcast(context.getApplicationContext(), 1, new Intent(NotificationPlayerControlReceiver.MUSIC_PLAYER_INTENT).putExtra("resultcode", NotificationPlayerControlReceiver.NEXT), PendingIntent.FLAG_CANCEL_CURRENT));

    Notification notification = builder.build();

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
        notification.tickerView = null;

    return notification;
}

更新通知:

 public static void update(String interpret, String title, boolean paused) {
    NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    manager.notify(0, createNotification(interpret, title, paused));
}
为避免更新时的闪烁,我已将构建器设置为全局变量并在每次更新时重复使用它,这很有效。但是重复使用它意味着我添加的所有按钮也会被重复使用,没有可能删除之前添加的操作。
只有在每次更新时重新初始化NotificationCompat.Builder,按钮才能更改,这意味着我会再次出现闪烁。
如何避免闪烁,同时让按钮更改?
编辑: 刚刚查看了Rocket Player,他们也没有解决问题,但Google Play Music解决了问题。

1
我不理解如何通过重用构建器(顺便说一下,在这段代码中你并没有这样做,可能是一个打字错误)来防止闪烁,因为通知对象每次仍然是不同的。 - njzk2
1
我不知道为什么,但这是事实。在这里找到了它https://dev59.com/Tmw15IYBdhLWcg3wxukh - Marian Klühspies
我理解你所提出的问题是,防止闪烁的方法是使用相同的ID。 - njzk2
你确定吗?因为在它目前的状态下,你的代码确实会在每次运行时创建一个新的构建器。 - njzk2
这里有两种可能性:1.注释掉 if (builder == null) 以创建每次一个新的 builder 对象,以便按钮更改可以工作,但通知会闪烁,因为它被完全重建。2.不要注释掉 if (builder == null),以避免闪烁,但按钮无法更改,它们保持在实际状态中。 - Marian Klühspies
显示剩余5条评论
3个回答

17

就像Boris所说的那样,问题在于每个更新都会构建一个新的通知。我的解决方案涵盖了相同的逻辑,但我使用了NotificationBuilder...

下面是代码:

if (mNotificationBuilder == null) {
            mNotificationBuilder = new NotificationCompat.Builder(this)
                    .setSmallIcon(iconId)
                    .setContentTitle(title)
                    .setContentText(message)
                    .setLargeIcon(largeIcon)
                    .setOngoing(true)
                    .setAutoCancel(false);
        } else {
            mNotificationBuilder.setContentTitle(title)
                    .setContentText(message);
        }

请记住,mNotificationBuilder 是该类中的私有字段。


这在2019年的三星S8/S10e上不起作用(可能也包括其他设备)。 - Marius Kaunietis

9
问题在于每次更新时都会创建新的通知。我曾经遇到过同样的问题,当我执行以下步骤时问题得以解决:
  • 在不同调用createNotification之间保留通知实例。
  • 每次从通知栏中删除时将此实例设置为null。
  • 执行以下代码:

代码:

private static Notification createNotification(String interpret, String title, boolean paused) {
   if (mNotification == null) {
       // do the normal stuff you do with the notification builder
   } else {
      // set the notification fields in the class member directly
      ... set other fields.
      // The below method is deprecated, but is the only way I have found to set the content title and text
      mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
   }
   return mNotification;
}

现在,当您调用notify时,不会出现闪烁:

manager.notify(0, createNotification(interpret, title, paused));

补充说明:我也遇到了一个问题,如果我执行了setLatestEventInfo,那么大图标和小图标就会出现问题。这就是为什么我这样做的原因:

int tmpIconResourceIdStore = mNotification.icon;
// this is needed to make the line below not change the large icon of the notification
mNotification.icon = 0;
// The below method is deprecated, but is the only way I have found to set the content title and text
mNotification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
mNotification.icon = tmpIconResourceIdStore;

查看Android代码,这行mNotification.icon = 0;禁用了图标混乱。


1
我保留了 NotificationBuilder 的实例,以消除通知的闪烁。 - JaydeepW

6

我知道这是一个相当老的问题,但由于我在其他地方找不到解决方案,因此我认为回答这个问题可能会帮助其他遇到同样问题的人。

这个问题有点棘手。我今天也遇到了这个问题,在搜索和尝试一段时间后,我找到了一个解决方案。

如何解决这个问题:

为了兼容低于API级别19的设备,我的解决方案是使用支持库中的NotificationCompat类。

正如其他人建议的那样,我保持对NotificationCompat.Builder的引用,只要需要通知就保持它的引用存在。我在我的通知中使用的操作仅在Builder初始创建时添加,而那些根据情况而变化的操作,我还将其存储在服务的私有成员中。在更改时,我重新使用Builder对象,并根据需要调整NotificationCompat.Action对象。然后,我调用Builder.getNotification()Builder.build()方法,具体取决于API级别(由于支持库,可能不必要,但我没有检查过。如果可以省略,请写评论,以便我改进代码;)

以下是我刚刚描述的示例代码:

public Notification createForegroundNotification(TaskProgressBean taskProgressBean, boolean indeterminate) {
  Context context = RewardCalculatorApplication.getInstance();

  long maxTime = TaskUtils.getMaxTime(taskEntry);
  long taskElapsedTime = TaskUtils.calculateActualElapsedTime(taskProgressBean);
  long pauseElapsedTime = taskProgressBean.getPauseElapsedTime();

  int pauseToggleActionIcon;
  int pauseToggleActionText;
  PendingIntent pauseToggleActionPI;
  boolean pauseButton = pauseElapsedTime == 0;
  if(pauseButton) {
    pauseToggleActionIcon = R.drawable.ic_stat_av_pause;
    pauseToggleActionText = R.string.btnTaskPause;
    pauseToggleActionPI = getPendingIntentServicePause(context);
  } else {
    pauseToggleActionIcon = R.drawable.ic_stat_av_play_arrow;
    pauseToggleActionText = R.string.btnTaskContinue;
    pauseToggleActionPI = getPendingIntentServiceUnpause(context);
  }

  String contentText = context.getString(R.string.taskForegroundNotificationText,
      TaskUtils.formatTimeForDisplay(taskElapsedTime),
      TaskUtils.formatTimeForDisplay(pauseElapsedTime),
      TaskUtils.formatTimeForDisplay(taskProgressBean.getPauseTotal()));


  // check if we have a builder or not...
  boolean createNotification = foregroundNotificationBuilder == null;
  if(createNotification) { // create one
    foregroundNotificationBuilder = new NotificationCompat.Builder(context);

    // set the data that never changes...plus the pauseAction, because we don't change the
    // pauseAction-object, only it's data...
    pauseAction = new NotificationCompat.Action(pauseToggleActionIcon, getString(pauseToggleActionText), pauseToggleActionPI);
    foregroundNotificationBuilder
        .setContentTitle(taskEntry.getName())
        .setSmallIcon(R.drawable.ic_launcher)
        .setContentIntent(getPendingIntentActivity(context))
        .setOngoing(true)
        .addAction(R.drawable.ic_stat_action_done, getString(R.string.btnTaskFinish), getPendingIntentServiceFinish(context))
        .addAction(pauseAction);
  }

  // this changes with every update
  foregroundNotificationBuilder.setContentText(contentText);

  if(indeterminate) {
    foregroundNotificationBuilder.setProgress(0, 0, true);
  } else {
    foregroundNotificationBuilder.setProgress((int) maxTime, (int) taskElapsedTime, false);
  }

  // if this is not the creation but the button has changed, change the pauseAction's data...
  if(!createNotification && (pauseButton != foregroundNotificationPauseButton)) {
    foregroundNotificationPauseButton = pauseButton;
    pauseAction.icon = pauseToggleActionIcon;
    pauseAction.title = getString(pauseToggleActionText);
    pauseAction.actionIntent = pauseToggleActionPI;
  }

  return (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
         ? foregroundNotificationBuilder.getNotification() // before jelly bean...
         : foregroundNotificationBuilder.build(); // since jelly bean...
}

foregroundNotificationBuilderpauseActionforegroundNotificationPauseButton 是服务类的私有成员。 getPendingIntent...() 方法是方便方法,只是简单地创建 PendingIntent 对象。

需要更新通知时,调用此方法,同时将其传递给服务的 startForeground() 方法。这解决了通知中闪烁和无法更新操作的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接