通知 DeleteIntent 在更高版本的 Android 上被破坏

Posted

技术标签:

【中文标题】通知 DeleteIntent 在更高版本的 Android 上被破坏【英文标题】:Notification DeleteIntent broken on later versions of Android 【发布时间】:2014-09-06 07:11:11 【问题描述】:

在我们的应用 OneBusAway android (open-source on Github) 中,我们需要在用户关闭特定提醒通知时收到通知,因此我们不会针对同一事件发布另一个提醒通知(他们的巴士到达还有多长时间)。

我们通过在我们的应用程序中侦听Intent 来做到这一点,注册为DeleteIntentNotification。当用户关闭通知时(通过滑动或点击通知窗口中的清除按钮),我们的应用应该会收到 Intent

从测试来看,使用the current version on Google Play(和current master branch on Github),我们的应用程序在以下Android版本中永远不会收到DeleteIntent:

Android 4.4.3 Android 4.4.4

但是,完全相同的代码确实在以下情况下起作用(即,注册为 DeleteIntent 的 Intent 被应用接收):

Android 2.3.3 Android 2.3.6 Android 4.1.1 Android 4.1.2

我查看了以下处理 DeleteIntent 的 SO 帖子,列出的解决方案均不适用于 Android 4.4.3 和 4.4.4:

Notification Auto-Cancel does not call DeleteIntent Android - DeleteIntent, how to use? Notification deleteIntent does not work https://***.com/questions/24218626/how-to-detect-notification-cancel-event-in-android-not-deleteintent https://***.com/questions/22769523/why-my-deleteintent-is-not-working-on-my-notification Android deleteIntent not working? What's wrong with my code? Custom actions using implicit intents between applications

当前工作的 master 分支使用一个 Service 来监听 Intent。然而,基于上面的一些帖子,我确实调整了一些代码,使其更符合使用 BroadcastReceiver 来监听 Intent 的工作示例。

使用 BroadcastReceiver 的代码在以下 Github 分支中:

https://github.com/CUTR-at-USF/onebusaway-android/tree/issue104-RepeatingReminders

以下是我当前版本的摘录(仍然适用于 Android 4.1.2 及更低版本,但不适用于 4.4.3 或 4.4.4),以及指向 Github 源的链接:


创建通知

https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/java/com/joulespersecond/seattlebusbot/tripservice/NotifierTask.java#L131

private Notification createNotification(Uri alertUri) 
    //Log.d(TAG, "Creating notification for alert: " + alertUri);
    Intent deleteIntent = new Intent(mContext, AlarmReceiver.class);
    deleteIntent.setAction(TripService.ACTION_CANCEL);
    deleteIntent.setData(alertUri);

    return new NotificationCompat.Builder(mContext)
            .setSmallIcon(R.drawable.ic_stat_notification)
            .setDefaults(Notification.DEFAULT_ALL)
            .setOnlyAlertOnce(true)
            .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
                    deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT))
            .setAutoCancel(true)
            .build();

标题和其他动态通知信息会在几行之后设置(如果通知仍未关闭,则稍后重置):

@SuppressWarnings("deprecation")
private void setLatestInfo(Notification notification,
        String stopId,
        String routeId,
        long timeDiff) 
    final String title = mContext.getString(R.string.app_name);

    final PendingIntent intent = PendingIntent.getActivity(mContext, 0,
            new ArrivalsListActivity.Builder(mContext, stopId).getIntent(),
            PendingIntent.FLAG_UPDATE_CURRENT);

    notification.setLatestEventInfo(mContext,
            title,
            getNotifyText(routeId, timeDiff),
            intent);

TripService 包含操作的常量:

public static final String ACTION_CANCEL =
        "com.joulespersecond.seattlebusbot.action.CANCEL";

报警接收器

https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/java/com/joulespersecond/seattlebusbot/AlarmReceiver.java

public class AlarmReceiver extends BroadcastReceiver 

    private static final String TAG = "AlarmReceiver";

    @Override
    public void onReceive(Context context, Intent intent) 
        Log.d(TAG, "In onReceive with intent action " + intent.getAction());
        ...
    

AndroidManifest

https://github.com/CUTR-at-USF/onebusaway-android/blob/issue104-RepeatingReminders/onebusaway-android/src/main/AndroidManifest.xml

