views:

957

answers:

4

I've got a few classes that implement Parcelable and some of these classes contain each other as properties. I'm marshalling the classes into a Parcel to pass them between activities. Marshalling them TO the Parcel works fine, but when I try to unmarshall them I get the following error:

...
AndroidRuntime  E  Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: schemas.Arrivals.LocationType
AndroidRuntime  E   at android.os.Parcel.readParcelable(Parcel.java:1822)
AndroidRuntime  E   at schemas.Arrivals.LayoverType.<init>(LayoverType.java:121)
AndroidRuntime  E   at schemas.Arrivals.LayoverType.<init>(LayoverType.java:120)
AndroidRuntime  E   at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.java:112)
AndroidRuntime  E   at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.java:1)
AndroidRuntime  E   at android.os.Parcel.readTypedList(Parcel.java:1509)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.java:244)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.java:242)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.java:234)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.java:1)
...

The LayoverType class (where it's failing):

public class LayoverType implements Parcelable {    
    protected LocationType location;
    protected long start;
    protected long end;

    public LayoverType() {}

    public LocationType getLocation() {
        return location;
    }

    public void setLocation(LocationType value) {
        this.location = value;
    }

    public long getStart() {
        return start;
    }

    public void setStart(long value) {
        this.start = value;
    }

    public long getEnd() {
        return end;
    }

    public void setEnd(long value) {
        this.end = value;
    }


    // **********************************************
    //  for implementing Parcelable
    // **********************************************

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(location, flags);
        dest.writeLong(start);
        dest.writeLong(end  );
    }

    public static final Parcelable.Creator<LayoverType> CREATOR = new Parcelable.Creator<LayoverType>() {
        public LayoverType createFromParcel(Parcel in) {
            return new LayoverType(in);
        }

        public LayoverType[] newArray(int size) {
            return new LayoverType[size];
        }
    };

    private LayoverType(Parcel dest) {
        location = (LocationType) dest.readParcelable(null);    // it's failing here
        start = dest.readLong();
        end   = dest.readLong();
    }
}

Here's the LocationType class:

public class LocationType implements Parcelable {
    protected int locid;
    protected String desc;
    protected String dir;
    protected double lat;
    protected double lng;

    public LocationType() {}

    public int getLocid() {
        return locid;
    }

    public void setLocid(int value) {
        this.locid = value;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String value) {
        this.desc = value;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String value) {
        this.dir = value;
    }

    public double getLat() {
        return lat;
    }

    public void setLat(double value) {
        this.lat = value;
    }

    public double getLng() {
        return lng;
    }

    public void setLng(double value) {
        this.lng = value;
    }

    // **********************************************
    //  for implementing Parcelable
    // **********************************************


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt   (locid);
        dest.writeString(desc );
        dest.writeString(dir  );
        dest.writeDouble(lat  );
        dest.writeDouble(lng  );
    }

    public static final Parcelable.Creator<LocationType> CREATOR = new Parcelable.Creator<LocationType>() {
        public LocationType createFromParcel(Parcel in) {
            return new LocationType(in);
        }

        public LocationType[] newArray(int size) {
            return new LocationType[size];
        }
    };

    private LocationType(Parcel dest) {
        locid = dest.readInt   ();
        desc  = dest.readString();
        dir   = dest.readString();
        lat   = dest.readDouble();
        lng   = dest.readDouble();
    }
}

Update 2: As far as I can tell it's failing at the following bit of code (from Parcel's source):

Class c = loader == null ? Class.forName(name) : Class.forName(name, true, loader);

Why is it not able to find the class? It both exists and implements Parcelable.

+1  A: 

I am not very familiar with Parcelable but if it's anything like Serialization each call to write an object that implements the interface will cause a recursive call to writeToParcel(). Therefore, if something along the call stack fails or writes a null value the class that initiated the call may not be constructed correctly.

Try: Trace the writeToParcel() call stack through all the classes starting at the first call to writeToParcel() and verify that all the values are getting sent correctly.

MKam
I was definately having problems with NullPointers before, but I initialized all the values and that went away. Now it seems like it just can't find the class.
fiXedd
Oh, it appears you were right all along. As best I can tell it was crapping out because something wasn't getting initialized and when it tried to read the next `Parcellable` out it was getting a null (or something).
fiXedd
can you tell us what exactly you fixed? I'm having the same problem and don't see what's wrong with my code.
Matthias
I also fixed it, here's what I learned from my own mistakes (and the atrocious documentation from Binder/Parcel/Parcelable): 1) you have to pair calls to write/read something *exactly* (i.e. you cannot use writeValue() to write a `Parcelable` and readParcelable() to read it). 2) Don't use readTypedList(), it always broke on me. Instead, createTypedArrayList() is much eaiser to use and also worked as expected. 3) on *every* call to readXYZ where it expects a `ClassLoader`, pass that class' loader, not null. That's also for readBundle() when it must unparcel values in its internal map. HTH.
Matthias
A: 

Could you post the full code for both of your classes? I'm especially interested in LocationType to see whether it's a public static inner class or not.

Romain Guy
I went ahead and included everything but the package line and the imports. Hope that helps and thanks for taking a look at this.
fiXedd
A: 

I found the problem was I was not passing my applications ClassLoader to the unmarshalling function:

in.readParceleable(getContext().getClassLoader());

Rather than:

in.readParceleable(null);
Max Howell
I suppose you mean LocationType.class.getClassLoader()
Max Gontar
That looked hopeful, but does not solve my problem. Can you please post the minimal project that illustrates the problem and its solution? I am struggling with the sample code from Google and it crashes in the receiver-activity.
mobibob
+1  A: 

I had the same problem with the following setup: some handler creates a Message and sends its over a Messenger to a remote service.

the Message contains a Bundle where I put my Parcelable descendant:

final Message msg = Message.obtain(null, 0);
msg.getData().putParcelable("DOWNLOADFILEURLITEM", downloadFileURLItem);

messenger.send(msg);

I had the same exception when the remote service tried to unparcel. In my case, I had overseen that the remote service is indeed a separate os process. Therefore, I had to set the current classloader to be used by the unparcelling process on the service side:

final Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());

DownloadFileURLItem urlItem = (DownloadFileURLItem)
bundle.getParcelable("DOWNLOADFILEURLITEM");

Bundle.setClassLoader sets the classloader which is used to load the appropriate Parcelable classes. In a remote service, you need to reset it to the current class loader.

Andre Steingress