DP5 7.0 - 向未决意图添加额外内容会失败吗?
DP5 7.0 - Does adding extras to a pending intent fail?
在跟踪器上添加链接问题:
https://code.google.com/p/android/issues/detail?id=216581&thanks=216581&ts=1468962325
所以我今天在我的 Nexus 5X 上安装了 DP5 Android 7.0 版本。我一直在开发一个应用程序,它使用 Android 的 AlarmManager class 在特定时间安排本地通知。在此版本之前,代码在设备 运行 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 预览版以下的所有内容)。 .so 我尽可能地遵循以下答案:
How can I correctly pass unique extras to a pending intent?
还有其他人有这个问题吗?
我之前曾看到过此类行为的报告,涉及自定义 Parcelable
对象和系统服务(例如,NotificationManager
)。似乎发生的是系统尝试使用 PendingIntent
,并且出于某种原因它试图取消 Parcel
Parcelable
作为其中的一部分。这失败了,因为系统没有你的 类。我已经有一段时间没听说有人 运行 参与其中了,但完全有可能 Android N 中出现回归并重新引入它。
您可能会翻阅 LogCat 以查看是否有任何来自系统(而不是您的应用程序)的消息——或者更好的是堆栈跟踪——似乎与您的警报事件有关。
如果您可以创建可重现的测试用例,请在 the Android issue tracker 上提交问题。如果你想到它,post 一个 link 到这里,因为我想看看它。
关于解决方法,我可以想到两个:
不要把 Parcelable
放在那里。相反,放置一个 ID,您可以根据需要使用该 ID 来查找信息,无论是从内存中的缓存(如果您的进程恰好还在)还是从您的持久数据存储中。
从 Parcelable
切换到我和其他人所说的 "bundle-able",您可以在其中将对象与 Bundle
相互转换。基本上,仅坚持 OS 定义的 类,没有自定义 类。然后,系统可以安全地取消 Parcel
和 Bundle
(无论出于何种原因)。当然,这比简单地使用注解处理器来创建 Parcelable
实现要痛苦得多。
对于任何最终在这里通过 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 对象 = gson.fromJson(json, type) 应该可行。
干杯。
我发现将 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 错误跟踪器上评论了这个问题:https://code.google.com/p/android/issues/detail?id=209422#c11 但我怀疑它是否会收到回复,因为这个问题已经被标记为已关闭。
在跟踪器上添加链接问题: https://code.google.com/p/android/issues/detail?id=216581&thanks=216581&ts=1468962325
所以我今天在我的 Nexus 5X 上安装了 DP5 Android 7.0 版本。我一直在开发一个应用程序,它使用 Android 的 AlarmManager class 在特定时间安排本地通知。在此版本之前,代码在设备 运行 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 预览版以下的所有内容)。 .so 我尽可能地遵循以下答案:
How can I correctly pass unique extras to a pending intent?
还有其他人有这个问题吗?
我之前曾看到过此类行为的报告,涉及自定义 Parcelable
对象和系统服务(例如,NotificationManager
)。似乎发生的是系统尝试使用 PendingIntent
,并且出于某种原因它试图取消 Parcel
Parcelable
作为其中的一部分。这失败了,因为系统没有你的 类。我已经有一段时间没听说有人 运行 参与其中了,但完全有可能 Android N 中出现回归并重新引入它。
您可能会翻阅 LogCat 以查看是否有任何来自系统(而不是您的应用程序)的消息——或者更好的是堆栈跟踪——似乎与您的警报事件有关。
如果您可以创建可重现的测试用例,请在 the Android issue tracker 上提交问题。如果你想到它,post 一个 link 到这里,因为我想看看它。
关于解决方法,我可以想到两个:
不要把
Parcelable
放在那里。相反,放置一个 ID,您可以根据需要使用该 ID 来查找信息,无论是从内存中的缓存(如果您的进程恰好还在)还是从您的持久数据存储中。从
Parcelable
切换到我和其他人所说的 "bundle-able",您可以在其中将对象与Bundle
相互转换。基本上,仅坚持 OS 定义的 类,没有自定义 类。然后,系统可以安全地取消Parcel
和Bundle
(无论出于何种原因)。当然,这比简单地使用注解处理器来创建Parcelable
实现要痛苦得多。
对于任何最终在这里通过 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 对象 = gson.fromJson(json, type) 应该可行。
干杯。
我发现将 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 错误跟踪器上评论了这个问题:https://code.google.com/p/android/issues/detail?id=209422#c11 但我怀疑它是否会收到回复,因为这个问题已经被标记为已关闭。