PendingIntent对第一条通知的处理正确,但对其余通知的处理不正确

98
  protected void displayNotification(String response) {
    Intent intent = new Intent(context, testActivity.class);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);

    Notification notification = new Notification(R.drawable.icon, "Upload Started", System.currentTimeMillis());
    notification.setLatestEventInfo(context, "Upload", response, pendingIntent);

    nManager.notify((int)System.currentTimeMillis(), notification);
}

这个函数将被多次调用。我希望每个 notification 被点击时都能启动 testActivity。但不幸的是,只有第一个通知会启动 testActivity。点击其余的通知会使通知窗口最小化。

额外信息:函数 displayNotification() 在名为 UploadManager 的类中。从实例化的 activity 中传递 ContextUploadManager 中。函数 displayNotification() 从一个也在 UploadManager 中运行的函数(在 AsyncTask 中)多次调用。

编辑1:我忘记提到我正在将字符串 response 作为 extra 传递到 Intent intent 中。

  protected void displayNotification(String response) {
    Intent intent = new Intent(context, testActivity.class);
    intent.putExtra("response", response);
    PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);

这非常重要,因为我需要额外的“响应”反映通知创建时 String response 的内容。使用 PendingIntent.FLAG_UPDATE_CURRENT,额外的“响应”会反映最后一次调用 displayNotification() 时 String response 的内容。

我读过关于 FLAG_UPDATE_CURRENT 的文档,所以知道这是什么原因。但是,目前我不确定如何解决这个问题。

14个回答

141
不要在 PendingIntent.getActivity 中使用 Intent.FLAG_ACTIVITY_NEW_TASK,应该使用 FLAG_ONE_SHOT 代替。

从评论中复制:

然后在Intent上设置一些虚拟操作,否则extras会被丢弃。例如:

intent.setAction(Long.toString(System.currentTimeMillis()))

34
否则 extras 将会被丢弃,因此在意图上设置一些虚拟操作。例如:intent.setAction("foo")。 - ognian
21
好的,已经完成翻译。以下是您需要的内容:太棒了,那个方法起作用了。我遵循 mbauer 建议使用 FLAG_UPDATE_CURRENT 并结合 setAction(Long.toString(System.currentTimeMillis())) 方法。使用 FLAG_ONE_SHOT 只允许我点击通知一次(这很有道理)。非常感谢 ognian。 - user350617
5
“否则,给Intent设置一些虚拟操作,否则extras将会被删除” - 这个文档在哪里可以找到? - Mr_and_Mrs_D
setAction机制对我起作用了。至于文档,不确定,但Android的源代码可在https://android.googlesource.com/上获取。;-) - Norman H
1
此外,如果您正在从非“Activity”上下文(例如从“Service”)创建“PendingIntent”,则仍然需要在“Intent”上设置“FLAG_ACTIVITY_NEW_TASK”。 - David Wasser
显示剩余4条评论

67

HomeScreen小部件上,我一直在使用RemoteViews和若干不同的Intents来处理每个Button的问题。 添加以下内容后解决了问题:

1. intent.setAction(Long.toString(System.currentTimeMillis()));

2. PendingIntent.FLAG_UPDATE_CURRENT

        PackageManager pm = context.getPackageManager();

        Intent intent = new Intent(context, MyOwnActivity.class);
        intent.putExtra("foo_bar_extra_key", "foo_bar_extra_value");
        intent.setAction(Long.toString(System.currentTimeMillis()));
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
                intent, PendingIntent.FLAG_UPDATE_CURRENT);
        RemoteViews views = new RemoteViews(context.getPackageName(),
                R.layout.widget_layout);
        views.setOnClickPendingIntent(my_button_r_id_received_in_parameter, pendingIntent);

