Parcelable inside bundle which is added to Parcel

Heinrisch picture Heinrisch · Nov 16, 2012 · Viewed 9.2k times · Source

In my project I have a model which holds basic information about model. For example lets say that the model is a Car. Then there are many different varieties of Cars and these have different data assigned to them. All models must be parcelables.

The differences between different cars is very small, it might just be a few data fields. So this is solved by creating presenters (just a class that holds data) for the different cars. The presenter would then know which extra data it should hold. Because the the presenter itself is not parcelable it will have a Bundle for all its data which then the Car class will then add to the parcelable. I don't want to make the presenters into parcelables.

So Car takes the Bundle from the presenter and puts it in its parcel:

  public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeBundle(getPresenter().getBundle());
  } 

And it will then unpack it with:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle());
  }

This works fine until a parcelable object is added to the bundle by the presenter. Then I get this error:

11-16 15:06:37.255: E/AndroidRuntime(15193): FATAL EXCEPTION: main
11-16 15:06:37.255: E/AndroidRuntime(15193): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.activity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2185)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2210)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.access$600(ActivityThread.java:142)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1208)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Handler.dispatchMessage(Handler.java:99)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Looper.loop(Looper.java:137)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.app.ActivityThread.main(ActivityThread.java:4931)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invokeNative(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at java.lang.reflect.Method.invoke(Method.java:511)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at dalvik.system.NativeStart.main(Native Method)
11-16 15:06:37.255: E/AndroidRuntime(15193): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readParcelable(Parcel.java:2077)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readValue(Parcel.java:1965)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Parcel.readMapInternal(Parcel.java:2226)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.unparcel(Bundle.java:223)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at android.os.Bundle.getString(Bundle.java:1055)
11-16 15:06:37.255: E/AndroidRuntime(15193):         at com.example.cars.CarPresenter.getExtraString(CarPresenter.java:34)
11-16 15:06:37.255: E/AndroidRuntime(15193):         ... 11 more

So it somehow fails to read anything from the Bundle.

This can be solved by modifying the readBundle call to:

  public Car(Parcel parcel) {    
    getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
  }

However, wouldn't this mean that I could only have one type of parcelables in my bundle? For example, what if another presenter wanted to add another parcelable object to the bundle?

Could anyone shed some light on this?

Answer

Nick Palmer picture Nick Palmer · Nov 27, 2012

ClassLoaders are one of the least understood features of Java. They provide "Namespaces", and in fact those namespaces can "nest" by virtue of the fact that if a class is not found by the current ClassLoader it can check a "Parent" ClassLoader.

In the case of an Android application there are two ClassLoaders. There is one which knows how to load classes from your APK and one that knows how to load classes from the Android framework. The former has the latter set as the "Parent" class loader so in general you don't notice. However, since Bundle is a framework class, and is loaded by the framework class loader you need to tell it about the ClassLoader being used to find classes in your APK. The class loader that Bundle uses by default is the framework ClassLoader which is a "lower" namespace than the one with your APK classes in it.

So by telling the bundle which ClassLoader to use you are telling it that it needs to check the APK ClassLoader for classes. It defaults to checking the framework APK since that is the ClassLoader which loaded the Bundle class, and is thus the only "Namespace" it knows how to find classes in.