Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parcelable inside bundle which is added to Parcel

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?

like image 279
Heinrisch Avatar asked Nov 16 '12 17:11

Heinrisch


Video Answer


2 Answers

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.

like image 69
Nick Palmer Avatar answered Oct 28 '22 20:10

Nick Palmer


You wrote:

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?

Actually, no. When you set the classLoader to engine.class.getClassLoader(), you are actually providing a class loader that knows how to load all classes from your APK. So you can use the same classloader to unparcel all other custom classes in your application that implement Parcelable.

The problem you are seeing is that, when you don't specify a classloader, the (default) system class loader is used to unparcel the Bundle. The system class loader only knows how to load classes that are known to the Android system and doesn't know how to load any application-specific classes.

Just to clarify, (in general) there isn't a "class loader per class", there is a system class loader and then there is a "class loader per application". I hope this answers your question.

like image 25
David Wasser Avatar answered Oct 28 '22 21:10

David Wasser