<receiver android:name=".AlarmReceiver">
     <!-- These action names must match the constants in TripService -->
      <intent-filter>
         <action android:name="com.joulespersecond.seattlebusbot.action.SCHEDULE" />
         <action android:name="com.joulespersecond.seattlebusbot.action.POLL" />
         <action android:name="com.joulespersecond.seattlebusbot.action.CANCEL" />
     </intent-filter>
 </receiver>

根据上述情况,在 Android 4.4.3/4.4.4 上,当用户关闭通知时,AlarmReceiver 永远不会看到 Intent。

我还尝试添加一个 MIME 类型,如 Custom actions using implicit intents between applications 中指定的那样,但这在 Android 4.4.3/4.4.4 上也不起作用:

Intent deleteIntent = new Intent(mContext, AlarmReceiver.class);
    deleteIntent.setAction(TripService.ACTION_CANCEL);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) 
        deleteIntent.setDataAndTypeAndNormalize(alertUri, TripService.REMINDER_MIME_TYPE);
     else 
        deleteIntent.setDataAndType(alertUri, TripService.REMINDER_MIME_TYPE);
    

    return new NotificationCompat.Builder(mContext)
            .setSmallIcon(R.drawable.ic_stat_notification)
            .setDefaults(Notification.DEFAULT_ALL)
            .setOnlyAlertOnce(true)
            .setDeleteIntent(PendingIntent.getBroadcast(mContext, 0,
                    deleteIntent, 0))
                    //.setLights(0xFF00FF00, 1000, 1000)
                    //.setVibrate(VIBRATE_PATTERN)
            .build();

REMINDER_MIME_TYPEapplication/vnd.com.joulespersecond.seattlebusbot.reminder

使用 MIME 类型的清单:

<receiver android:name=".AlarmReceiver">
        <!-- These action names must match the constants in TripService -->
        <intent-filter>
            <action android:name="com.joulespersecond.seattlebusbot.action.SCHEDULE" />
            <action android:name="com.joulespersecond.seattlebusbot.action.POLL" />
            <action android:name="com.joulespersecond.seattlebusbot.action.CANCEL" />

            <data android:mimeType="application/vnd.com.joulespersecond.seattlebusbot.reminder" />
        </intent-filter>
    </receiver>

我也尝试使用支持库(即使用Notification.Builder 而不是NotificationCompat.Builder),但这也没有改变任何东西。

任何想法为什么这不适用于 Android 4.4.3/4.4.4?

Github issue 中显示了有关此问题的更多信息。

编辑

我还在一个小型 Github 项目“DeleteIntentDemo”中复制了这个问题:

https://github.com/barbeau/DeleteIntentDemo

此项目的 README 中有重现说明。

编辑 2

这似乎是由于 Notification.setLatestEventInfo() 中的 Android 中的一个错误 - 我在这里报告了它: https://code.google.com/p/android/issues/detail?id=73720

请参阅@CommonsWare 的解决方法。

编辑 3

用于修复此问题的我的 AOSP 补丁现已合并,因此在未来的 Android 版本中,旧版应用不会出现此问题: https://code.google.com/p/android/issues/detail?id=73720#c4

但是,在上面的 AOSP 线程中,它强调不应再使用 Notification.setLatestEventInfo() - 而是使用 Notification.Builder 创建一个新的通知。

【问题讨论】:

你试过明确的Intent吗?除非这些操作是第三方应该调用的某些公共 SDK 的一部分,否则您不应该在 AlarmReceiver 上使用 &lt;intent-filter&gt;。我无法通过明确的Intent 重现您的问题——setDeleteIntent() 在运行 4.4.4 的 Nexus 4 上运行良好。 当前正在使用的 Intent deleteIntent = new Intent(mContext, AlarmReceiver.class); 是显式 Intent,对吗?其中一些设计早于我参与该项目的时间,但我相信这个想法是(最终)允许外部应用程序安排/取消交通警报以及从内部代码触发相同的操作。我尝试删除 AlarmReceiver 上的 &lt;intent-filter&gt;s,但这并没有改变任何东西。 @CommonsWare 在我的答案的底部,我在 Github 上添加了一个小示例项目来重现该问题 - github.com/barbeau/DeleteIntentDemo。任何反馈表示赞赏。如果您能提供正常工作的示例,那也会有所帮助。 这是自动取消错误还是别的什么?手动删除通知时会触发 Intent 吗? @SeanBarbeau:“当前使用的是显式 Intent,对吗?” -- 是的,抱歉,我关注的是&lt;intent-filter&gt; 【参考方案1】:

