生成int类型的唯一ID作为Android通知ID

80

当我发送多个推送通知时,我需要它们都按照发送时间降序排列显示在通知栏中。我知道应该使用唯一的通知 - 我尝试生成随机数,但这不能解决我的问题,因为我需要它们被排序。我尝试使用AtomicInt,但仍未得到所需的结果。

package com.mypackage.lebadagency;
import java.util.concurrent.atomic.AtomicInteger;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.NotificationCompat;
import android.util.Log;



import android.widget.RemoteViews;

import com.google.android.gms.gcm.GoogleCloudMessaging;

public class GCMNotificationIntentService extends IntentService {

  private AtomicInteger c = new AtomicInteger(0);
  public int NOTIFICATION_ID = c.incrementAndGet(); 

  private NotificationManager mNotificationManager;
  NotificationCompat.Builder builder;

  public GCMNotificationIntentService() {
    super("GcmIntentService");
  }

  public static final String TAG = "GCMNotificationIntentService";

  @Override
  protected void onHandleIntent(Intent intent) {
    Bundle extras = intent.getExtras();
    GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);

    String messageType = gcm.getMessageType(intent);

    if (!extras.isEmpty()) {
      if (GoogleCloudMessaging.MESSAGE_TYPE_SEND_ERROR
          .equals(messageType)) {
        sendNotification("Send error: " + extras.toString());
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_DELETED
          .equals(messageType)) {
        sendNotification("Deleted messages on server: "
            + extras.toString());
      } else if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE
          .equals(messageType)) {

        for (int i = 0; i < 3; i++) {
          Log.i(TAG,
              "Working... " + (i + 1) + "/5 @ "
                  + SystemClock.elapsedRealtime());
          try {
            Thread.sleep(5000);
          } catch (InterruptedException e) {
          }

        }
        Log.i(TAG, "Completed work @ " + SystemClock.elapsedRealtime());

        sendNotification(""
            + extras.get(Config.MESSAGE_KEY));
        Log.i(TAG, "Received: " + extras.toString());
      }
    }
    GcmBroadcastReceiver.completeWakefulIntent(intent);
  }

  private void sendNotification(String msg) {

    Log.d(TAG, "Preparing to send notification...: " + msg);
    mNotificationManager = (NotificationManager) this
        .getSystemService(Context.NOTIFICATION_SERVICE);
    //here start
    Intent gcmintent = new Intent(this, AppGcmStation.class);
    gcmintent.putExtra("ntitle", msg);
    gcmintent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    int requestID = (int) System.currentTimeMillis();
    //here end
    PendingIntent contentIntent = PendingIntent.getActivity(this, requestID,
        gcmintent, PendingIntent.FLAG_UPDATE_CURRENT);

    NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(
        this).setSmallIcon(R.drawable.ic_launcher)
        .setContentTitle("my title")
        .setStyle(new NotificationCompat.BigTextStyle().bigText(msg))
        .setContentText(msg);
    mBuilder.setAutoCancel(true);
    mBuilder.setTicker(msg);
    mBuilder.setVibrate(new long[] { 1000, 1000, 1000, 1000, 1000 }); 
    mBuilder.setLights(Color.RED, 3000, 3000);
    mBuilder.setContentIntent(contentIntent);
    mBuilder.setDefaults(Notification.DEFAULT_SOUND);



    mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
    Log.d(TAG, "Notification sent successfully.");
  }
}

我需要最好且最简单的方法来生成一个递增的整数 id,以分配为通知 id。


1
你得到了什么结果,与期望的结果有何不同?我尝试使用AtomicInt,但仍然没有得到期望的结果。 - Ted Hopp
2
当我使用原子操作时,Android 设备会接收到所有通知,但通知列表只显示最新发送的通知。 - Zenogrammer
7个回答

114
您正在为所有通知使用相同的通知ID(该值始终为1)。您可能应将通知ID分离到单独的单例类中:
public class NotificationID {
    private final static AtomicInteger c = new AtomicInteger(0);
    public static int getID() {
        return c.incrementAndGet();
    }
}

在您的代码中,使用 NotificationID.getID() 代替 NOTIFICATION_ID

编辑:正如 @racs 在评论中指出的那样,上述方法并不足以确保如果您的应用程序进程被杀死时会有适当的行为。最少应该从某个活动的保存状态初始化 AtomicInteger 的初始值,而不是从 0 开始。如果通知 ID 需要在应用程序重启时唯一(再次,应用程序进程可能被杀死),则应在每次增量后将最新值保存到某处(可能是共享首选项)并在应用程序启动时恢复。


