20

For my Android application, I get several unmarshalling errors although I think I've done everything that is needed to properly save and load objects via Parcelables. Can you tell me what's wrong with my code?

Error 1:

java.lang.RuntimeException: Unable to start activity ComponentInfo
Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@41279860: Unmarshalling unknown type code 6619241 at offset 1372
at android.os.Parcel.readValue(Parcel.java:1922)
at android.os.Parcel.readMapInternal(Parcel.java:2094)
at android.os.Bundle.unparcel(Bundle.java:223)
at android.os.Bundle.getParcelable(Bundle.java:1158)
at android.app.Activity.onCreate(Activity.java:860)
at my.app.package.PlayComputer.onCreate(PlayComputer.java:1012)
at android.app.Activity.performCreate(Activity.java:4465)

Line 1012 in MyActivity is the call to super.onCreate(savedInstanceState); in the Activity's onCreate().

protected void onSaveInstanceState(Bundle savedInstanceState) {
    if (myObject == null) {
        savedInstanceState.putParcelable("myObject", null);
    }
    else {
        savedInstanceState.putParcelable("myObject", myObject);
    }
    savedInstanceState.putInt(...);
    savedInstanceState.putString(...);
    savedInstanceState.putBoolean(...);
    super.onSaveInstanceState(savedInstanceState);
}

myObject is of class MyObject which has the following methods:

public void writeToParcel(Parcel out, int flags) {
    out.writeIntArray(...);
    out.writeInt(...);
    out.writeStringArray(...);
    out.writeString(...);
    out.writeParcelableArray(..., flags);
}

public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
    public MyObject createFromParcel(Parcel in) {
        try {
            if (in == null) {
                return null;
            }
            else {
                return new MyObject(in);
            }
        }
        catch (Exception e) {
            return null;
        }
    }
    public MyObject[] newArray(int size) {
        return new MyObject[size];
    }
};

private MyObject(Parcel in) {
    in.readIntArray(...);
    ... = in.readInt();
    in.readStringArray(...);
    ... = in.readString();
    ... = (OtherObject[]) in.readParcelableArray(OtherObject.class.getClassLoader());
}

Error 2:

java.lang.RuntimeException: Unable to start activity ComponentInfo
Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling:
at android.os.Parcel.readParcelable(Parcel.java:1971)
at android.os.Parcel.readValue(Parcel.java:1859)
at android.os.Parcel.readMapInternal(Parcel.java:2099)
at android.os.Bundle.unparcel(Bundle.java:223)
at android.os.Bundle.getParcelable(Bundle.java:1158)
at android.app.Activity.onCreate(Activity.java:905)
at my.app.package.PlayComputer.onCreate(SourceFile:1012)

Same files and classes.

Error 3:

java.lang.RuntimeException: Unable to start activity ComponentInfo
Caused by: java.lang.RuntimeException: Parcel android.os.Parcel@4051aff8: Unmarshalling unknown type code 7340149 at offset 1276
at android.os.Parcel.readValue(Parcel.java:1913)
at android.os.Parcel.readMapInternal(Parcel.java:2083)
at android.os.Bundle.unparcel(Bundle.java:208)
at android.os.Bundle.getParcelable(Bundle.java:1100)
at my.app.package.PlayComputer.onCreate(SourceFile:1111)

This time, the causing line (1111) is the following one:

myObject = (MyObject) savedInstanceState.getParcelable("myObject");
caw
  • 30,999
  • 61
  • 181
  • 291
  • Is the GameState(Parcel in) constructor a typo and you meant to write a MyObject(Parcel in) constructor? – fedepaol Dec 21 '12 at 21:46
  • Are you separately creating the array objects in your constructor? The `readXXXArray()` methods require a fully initialized array instance to be passed in. You can use the `createXXXArray()` methods to get a new instance of that array returned back to you. – devunwired Dec 21 '12 at 22:41
  • These arrays have been declared before, of course, but not necessarily initialized yet. From the documentation, I can't see that this is a precondition: http://developer.android.com/reference/android/os/Parcel.html – caw Dec 21 '12 at 22:51
  • Well, the documentation is very poor for `Parcel` and `Parcelable`, so I don't see the differences of `readXXXArray()` and `writeXXXArray`. – caw Dec 21 '12 at 23:14
  • So is `writeTypedArray(MyObject.CREATOR)` also the recommended method for saving `Parcelable`s? As you can see above, I have used `writeParcelableArray()`. – caw Dec 22 '12 at 13:28

3 Answers3

55

Android has two different classloaders: the framework classloader (which knows how to load Android classes) and the APK classloader (which knows how to load your code). The APK classloader has the framework classloader set as its parent, meaning it can also load Android classes.

Error #2 is likely caused by the Bundle using the framework classloader so it doesn't know of your classes. I think this can happen when Android needs to persist your Bundle and later restore it (for example when running out of memory in the background). You can fix this by setting the APK classloader on the bundle:

savedInstanceState.setClassLoader(getClass().getClassLoader());

Error #1 and #3 are more mysterious, are you perhaps writing null values in writeToParcel()? Android doesn't like that very much I'm afraid.