在您的示例项目中,如果您删除以下行,deleteIntent 将在运行 4.4.4 的 Nexus 4 上运行:

setLatestInfo(getActivity(), notification, routeId);

我怀疑这个电话正在消灭你的deleteIntent。作为setLatestInfo() 处理的一部分,将deleteIntent 重新应用于Notification 可能会起作用。

【讨论】:

你是对的!如果我在演示项目中删除该行,它也适用于带有 4.4.4 的 Galaxy S3。查看Notification.setLatestEventInfo() (goo.gl/lqrdwy) 的Android 源代码,看起来Notification.Builder() 在内部用于用新值覆盖当前通知,但当前deleteIntent 从未被复制 - 所以这本质上是一个错误那里。奇怪,因为消除Notification.setLatestEventInfo() 对原始项目没有任何影响(只是再次测试)。除了这个之外,肯定还有其他事情发生...... @SeanBarbeau:请注意,您的示例使用的是NotificationCompat.Builder,而不是您评论中提到的Notification.Builder。如果您想出另一个可以在 4.4.4 上重现您的问题的示例,请告诉我。 对 - 在演示项目中,我最初使用 NotificationCompat.Builder 创建 notification。但是,当稍后调用 notification.setLatestEventInfo() 时,Notification.setLatestEventInfo() 在内部使用 Notification.Builder。因此,即使我最初使用兼容性库创建Notification,平台最终在Notification.setLatestEventInfo() 的过程中使用正常的Notification.Builder。我们在Notification.setLatestEventInfo() 中缺少builder.setDeleteIntent(this.deleteIntent) github.com/barbeau/DeleteIntentDemo/tree/NoCompatLibrary 在不涉及兼容性库的情况下说明了相同的问题。不过,我仍在处理原始应用程序中的问题。在调用Notification.setLatestEventInfo() 后重新应用deleteIntent 确实可以解决问题,但(与演示项目不同)简单地消除对Notification.setLatestEventInfo() 的调用并不能解决问题。我真的很想知道为什么如果没有调用Notification.setLatestEventInfo(),它仍然无法工作。 啊 - 服务问题似乎是我在测试期间的错误。所以,总而言之,一个问题是导致这个 - Notification.setLatestEventInfo() 擦除任何以前注册的 deleteIntent。解决方法是永远不要调用这个方法,或者调用后重新注册deleteIntent。【参考方案2】:

您一定有不同的问题,因为我能够在几个 4.3 和 4.4 模拟器中接收 deleteintent。

我想测试你的“简单”项目,但它使用 Android Studio,所以我自己做了更简单的测试。

重现步骤:

-创建一个Activity并在manifest中设置启动模式为singleInstance。

-在按钮或菜单项的处理程序中,启动通知:

    Intent deleteIntent = new Intent(this, MainActivity.class);

     Notification notification = new NotificationCompat.Builder(this)
    .setSmallIcon(android.R.drawable.ic_dialog_alert)
    .setOnlyAlertOnce(true)
    .setContentTitle("Notification delete intent test")
    .setContentText("Please dismiss this notification by swipping or deleting it. A Toast will be shown if the deletion intent works.")
    .setDeleteIntent(PendingIntent.getActivity(this, 0, deleteIntent, 0))
    .setAutoCancel(true)
    .build();

     NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
     nm.notify((int)System.currentTimeMillis(), notification);

-覆盖onNewIntent 以在取消通知时显示祝酒词或记录消息:

    @Override
    public void onNewIntent(Intent intent)
        Toast.makeText(this, "Notification deleted!", Toast.LENGTH_LONG).show();
    

要关闭通知,请滑动或按清除按钮。按下它不会起作用,因为自动取消不被视为明确的用户操作,因此不会传递删除意图。

【讨论】:

问题似乎(至少部分)与使用Notification.setLatestEventInfo()有关。请参阅上面的@CommonsWare 答案。

以上是关于通知 DeleteIntent 在更高版本的 Android 上被破坏的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们不再需要在更高版本的 ASP.NET Core 中手动验证模型?

React Native 版本 .apk 在更高版本的 android 设备中崩溃,地理定位权限被拒绝错误

React Native Maps - 自定义标记图像未在更高的 Android 版本中呈现

背景位置跟踪以计算他们行进的距离和路线图

如何在更高维度的超球面上均匀分布点?

在Java中如何在更高级别调用函数时显示函数参数? [复制]