8

在跟踪器上添加链接问题: 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 预览的所有内容)......所以我遵循以下答案尽我所能:

如何正确地将独特的附加内容传递给待处理的意图?

其他人有这个问题吗?

4

3 回答 3

8

对于任何最终从 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) 应该可以。

干杯。

于 2016-08-31T19:49:58.427 回答
2

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

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

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

在解决方法方面,我可以想到两个:

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

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

于 2016-07-19T19:43:52.583 回答
2

我发现将 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但我怀疑它会收到回复,因为这个问题已经被标记为关闭。

于 2017-03-06T23:58:39.790 回答