+1 酷!谢谢。不知道为什么添加intent.setAction()会使其起作用? - AjOnFire
3
我很喜欢Android SDK对开发人员的直觉性支持...(:♥️ 顺便看一下@ObjectiveTruth下面的答案,了解setAction的原因。 - Aviel Gross
1
出现了奇怪的行为,如果没有调用setAction方法,意图附加项在调试时可以工作,但是当不调试时,意图附加项始终与第一次调用中传递的初始附加项相同。我发现,在调试时,当导航离开应用程序时,总是会调用onCreate,但是在没有调试时,只调用onStart而不调用onCreate。调用setAction方法解决了这个问题,我猜测这与意图不被“不同”有关,如果只有额外的值已更改。 - MaxJ
@clu 既然我已经在使用 setAction,那么你可以使用 addCategoryPendingIntent 使用 Intent.filterEquals 来检查动作、数据、类型、类和类别的相等性。https://developer.android.com/reference/android/content/Intent.html#filterEquals(android.content.Intent) - iamreptar
只有使用 PendingIntent.FLAG_ONE_SHOT 对我有效.. 这会引起其他问题吗? - Jishant
显示剩余2条评论

50

SetAction为我解决了问题。以下是我的理解:


我有多个小部件,每个小部件都附带一个PendingIntent。每当其中一个得到更新时,它们全部得到更新。标志用于描述完全相同的PendingIntent所发生的情况。

现在FLAG_UPDATE_CURRENT的描述更清晰了:

如果您正在制作的相同PendingIntent已经存在,则将所有旧PendingIntent更新为您正在制作的新PendingIntent。

完全相同的定义会查看整个PendingIntent,除了额外的内容(extras)。因此,即使每个意图上有不同的额外内容(对我来说,我正在添加appWidgetId),则对于Android而言,它们仍然是相同的。

使用一些虚拟的唯一字符串添加.setAction告诉操作系统,这些是完全不同的,不会更新任何内容。最终,以下是我的实现,它按照我想要的方式工作,其中每个小部件都附有自己的配置意图:

Intent configureIntent = new Intent(context, ActivityPreferences.class);

configureIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);

configureIntent.setAction("dummy_unique_action_identifyer" + appWidgetId);

PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, configureIntent,
    PendingIntent.FLAG_UPDATE_CURRENT);

更新


如果您正在处理广播,则以下解决方案更佳。唯一的request code可以定义唯一的PendingIntent。这是我的解决方案:

//Weee, magic number, just want it to be positive nextInt(int r) means between 0 and r
int dummyuniqueInt = new Random().nextInt(543254); 
PendingIntent pendingClearScreenIntent = PendingIntent.getBroadcast(context, 
    dummyuniqueInt, clearScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);

2
优雅简洁的解决方案 - Varun Garg
1
对我来说,使用唯一的id和PendingIntent.FLAG_ONE_SHOT来创建待定意图,再加上在意图上设置setAction方法是有效的。 - Kaustuv
有点奇怪,它似乎没有考虑到意图更改的额外变化,但这似乎是真的 :/ - zeroDivider
唯一的PendingIntents解决了问题。 - Mahmoud Mabrok

24

我看到答案但没有解释。此外,所有答案都没有涉及所有可能的解决方案,因此我将尝试澄清这一点。

文档:

如果您真正需要同时使用多个不同的PendingIntent对象(例如作为同时显示的两个通知),那么您需要确保有某些不同之处将它们与不同的PendingIntents关联起来。这可以是任何被Intent.filterEquals考虑的Intent属性,或者是提供给getActivity(Context,int,Intent,int)、getActivities(Context,int,Intent[],int)、getBroadcast(Context,int,Intent,int)或getService(Context,int,Intent,int)的不同请求代码整数。

问题的原因:

你创建了两个带有两个待处理意图的通知。每个待处理意图都与一个意图相关联:

Intent intent = new Intent(context, testActivity.class);

然而,这两个意图是相等的,因此当第二个通知到达时,它将启动第一个意图。

解决方案:

