【问题标题】:DP5 7.0 - Does adding extras to a pending intent fail?DP5 7.0 - 向未决意图添加额外内容会失败吗?
【发布时间】:2016-11-22 19:03:36
【问题描述】:

在跟踪器上添加链接问题: https://code.google.com/p/android/issues/detail?id=216581&thanks=216581&ts=1468962325

所以我今天在我的 Nexus 5X 上安装了 DP5 Android 7.0 版本。我一直在开发一个使用 Android 的 AlarmManager 类在特定时间安排本地通知的应用程序。在此版本之前,该代码在运行 KitKat、Lollipop 和 Marshmallow 的设备上运行良好。

以下是我如何安排闹钟:

Intent intent = new Intent(context, AlarmManagerUtil.class);
            intent.setAction(AlarmManagerUtil.SET_NOTIFICATION_INTENT);
            intent.putExtra(AlarmManagerUtil.REMINDER_EXTRA, Parcels.wrap(reminders));
            intent.putExtra("time", when.getMillis());
            PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            if (alarmManager != null) {
                if (Build.VERSION.SDK_INT >= 23) {
                  alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, when.getMillis(), pendingIntent);
                } else if (Build.VERSION.SDK_INT >= 19) {
                    alarmManager.setExact(AlarmManager.RTC_WAKEUP, when.getMillis(), pendingIntent);
                } else {
                    alarmManager.set(AlarmManager.RTC_WAKEUP, when.getMillis(), pendingIntent);
                }

我的“SET_NOTIFICATION_INTENT”的AlarmManagerUtil @onReceive 看起来像这样:

