使用Parcelable时出现Class not found when unmarshalling问题

  最近在使用Parcelable时遇到了Class not found when unmarshalling的问题,场景为在指定了单独process的Service中使用AlarmManager添加闹钟:

Intent intent = new Intent(context, AlertBroadcastReceiver.class);
AlarmModel nextAlarm = getNextAlarm(alarms);
intent.putExtra(ALARM_EXTRA, nextAlarm);

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
        PendingIntent.FLAG_CANCEL_CURRENT);

AlarmManager manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP, nextAlarm.getNextAlertTime().getTime(),
        pendingIntent);

其中AlarmModel实现了Parcelable。然后在BroadcastReceiver中接收上面的pendingIntent,并取出AlarmModel:

AlarmModel alarmModel = intent.getParcelableExtra(ALARM_EXTRA);

  这段程序在我自己的手机(Android 6.0.1)上可以正常运行,但在Android 7.0的模拟器上会指示Class not found when unmarshalling,出现ClassNotFoundException异常,如下所示:

09-18 18:13:00.021 1490-1526/system_process E/Parcel: Class not found when unmarshalling: com.nex3z.shalarm.presentation.model.AlarmModel
                                                      java.lang.ClassNotFoundException: com.nex3z.shalarm.presentation.model.AlarmModel
                                                          at java.lang.Class.classForName(Native Method)
                                                          at java.lang.Class.forName(Class.java:400)
                                                          at android.os.Parcel.readParcelableCreator(Parcel.java:2507)
                                                          at android.os.Parcel.readParcelable(Parcel.java:2461)
                                                          at android.os.Parcel.readValue(Parcel.java:2364)
                                                          at android.os.Parcel.readArrayMapInternal(Parcel.java:2717)
                                                          at android.os.BaseBundle.unparcel(BaseBundle.java:269)
                                                          at android.os.Bundle.putAll(Bundle.java:226)
                                                          at android.content.Intent.fillIn(Intent.java:8171)
                                                          at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:255)
                                                          at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:216)
                                                          at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:7151)
                                                          at android.app.PendingIntent.send(PendingIntent.java:836)
                                                          at com.android.server.AlarmManagerService$DeliveryTracker.deliverLocked(AlarmManagerService.java:2984)
                                                          at com.android.server.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:2424)
                                                          at com.android.server.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:2543)
                                                       Caused by: java.lang.ClassNotFoundException: com.nex3z.shalarm.presentation.model.AlarmModel
                                                          at java.lang.Class.classForName(Native Method)
                                                          at java.lang.BootClassLoader.findClass(ClassLoader.java:1346)
                                                          at java.lang.BootClassLoader.loadClass(ClassLoader.java:1406)
                                                          at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
                                                          at java.lang.Class.classForName(Native Method) 
                                                          at java.lang.Class.forName(Class.java:400) 
                                                          at android.os.Parcel.readParcelableCreator(Parcel.java:2507) 
                                                          at android.os.Parcel.readParcelable(Parcel.java:2461) 
                                                          at android.os.Parcel.readValue(Parcel.java:2364) 
                                                          at android.os.Parcel.readArrayMapInternal(Parcel.java:2717) 
                                                          at android.os.BaseBundle.unparcel(BaseBundle.java:269) 
                                                          at android.os.Bundle.putAll(Bundle.java:226) 
                                                          at android.content.Intent.fillIn(Intent.java:8171) 
                                                          at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:255) 
                                                          at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:216) 
                                                          at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:7151) 
                                                          at android.app.PendingIntent.send(PendingIntent.java:836) 
                                                          at com.android.server.AlarmManagerService$DeliveryTracker.deliverLocked(AlarmManagerService.java:2984) 
                                                          at com.android.server.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:2424) 
                                                          at com.android.server.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:2543) 
                                                       Caused by: java.lang.NoClassDefFoundError: Class not found using the boot class loader; no stack trace available
09-18 18:13:00.022 1490-1526/system_process W/Bundle: Failed to parse Bundle, but defusing quietly
                                                      android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.nex3z.shalarm.presentation.model.AlarmModel
                                                          at android.os.Parcel.readParcelableCreator(Parcel.java:2535)
                                                          at android.os.Parcel.readParcelable(Parcel.java:2461)
                                                          at android.os.Parcel.readValue(Parcel.java:2364)
                                                          at android.os.Parcel.readArrayMapInternal(Parcel.java:2717)
                                                          at android.os.BaseBundle.unparcel(BaseBundle.java:269)
                                                          at android.os.Bundle.putAll(Bundle.java:226)
                                                          at android.content.Intent.fillIn(Intent.java:8171)
                                                          at com.android.server.am.PendingIntentRecord.sendInner(PendingIntentRecord.java:255)
                                                          at com.android.server.am.PendingIntentRecord.sendWithResult(PendingIntentRecord.java:216)
                                                          at com.android.server.am.ActivityManagerService.sendIntentSender(ActivityManagerService.java:7151)
                                                          at android.app.PendingIntent.send(PendingIntent.java:836)
                                                          at com.android.server.AlarmManagerService$DeliveryTracker.deliverLocked(AlarmManagerService.java:2984)
                                                          at com.android.server.AlarmManagerService.deliverAlarmsLocked(AlarmManagerService.java:2424)
                                                          at com.android.server.AlarmManagerService$AlarmThread.run(AlarmManagerService.java:2543)

  由于其他地方也使用了类似方法来在Activity之间传递数据,在Android 7.0上并没有以上异常,猜测原因应该和远程Service有关,远程Service无法获取AlarmModel的信息。可以找到类似历史Issue

  一个应对方式是先把Parcelable放到Bundle中,再把Bundle放到Intent中,如下所示:

AlarmModel nextAlarm = getNextAlarm(alarms);

Intent intent = new Intent(context, AlertBroadcastReceiver.class);
Bundle alarmBundle = new Bundle();
alarmBundle.putParcelable(ALARM, nextAlarm);
intent.putExtra(ALARM_BUNDLE, alarmBundle);

PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent,
        PendingIntent.FLAG_CANCEL_CURRENT);

AlarmManager manager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
manager.set(AlarmManager.RTC_WAKEUP, nextAlarm.getNextAlertTime().getTime(),
        pendingIntent);

对应地,接收时先获取Bundle,再获取Parcelable:

Bundle alarmBundle = intent.getBundleExtra(ALARM_BUNDLE);
AlarmModel alarmModel = alarmBundle.getParcelable(ALARM);

  在原生AlarmClock中的Alarms.java中还可以看到另一种处理方法,把Parcelable转换为字节数组进行传递:

Intent intent = new Intent(ALARM_ALERT_ACTION);
// XXX: This is a slight hack to avoid an exception in the remote
// AlarmManagerService process. The AlarmManager adds extra data to
// this Intent which causes it to inflate. Since the remote process
// does not know about the Alarm class, it throws a
// ClassNotFoundException.
//
// To avoid this, we marshall the data ourselves and then parcel a plain
// byte[] array. The AlarmReceiver class knows to build the Alarm
// object from the byte[] array.
Parcel out = Parcel.obtain();
alarm.writeToParcel(out, 0);
out.setDataPosition(0);
intent.putExtra(ALARM_RAW_DATA, out.marshall());
PendingIntent sender = PendingIntent.getBroadcast(
        context, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
am.set(AlarmManager.RTC_WAKEUP, atTimeInMillis, sender);

1 Comment

  1. yrzh

    非常有用,7.0才会出现的问题

Comments are closed.