谢谢您,先生。但是您能否向我解释一下为什么它在单独的类中可以工作,而在我的代码中却无法工作? - Zenogrammer
2
您的代码在初始化“NOTIFICATION_ID”时仅调用一次incrementAndGet。此后,您只需使用分配的值。此外,每次重新创建服务时,它都会将“NOTIFICATION_ID”初始化为1。通过将ID生成器移动到单独的类中,它创建了一个超出服务本身生命周期的持久值(并在内存中持续应用程序的持续时间)。为确保应用程序生成的每个通知都是唯一的,您可能应该将该值持久化到共享首选项(或其他方便的位置),以便它在整个进程关闭时仍然存在。 - Ted Hopp
1
@Zainodis - 是的,对于AtomicInteger与普通的int值来说,相同的行为将会保持不变--连续递增最终会从Integer.MAX_VALUE回绕到Integer.MIN_VALUE - Ted Hopp
21
这不是一个适当的解决方案:如果你的应用程序被强行关闭,那么ID将会重新启动,除非你在应用程序重新启动之间的某个时刻保存先前的状态(例如,保存到共享首选项)。 - racs
我认为这也应该是一个静态类。 - Joe Maher
显示剩余2条评论

37

对于仍在寻找答案的人。我生成了一个时间戳并将其用作ID。

import java.util.Date;
import java.util.Locale;

public int createID(){
   Date now = new Date();
   int id = Integer.parseInt(new SimpleDateFormat("ddHHmmss",  Locale.US).format(now));
   return id;
}

像这样使用它

int id = createID();
mNotifyManager.notify(id, mBuilder.build());

2
@RaphaelC 你可能想在这个格式中添加毫秒,这样它就会变成“ddHHmmssSS”。 - Paul Freez
12
如果您在同一毫秒内收到多个通知,情况也是如此,但我同意概率很小。在这种情况下,您也可以简单地使用以下代码: int id = (int)System.currentTimeMillis(); 这样可以避免任何解析处理。 - Raphael C
2
@Paul Freez 当然可以工作。它只会被截断。 - Raphael C
1
@marcosE,这不是我的方法,我只是在回应。请看我的第一个答案:“所以,如果您在同一秒钟内收到多个通知,它仍会取消先前的通知”。 - Raphael C
4
我不建议采用这种方法。用户可能会更改系统时钟(或在不同时区之间移动,或者使用夏令时),从而意外地导致ID冲突。而且,在同一秒钟内发送的多个通知将具有相同的ID。 - Bip901
显示剩余5条评论

22

也许不是最好的方法,但肯定是最简单的方法,就是使用当前时间。

int oneTimeID = (int) SystemClock.uptimeMillis();
mNotificationManager.notify(oneTimeID, mBuilder.build());

优点:这是获得递增ID最简单的方法。

缺点:时间是一个 long 类型,并且我们将其截断为一半。这意味着计数器将在大约每25天循环一次,即 2'147'483'647 /1000(毫秒->秒)/60(秒->分)/60(分->时)/24(时->天)。

SystemClock.uptimeMillis() 相比于 currentTimeMillis 具有两个优点:

  1. 减去了所有处于深度睡眠状态的毫秒数,从而减少了包裹次数。
  2. 当手机重新启动时,它会从 0 开始计数。

int oneTimeID = (int) (SystemClock.uptimeMillis() % 99999999); 这段代码是什么意思? - Sos.
1
@زياد 这将更快地完成循环。 - Agent_L
是的,但这始终是整数,它不会很长,就像SystemClock.uptimeMillis()。 - Sos.
@زياد 这就是将类型转换为(int)的作用。 - Agent_L
这是一个很好的解决方案。另外,如果您想减少与其他通知发生碰撞的概率,并使用一致的 ID,请尝试将这些 ID 保持在一个范围内(即100-500)。 - lasec0203

18
private static final String PREFERENCE_LAST_NOTIF_ID = "PREFERENCE_LAST_NOTIF_ID";