public void fireNotification(Context context, Intent intent) {
    List<Reminder> reminderToFire = Parcels.unwrap(intent.getParcelableExtra(REMINDER_EXTRA));
    long timeToFire = intent.getLongExtra("time", 0L); //.... }

奇怪的是,这里的“reminderToFire”为空仅在 Android N 设备上,但 timeToFire 是正确的。

我认为它与 Parceler 库有关?我正在使用 Java 1.8 编译并针对 Android API 24。

我确实在网上寻找过这个问题的答案,但我的情况有点独特,因为代码 100% 适用于所有早期版本的 Android(所有低于 N 的预览版)......所以我正在关注尽我所能回答以下问题:

How can I correctly pass unique extras to a pending intent?

其他人有这个问题吗?

【问题讨论】:

  • 您是否查看过Parcels.wrap(reminders) 是否返回null?在将值传递给Parcels.unwrap() 之前,您是否查看过intent.getParcelableExtra(REMINDER_EXTRA) 是否返回null?您是否尝试将其他一些 Parcelable 填充到 Intent 中以查看它是否能在旅途中幸存下来(例如,Point)?
  • @CommonsWare Intent 被传递到上面的 PendingIntent 参数中,正在加载 mExtras 对象,其中 mMap 包含两个对象,Long timeToFire 和 Parceled 提醒对象。在 AlarmManager onReceive 中展开后,Long 有效,但 reminder 对象为空。我会尝试 Parcel 一些其他对象,例如 Point 并看看它是如何进行的。 Reminder 正确配置为 @Parcel
  • "在 AlarmManager onReceive 中解包后,Long 有效,但提醒对象为空" -- 但是,是 intent.getParcelableExtra(REMINDER_EXTRA) null?这将表明价值正在丢失。如果intent.getParcelableExtra(REMINDER_EXTRA) 不是null,但Parcels.unwrap(intent.getParcelableExtra(REMINDER_EXTRA)) 是null,则表明Parcels 难以恢复对象。
  • @CommonsWare 啊,没有intent.getParcelableExtra(REMINDER_EXTRA) 正确有数据;但是,意图中的 mExtras 对象会丢失 mMap 对象,直到它尝试获取额外内容。现在调试,实际上看起来 unwrap 是用 Parcelable input = null 调用的,因此返回 null。运行早期版本 Android 的设备并非如此……我将在 Github 上打开 Parceler 库的问题。谢谢先生。
  • “它实际上看起来像使用 Parcelable input = null 调用 unwrap 并因此返回 null” - 我很困惑这如何与“intent.getParcelableExtra(REMINDER_EXTRA) 正确地拥有数据"。

标签: android alarmmanager android-notifications parceler android-7.0-nougat


【解决方案1】:

对于任何最终从 AlarmManager 拉扯头发的人(并且还没有放弃并转到 JobScheduler),Google 在生产 API 24 版本中不支持将 Parcelable 对象传递到 AlarmManager。

我解决这个问题的方法: 如果您需要将列表(或单个对象)发送到 AlarmManager,请将项目作为字符串存储到 SharedPreferences 中。 (Gson.toJson(object, type)) 如果对象是接口,则有许多接口适配器解决方案。我发现一个漂浮在 S/O 周围:

public final class InterfaceAdapter<T> implements JsonSerializer<T>, JsonDeserializer<T> {

public JsonElement serialize(T object, Type interfaceType, JsonSerializationContext context) {
    final JsonObject wrapper = new JsonObject();
    wrapper.addProperty("type", object.getClass().getName());
    wrapper.add("data", context.serialize(object));
    return wrapper;
}

public T deserialize(JsonElement elem, Type interfaceType, JsonDeserializationContext context) throws JsonParseException {
    final JsonObject wrapper = (JsonObject) elem;
    final JsonElement typeName = get(wrapper, "type");
    final JsonElement data = get(wrapper, "data");
    final Type actualType = typeForName(typeName);
    return context.deserialize(data, actualType);
}

private Type typeForName(final JsonElement typeElem) {
    try {
        return Class.forName(typeElem.getAsString());
    } catch (ClassNotFoundException e) {
        throw new JsonParseException(e);
    }
}

private JsonElement get(final JsonObject wrapper, String memberName) {
    final JsonElement elem = wrapper.get(memberName);
    if (elem == null)
        throw new JsonParseException("no '" + memberName + "' member found in what was expected to be an interface wrapper");
    return elem;
}
}

一旦您设置了适配器,如果您使用某种 DI 框架(即 Dagger2),则无需每次都使用 TypeAdapter 设置 GS0N...

@Singleton
@Provides
public Gson providesGson() {
    return new GsonBuilder()
            .registerTypeAdapter(YourInterfaceClass.class, new InterfaceAdapter<YourInterfaceClass>())
            .create();

所以你所要做的就是运行......

/**
 * stores yourInterfaceClass in shared prefs
 */
public void setNextReminder(List<YourInterfaceClass> yourInterfaceClass) {
    Type type = new TypeToken<List<YourInterfaceClass>>() {}.getType();
    sharedPrefs.edit().putString(YOUR_KEY, gson.toJson(yourInterfaceClass, type)).apply();
}

希望这会有所帮助。当然,当您需要将此对象从共享首选项中取出时......

String json = sharedPrefs.getString(YOUR_KEY, "No object found");

执行典型的 List object = gson.fromJson(json, type) 应该可以工作。

干杯。

【讨论】:

  • 一种可能更快的方法是将Parcelable 转换为byte[],当I blogged about this issue 时有人向我指出了这一点。有关示例实现,请参阅 stackoverflow.com/a/18000094/115145。
  • @CommonsWare,很好。这当然是一种解决方法。使用 Parcelable 时风险仍然存在,尽管我的解决方案依赖于 GSON(或其他一些序列化程序)来实现。在任何情况下,任何一种解决方案都肯定有效。
  • 我认为您不需要通过共享首选项。实际上基本类型仍然有效,您可以通过意图传递 json 字符串并在接收时使用 Gson 对其进行解析。我还注意到传递位图没有问题...(实现 Parcelable)
  • 是的,正如 ilansas 所说,将其作为字符串发送就足够了。
  • 幸运的是,我一直在使用我的 POJO 制作 JSON 的好习惯
【解决方案2】:

我以前见过这种行为,使用自定义Parcelable 对象和系统服务(例如NotificationManager)。似乎发生的事情是系统尝试使用PendingIntent,并且由于某种原因它尝试取消ParcelParcelable。这失败了,因为系统没有你的课程。我已经有一段时间没有听说有人遇到这种情况了,但完全有可能在 Android N 中存在回归,重新引入了它。

您可能会翻阅 LogCat 以查看是否有来自系统(而不是您的应用程序)的任何消息(或者更好的是堆栈跟踪)似乎与您的警报事件有关。

如果您可以创建可重现的测试用例,请在 the Android issue tracker 上提交问题。如果您想到它,请在此处发布指向它的链接,因为我想看看它。

就解决方法而言,我可以想到两个:

  1. 不要把Parcelable 放在那里。取而代之的是,放置一个 ID,您可以根据需要使用它来查找信息,无论是从内存中的缓存(如果您的进程恰好还在)还是从您的持久数据存储中。

  2. 从Parcelable 切换到我和其他人所称的“可捆绑”,您可以在其中将对象与Bundle 相互转换。基本上,只坚持操作系统定义的类,没有自定义类。然后,系统可以安全地取消Parcel 和Bundle(无论出于何种原因)。当然,这比简单地使用注释处理器创建Parcelable 实现要痛苦得多。

【讨论】:

  • 你说的没错,我一定会在追踪器上发布问题。它必须与 N 个来源有关(由于尚未正式发布,我们还看不到)
  • @Aceofspadez44:NDP5 资源尚不可用,但NDP4 is。
  • 不错!我也确实针对 NDP4 编译了这个项目并得到了相同的结果。再次感谢您的帮助。我必须将其捆绑或将对象粘贴在共享首选项中并使用密钥稍后处理。
  • @Aceofspadez44:没有可重现的测试用例,我对您的问题不抱太大希望。
【解决方案3】:

我发现将 Parcelable 包装在 Bundle 中是可行的。

// When setting up the PendingIntent for the AlarmManager:
Intent intent = new Intent(context, MyService.class);
MyParcelable myParcelable = new MyParcelable();
Bundle b = new Bundle();
b.putParcelable(EXTRA_MY_PARCELABLE, myParcelable);
intent.putExtra(EXTRA_BUNDLE, b);
PendingIntent.getService(0, intent, 0);

// From the Service (or Activity, BroadcastReceiver, etc.):
Bundle b = intent.getExtra(EXTRA_BUNDLE);
MyParcelable myParcelable = b.getParcelableExtra(EXTRA_MY_PARCELABLE);

但是,我不确定这种方法是否具有前瞻性。我已经在 android bug tracker 上评论了这个问题:https://code.google.com/p/android/issues/detail?id=209422#c11 但我怀疑它会收到回复,因为这个问题已经被标记为关闭。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多