两个或多个带有进度的前台通知在更新其进度时相互替换

Two or more Foreground Notification with Progress is replacing each other while updating its progress

我有一项服务会 运行 在前台上传任务,然后在通知中显示进度。由于用户可能会使用不同的 ID 请求多次上传,因此会有两个或多个前台服务 运行。一切正常,但我想要的是使用此代码显示所有任务的进度通知。

 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)
            .setSmallIcon(R.drawable.ic_file_upload_white_24dp)
            .setContentTitle(getString(R.string.app_name))
            .setContentText(caption)
            .setProgress(100, percentComplete, false)
            .setContentInfo(String.valueOf(percentComplete +"%"))
            .setOngoing(true)
            .setAutoCancel(false);

    NotificationManager manager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    manager.notify(code, builder.build());

    startForeground(code,builder.build());

但不幸的是,通知只是被覆盖了,但如果我删除了 startForeground() 那么一切正常,因为它根据正在进行的任务显示多个进度通知,但如果内存不足,系统可能会终止进程这就是为什么我希望它在前景中 运行。那么我如何才能在前台显示与正在进行的任务数量相等的通知数量?

不同的通知使用不同的id。在此代码中,您使用相同的默认 ID,因此新通知将替换旧通知。

您应该传递不同的 Id 以获得不同的通知。

在您的代码中:

使用getId()方法为每个通知获取不同的ID:

private static final AtomicInteger c = new AtomicInteger(0);
 public static int getID() {
        return c.incrementAndGet();
    }

NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)
            .setSmallIcon(R.drawable.ic_file_upload_white_24dp)
            .setContentTitle(getString(R.string.app_name))
            .setContentText(caption)
            .setProgress(100, percentComplete, false)
            .setContentInfo(String.valueOf(percentComplete +"%"))
            .setOngoing(true)
            .setAutoCancel(false);

    NotificationManager manager =
            (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    manager.notify(getId(), builder.build());

    startForeground(getId(),builder.build()); // you need to pass different id here. Or i think this method is not there.

对于androidO您需要通知渠道:

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel notificationChannel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, "My Notifications",                      NotificationManager.IMPORTANCE_HIGH);

            // Configure the notification channel.
            notificationChannel.setDescription("Channel description");
            notificationChannel.enableLights(true);
            notificationChannel.setLightColor(Color.RED);
            notificationChannel.setVibrationPattern(new long[]{0, 1000, 500, 1000});
            notificationChannel.enableVibration(true);
            notificationChannel.setImportance(NotificationManager.IMPORTANCE_HIGH);
            notificationChannel.setSound(notification, Notification.AUDIO_ATTRIBUTES_DEFAULT);
            notificationManager.createNotificationChannel(notificationChannel);
        }

        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID);

        notificationBuilder.setAutoCancel(true)
                .setDefaults(Notification.DEFAULT_ALL)
                .setWhen(System.currentTimeMillis())
                .setSound(notification)
                .setContentIntent(pendingIntent)
                .setContentText(message)
                .setContentTitle(getResources().getString(R.string.app_name))
                .setSmallIcon(R.drawable.ic_visualogyx_notification)
                .setStyle(new NotificationCompat.BigTextStyle().bigText(message).setBigContentTitle(getResources().getString(R.string.app_name)))
                .setTicker("Hearty365")
                .setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher_round));

        notificationManager.notify(getID(),notificationBuilder.build());

我终于知道怎么做了。 首先,我需要 运行 它在 onCreate 中有一点延迟。

 @Override
public void onCreate (){
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
                startForeground(requestCode, getMyActivityNotification("",completedParts,totalSize));
        }
    },500);

}

然后创建通知提供程序方法。

//Notification provider
private Notification getMyActivityNotification(String caption, long completedUnits, long totalUnits){

    int percentComplete = 0;
    if (totalUnits > 0) {
        percentComplete = (int) (100 * completedUnits / totalUnits);
    }

    //Return the latest progress of task
    return new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT)
            .setSmallIcon(R.drawable.ic_file_upload_white_24dp)
            .setContentTitle(getString(R.string.app_name))
            .setContentText(caption)
            .setProgress(100, percentComplete, false)
            .setContentInfo(String.valueOf(percentComplete +"%"))
            .setOngoing(true)
            .setAutoCancel(false)
            .build();

}

那么更新通知进度应该和调用前台分开

/**
 * Show notification with a progress bar.
 * Updating the progress happens here
 */
protected void showProgressNotification(String caption, long completedUnits, long totalUnits, int code) {

    createDefaultChannel();
    mCaption = caption;
    requestCode = code;
    completedParts = completedUnits;
    totalSize = totalUnits;

    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    if (mNotificationManager != null) {
        //Update the notification bar progress
        mNotificationManager.notify(requestCode,  getMyActivityNotification(caption,completedUnits,totalUnits));
    }
}

在调用 startForeground 之前,您需要调用 ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_DETACH) 以保留旧通知。

以下是一个实用程序class,您可以在前台服务中使用它来管理通知:

class ForegroundNotifications(
    private val context: Context,
    private val foregroundServiceHelper: ForegroundServiceHelper,
    private val notificationService: NotificationService
) {

    var activeNotificationId: Int? = null
        private set

    val notificationIds: Set<Int>
        get() = entries.keys

    val isEmpty: Boolean
        get() = entries.isEmpty()

    private val entries: LinkedHashMap<Int, Notification> = LinkedHashMap()

    fun notifyAndDetachPrevious(notificationId: Int, notification: Notification) {
        synchronized(this) {
            foregroundServiceHelper.startForegroundAndDetachPreviousNotification(
                notificationId,
                notification
            )

            entries[notificationId] = notification
            activeNotificationId = notificationId
        }
    }

    fun cancel(notificationId: Int) {
        synchronized(this) {
            if (notificationId == activeNotificationId) {
                val newActiveNotificationId = entries.keys.findLast { it != activeNotificationId }
                val notification = entries[newActiveNotificationId]

                if (newActiveNotificationId != null && notification != null) {
                    notifyAndDetachPrevious(newActiveNotificationId, notification)
                }
            }

            entries.remove(notificationId)

            if (isEmpty) {
                foregroundServiceHelper.stopForeground()
            } else {
                notificationService.cancel(context, id = notificationId)
            }
        }
    }

}

interface NotificationService {

    fun createDefaultChannel(context: Context)

    fun notify(context: Context, tag: String? = null, id: Int, notification: Notification)

    fun cancel(context: Context, tag: String? = null, id: Int)

}

interface ForegroundServiceHelper {

    fun startForegroundAndDetachPreviousNotification(
        notificationId: Int,
        notification: Notification
    )

    fun stopForeground()

}

class ForegroundServiceHelperImpl(
    private val service: Service
) : ForegroundServiceHelper {

    override fun startForegroundAndDetachPreviousNotification(
        notificationId: Int,
        notification: Notification
    ) {
        ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_DETACH)
        service.startForeground(notificationId, notification)
    }

    override fun stopForeground() {
        ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_REMOVE)
    }

}