private static int getNextNotifId(Context context) {
    SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
    int id = sharedPreferences.getInt(PREFERENCE_LAST_NOTIF_ID, 0) + 1;
    if (id == Integer.MAX_VALUE) { id = 0; } // isn't this over kill ??? hahaha!!  ^_^
    sharedPreferences.edit().putInt(PREFERENCE_LAST_NOTIF_ID, id).apply();
    return id;
}

1
共享首选项存储是异步完成的,因此在您存储值和实际存储之间,您可能会再次调用此方法,并且getInt()将返回与之前相同的int,这+1将使您获得与先前通知相同的ID。如果您一次收到推送通知并连续快速调用此方法,则可能会发生这种情况。 - AndreiBogdan
7
并非如此:调用apply时,磁盘上的提交是异步的,而内存中的数据会立即更新。如果在一个apply()仍然未完成的情况下,另一个编辑器对这个SharedPreferences执行了普通的commit()操作,那么commit()将被阻塞,直到所有异步提交以及提交本身都已完成。简而言之,这种情况可能不会发生。我还为这种情况编写了一个单元测试,您可以轻松地自行验证这一点。 - Raphael C
2
我可能只是出于好玩而发布这篇文章,但我们必须尊重您的答案在以下情况下无法正常工作:如果您的应用程序发送了 Integer.MAX_VALUE 的通知,那么您的 ID 将重置为值 0,因此它将覆盖具有 ID 0 的第一条通知。但是,这种情况实际上永远不会发生,因为您可能无法在 Android 中拥有如此多的通知,但从理论上讲...忘了它,我只是开玩笑XD 谢谢您的好回答!P.S. 很好的评论 ^_^ - JonasPTFL

5
您可以使用计数器并将其存储在SharedPreferences中。 这是一个Kotlin示例:
fun getNextNotificationId(context: Context) : Int {
    val sp = context.getSharedPreferences("your_shared_preferences_key", MODE_PRIVATE)
    val id = sp.getInt("notification_id_key", 0)
    sp.edit().putInt("notification_id_key", (id + 1) % Int.MAX_VALUE).apply()

    return id
}

这段代码会获取id,并存储下一个id(增加1),如果id达到整数的最大值,它将被重置为0。

您可以像这样使用它:

val notificationId = getNextNotificationId(applicationContext)
notificationManager.notify(notificationId, yourNotification)

5

您需要设置一个唯一的标签(字符串)/ id(整数)

请查看官方文档中的此方法

我建议在通知时使用任何时间戳(SystemClock.uptimeMillis() / System.currentTimeMillis())作为标签。

 notificationManager.notify(String.valueOf(System.currentTimeMillis()), 0, notification);

这应该是此情况下的最佳答案,如果我们需要处理推送通知(以编程方式取消),我们应该跟踪(标签,ID)。我认为最佳实践是为特定目的定义ID,而不是魔数0。 - NguyenDat
如果在没有传递标签的情况下通知(notify())或取消(cancel())一个通知,而您有两个具有相同ID但不同标签的可见通知,那么会发生什么?它们都将被取消或更新吗? - Malachiasz

1
如果有人阅读这篇文章,可以在这里找到一种方法。最好还要指定tag名称和id,这样如果你将其作为模块与开发者共享,就会更加方便。
注意:随意分配整数ID的问题是,如果任何模块或库使用相同的ID,则您的通知将被新的通知数据替换。
// here createID method means any generic method of creating an integer id
int id = createID();
// it will uniqly identify your module with uniq tag name & update if present.
mNotifyManager.notify("com.packagename.app", id, mBuilder.build());

5
请说明createID()是什么,如果没有读过其他回答,这可能会让人感到困惑。 - user905686
@我是蝙蝠侠。谢谢。现在我想知道这怎么回答问题?添加标签会如何影响顺序?而且它并没有解决需要唯一ID的原始问题。如果您想将其作为额外信息添加,请考虑用评论替换您的答案。 - user905686
@user905686 使用一个独特的标签名称,比如你创建的下载器库的模块,用户应用程序代码中的一些随机数将不会更新/取消您的通知。 - Renjith Thankachan
@user905686请查看我回答中的注意事项 - Renjith Thankachan
@我是蝙蝠侠 嗯,得到相同的随机数的概率是微乎其微的。如果你想使用非随机ID可能会有所帮助,但你仍然需要确保用户不使用相同的标签。 - user905686
@user905686 我回答了那些制作SDK或库的人的问题,而不是你的问题;) 你可以使用jQuery。 - Renjith Thankachan

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