Android Parcelable order

1k views Asked by At

I've had an issue with some Parcelable custom classes that I am using for an android app, which I've managed to resolve in a very odd way.

I had a crash occuring when reading from a parcelable only in a few specific cases (which has led me to think that my implementation wasn't entirely wrong).

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.worldcraze.worldcraze/com.worldcraze.worldcraze.AdActivity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: Surface Book
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2416)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2476)
at android.app.ActivityThread.-wrap11(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1344)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

Caused by: android.os.BadParcelableException: ClassNotFoundException when     unmarshalling: Surface Book
at android.os.Parcel.readParcelableCreator(Parcel.java:2432)
at android.os.Parcel.readParcelable(Parcel.java:2358)
at com.worldcraze.worldcraze.API.Model.TransportOffer.<init>     (TransportOffer.java:33)
at     com.worldcraze.worldcraze.API.Model.TransportOffer$1.createFromParcel(TransportO    ffer.java:43)
    at     com.worldcraze.worldcraze.API.Model.TransportOffer$1.createFromParcel(TransportO    ffer.java:40)
at android.os.Parcel.createTypedArray(Parcel.java:2167)
at com.worldcraze.worldcraze.API.Model.Ad.<init>(Ad.java:42)
at com.worldcraze.worldcraze.API.Model.Ad$1.createFromParcel(Ad.java:52)
at com.worldcraze.worldcraze.API.Model.Ad$1.createFromParcel(Ad.java:49)
at android.os.Parcel.readParcelable(Parcel.java:2367)
at android.os.Parcel.readValue(Parcel.java:2264)
at android.os.Parcel.readArrayMapInternal(Parcel.java:2614)
at android.os.BaseBundle.unparcel(BaseBundle.java:221)
at android.os.Bundle.getParcelable(Bundle.java:786)
at android.content.Intent.getParcelableExtra(Intent.java:5377)
at com.worldcraze.worldcraze.AdActivity.onCreate(AdActivity.java:57)
at android.app.Activity.performCreate(Activity.java:6251)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1107)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2369)
... 9 more

Here is my original implementation of the Model the crash was occuring in (Ad.java)

public class Ad implements Parcelable {

public String              author;
public String              transporter;
public Image               cover_urls;
public String              details;
public String              id;
public int                 item_size;
public String              object_name;
public Money               tips;
public Money               price;
public Location            where_to_buy;
public String              resource_uri;
public Location            where_to_deliver;
public Permissions         permissions;
public TransportOffer[]    transport_offers;
public float               fees_lemonway_CB;
public String              validation_code;
public Date                creation_dtime;
public String              accepted_transport_offer_id;

protected   Ad(Parcel in) {
    author           = in.readString();
    transporter      = in.readString();
    details          = in.readString();
    id               = in.readString();
    item_size        = in.readInt();
    object_name      = in.readString();
    resource_uri     = in.readString();
    cover_urls       = in.readParcelable(Image.class.getClassLoader());
    tips             = in.readParcelable(Money.class.getClassLoader());
    price            = in.readParcelable(Money.class.getClassLoader());
    permissions      = in.readParcelable(Permissions.class.getClassLoader());
    transport_offers = in.createTypedArray(TransportOffer.CREATOR);
    where_to_buy     = in.readParcelable(Location.class.getClassLoader());
    where_to_deliver = in.readParcelable(Location.class.getClassLoader());
    fees_lemonway_CB = in.readFloat();
    validation_code  = in.readString();
    creation_dtime   = (Date) in.readSerializable();
    accepted_transport_offer_id = in.readString();
}

public static final Creator<Ad> CREATOR = new Creator<Ad>() {
    @Override
    public Ad createFromParcel(Parcel in) {
        return new Ad(in);
    }

    @Override
    public Ad[] newArray(int size) {
        return new Ad[size];
    }
};

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

@Override
public void writeToParcel(Parcel dest, int flags) {
    dest.writeString(author);
    dest.writeString(transporter);
    dest.writeString(details);
    dest.writeString(id);
    dest.writeInt(item_size);
    dest.writeString(object_name);
    dest.writeString(resource_uri);
    dest.writeParcelable(cover_urls, flags);
    dest.writeParcelable(tips, flags);
    dest.writeParcelable(price, flags);
    dest.writeParcelable(permissions, flags);
    dest.writeTypedArray(transport_offers, flags);
    dest.writeParcelable(where_to_buy, flags);
    dest.writeParcelable(where_to_deliver, flags);
    dest.writeFloat(fees_lemonway_CB);
    dest.writeString(validation_code);
    dest.writeSerializable(creation_dtime);
    dest.writeString(accepted_transport_offer_id);
}

}

I managed to fix the issue by changing the order of the reads/writes of some Parcelable attributes. I am now reading/writing where_to_buy and where_to_deliver right before cover_urls which seems to have fixed the problem.

    where_to_buy     = in.readParcelable(Location.class.getClassLoader());
    where_to_deliver = in.readParcelable(Location.class.getClassLoader());
    cover_urls       = in.readParcelable(Image.class.getClassLoader());
    tips             = in.readParcelable(Money.class.getClassLoader());
    price            = in.readParcelable(Money.class.getClassLoader());
    permissions      = in.readParcelable(Permissions.class.getClassLoader());
    transport_offers = in.createTypedArray(TransportOffer.CREATOR);

(The order is the same in writeToParcel, I am just saving a few lines here by not pasting it).

This weird fix is working like a charm and I have no idea why.

Has anyone encountered something similar or knows why the order of the packing/unpacking influences the result ?

Cheers !

1

There are 1 answers

3
azizbekian On BEST ANSWER

That's not an abnormal behavior. If you understand how it works internally you would grasp why you get an exception.

Serialization is basically writing bytes in order. So you are writing some variable length of bytes in some order (e.g. 4-16-1-2), and then you are reading bytes from Parcel.

While reading from Parcel, you try to read e.g. 16 bytes, but you have actually written 4 bytes, that's why crash happens.

The order of reading should match the order of writing, otherwise you'd end up in situation when you try to read e.g. 16 bytes, but in fact your variable is 4 bytes.