alexanderblom
  • 8,632
  • 6
  • 34
  • 40
  • Thanks! `savedInstanceState.setClassLoader(getClass().getClassLoader());` looks good but I'm afraid I can only call this for one class. What if I have to put objects from more than one class into that `Bundle`? What class to call that for? – caw Dec 30 '12 at 22:35
  • Furthermore, with some changes, it seems as if the problem is resolved now: http://stackoverflow.com/questions/13997550/unmarshalling-errors-in-android-app-with-custom-parcelable-classes#comment19498428_13997695 Could you imagine this way of reading the objects was what caused the problems? – caw Dec 30 '12 at 23:49
  • 1
    This will work for all your classes as getClass().getClassLoader() will retrive the APK classloader (which can load all of your classes). When reading using readXXXArray(), what arrays did you pass in? How do you know that they are of the correct length? createTypedArray() and others will not write/read type information which means that classloaders are not really involved. – alexanderblom Dec 31 '12 at 08:55
  • I didn't know that `readXXXArray()` is not the adequate function as the documentation is very poor on this topic. So basically, one should always use `createXXXArray()`, unless an array of a specific length is needed, right? And `writeTypedArray()` should be preferred to `writeParcelableArray()` as well, shouldn't it? As my class implements `Parcelable`, I can use both. – caw Dec 31 '12 at 13:39
  • By the way, do I need to set the class loader for the `Bundle` in `onSaveInstanceState()` only (when saving) or in `onCreate()` (when restoring) as well? – caw Dec 31 '12 at 13:47
  • 2
    The documentation is awful here, I looked at the source instead. Yeah, readXXXArray() is only useful if you have a fixed size array. I think writeTypedArray() is a bit cleaner yes, I'm not really sure what their intention with these different calls were though. You should only need to set it when restoring, when saving you already have the classes loaded so their classloader is known. – alexanderblom Jan 01 '13 at 15:34
  • 1
    I tried `savedInstanceState.setClassLoader(getClass().getClassLoader());` in `onCreate` of my fragment but this didn't change anything. Do you have any ideas why this doesn't work? – Julian Sievers Jul 25 '14 at 12:02
3

Before reading from bundle, set ClassLoader:

bundle.setClassLoader(getClass().getClassLoader());

This saved my time!

Pang
  • 9,564
  • 146
  • 81
  • 122
hov.terteryan
  • 758
  • 6
  • 16
2

By the look of it, the createFromParcel and newArray should be overridden like this:

public static final Parcelable.Creator<MyObject> CREATOR = new Parcelable.Creator<MyObject>() {
    @Override
    public MyObject createFromParcel(Parcel in) {
        MyObject myObj = new MyObject();
        myObj.intArray = in.readIntArray(...);
        myObj.intValue = in.readInt(...);
        // ....
        // IN THE SAME ORDER THAT IS WRITTEN OUT AS PER writeToParcel!
        //
        return myObj;
    }
    @Override
    public MyObject[] newArray(int size) {
        return new MyObject[size];
    }
};

Edit:

I forgot to mention that for the above to work, there should have been an empty constructor!

public MyObject(){}
t0mm13b
  • 34,087
  • 8
  • 78
  • 110
  • Thank you! Isn't this exactly what I am doing? I'm just using the constructor `MyObject(in)` to make things more clear. Ah okay, maybe you didn't see this because of my typo in the constructor's name, which has been corrected now. – caw Dec 21 '12 at 22:26
  • 1
    The only thing I can think of, is perhaps there should be an empty constructor, have amended the answer... – t0mm13b Dec 21 '12 at 22:33
  • 1
    please verify that you are `@Override` the two methods `describeContents()` and `writeToParcel()` :) – t0mm13b Dec 21 '12 at 22:37
  • I've checked that I'm overriding these methods - and yes, I do. Don't you see that your solution and my not-really-working solution are exactly the same? I've just put some code into a helper method called `GameState(Parcel in)` - just like in the documentation. – caw Dec 21 '12 at 22:56
  • 1
    That is how I use it in my projects and have no issues with `Parcelable` - sorry if my answer is not of much help to you. – t0mm13b Dec 21 '12 at 22:58
  • No problem ;) It's great that it works for your, but as there is no difference to my solution, it doesn't really help. Probably the cause for the problems is reading and writing arrays (of parcelable objects) and you don't have that, so your code works for you. – caw Dec 21 '12 at 23:13
  • Have used reading/writing arrays btw, its obviously something silly you're doing wrong somewhere for that to blow up in your face with the exception... try printing it/logcat it to see where is it going..from initialization, in every method to see where is it actually going wrong - that will help you nail it :) *protip* I use logcat.d(TAG, "if (....){ *** HERE ***}else{}"); in every statement flow to verify! Just use your imagination that suits yourself ;) – t0mm13b Dec 21 '12 at 23:21
  • Marco W. I'd guess your problem lays not in 'MyObject' but in 'OtherObject'. Did you try taking out marshalling/unmarshalling just that attribute? – Stefan de Bruijn Dec 30 '12 at 15:14
  • I've replaced all `readXXXArray()` with `createXXXArray()` now. Moreover, I had been reading arrays of parcelable objects like this: `obj[] = (Obj[]) in.readParcelableArray(Obj.class.getClassLoader())` Now I am reading them like this: `obj[] = in.createTypedArray(...)` With these two changes, it seems like the problem is solved. Could this have been the cause for those problems? – caw Dec 30 '12 at 23:48