您必须使每个意图独特,以便不会有任何挂起的意图相等。如何使意图独特?不是通过您使用 putExtra() 的附加参数。即使这些附加参数不同,意图仍可能相等。要使每个意图唯一,必须为意图操作、数据、类型、类或请求代码设置唯一值:(其中任何一个都将起作用)

  • 操作:intent.setAction(...)
  • 数据:intent.setData(...)
  • 类型:intent.setType(...)
  • 类:intent.setClass(...)
  • 类别:intent.addCategory(...)
  • 请求代码:PendingIntent.getActivity(context, YOUR_UNIQUE_CODE, intent, Intent.FLAG_ONE_SHOT);

注意:设置唯一请求代码可能有些棘手,因为您需要一个int,而 System.currentTimeMillis() 返回long,这意味着一些数字将被删除。因此,我建议使用类别操作并设置唯一字符串。


这是最终对我起作用的方法,为每个通知使用唯一的ID(无论如何都需要可取消性),并为每个操作使用自定义类别(在同一通知上永远不会有多个相同类型的操作)。 - MandisaW
是的,我也在为每个意图使用独特的类别,效果很好。 - steliosf
遇到了同样的问题。两个通知同时触发,当我点击第二个通知时,什么也没有发生。在设置了 setAction(Long.toString(System.currentTimeMillis())); 之后,它就像魔法一样正常工作了。感谢 @MScott 的详细解释。 - Anantha Babu

13

我遇到了同样的问题,并且通过将标志更改为以下内容解决了它:

PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

非常感谢您抽出时间发布解决问题的方法。我忘了提到我在 Intent 中传入了一个额外参数。这使得问题变得更加复杂。请查看我的编辑1。 - user350617

9
根据文档所述,请使用唯一请求代码:
如果您真的需要同时存在多个不同的PendingIntent对象(例如用作同时显示的两个通知),则需要确保它们之间存在不同之处,以将它们与不同的PendingIntents相关联。这可以是Intent.filterEquals考虑的任何Intent属性之一,或者是提供给getActivity(Context、int、Intent、int)、getActivities(Context、int、Intent[]、int)、getBroadcast(Context、int、Intent、int)或getService(Context、int、Intent、int)的不同请求代码整数。

1
这是唯一真正且直戳要点的答案。我一直在寻找它,因为我想发布同样的内容。 :-) - Sevastyan Savanyuk

8

就我个人而言,使用PendingIntent.FLAG_CANCEL_CURRENT比使用PendingIntent.FLAG_UPDATE_CURRENT更加成功。


我完全同意这个观点。如果我们可以让它取消旧的意图然后创建一个新的意图,那么就没有必要在意图中填充无用的额外信息。虽然有时候这可能是无用的,但现在的问题是“是为了节省内存还是为了节省时间”。 - zeroDivider

6

我曾经遇到同样的问题,并通过以下步骤解决:

1)清除任何意图标记

intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);

2) 通过以下代码插入intent.setAction

 intent.setAction(Long.toString(System.currentTimeMillis()));

3)对于PendingIntent,插入以下代码:

   PendingIntent Pintent = PendingIntent.getActivity(ctx,0, intent,PendingIntent.FLAG_UPDATE_CURRENT);

I hope to work with you


1
有人可以解释一下为什么这个答案被踩了吗?这个方法对我很有效。我不知道这是否是合法的答案,但这个解决方案对我来说是完美的解决方案。 - Sandeep R
也对我有用!谢谢! - Andres

2
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, Intent.FLAG_ACTIVITY_NEW_TASK);

在 PendingIntent 中有两个 int 参数,第二个和最后一个。第二个参数是“请求码”,它必须是唯一的数字(例如您的通知的 ID),否则(如在您的示例中它等于零),它将始终被覆盖。

1

创建PendingIntent对象时,我们调用PendingIntent.getActivity(mContext, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT)方法;

因此,在此处使用以下代码生成PendingIntent并将其发送到通知。当您获取另一个通知时,它将具有唯一的requestCode以仅获取该通知的数据。

Random objRandom = new Random();
final PendingIntent resultPendingIntent =
        PendingIntent.getActivity(
                mContext,
                objRandom.nextInt(100),
                intent,
                PendingIntent.FLAG_UPDATE_CURRENT
        );

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