Given a custom class org.example.app.MyClass implements Parcelable
, I want to write a List
to a Parcel. I did the marshalling with
Don't unmarshall a custom class (i.e. one provided by your application and not by the Android framework) with the framework class loader that is used when you give null
as the ClassLoader argument. Use the application class loader:
parcel.readList(myclassList, getClass().getClassLoader());
Whenever a Parcel.read*()
method also has a ClassLoader as an argument (e.g. Parcel.readList(List outVal, ClassLoader loader)) and you want to read an application class from a Parcel
, use the application class loader that can be retrieved with getClass().getClassLoader()
.
Background: Android comes with two class loaders: the system class loader, that is able to load all system classes but the ones provided by your application; and the application class loader, which has set the system class loader as its parent and therefore is able to load all classes. If you give null as the class loader, Parcel.readParcelableCreator() will use the framework class loader, causing a ClassNotFoundException.
Thanks to alexanderblom for providing the hint that led me on the right track.
I believe a more correct form of this would be:
List<MyClass> myclassList = new ArrayList<MyClass>();
parcel.readList(myclassList, MyClass.class.getClassLoader());
Because here you are explicitly using the class loader for the List's generic type.
You can get this error if you subclass a custom View incorrectly.
Assume you are subclassing BottomNavigationView and you want to add saved state to the superstate in onSaveInstanceState()
.
An incorrect implementation of the Parcelable boilerplate (copied from another class or a template) would look like this:
static class State extends BaseSavedState {
Bundle stateBundle;
//incorrect as super state uses ClassLoaderCreator
public static final Creator<State> CREATOR = new Creator<State>() {
public State createFromParcel(Parcel in) {
return new State(in);
}
public State[] newArray(int size) {
return new State[size];
}
};
State(Parcel source) {
super(source);
this.stateBundle = source.readBundle(getClass().getClassLoader());
}
State(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeBundle(stateBundle);
}
}
This would not work as the superstate from BottomNavigationView
requires a classloader. Instead you should carefully inspect the SavedState
class from BottomNavigationView
and use the correct ClassLoaderCreator
rather than Creator
:
static class State extends AbsSavedState {
Bundle stateBundle;
public static final Creator<State> CREATOR = new ClassLoaderCreator<State>() {
public State createFromParcel(Parcel in, ClassLoader classLoader) {
return new State(in, classLoader);
}
@Override
public State createFromParcel(Parcel source) {
return new State(source, null);
}
public State[] newArray(int size) {
return new State[size];
}
};
State(Parcel source, ClassLoader classLoader) {
super(source, classLoader);
this.stateBundle = source.readBundle(classLoader);
}
State(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeBundle(stateBundle);
}
}
Note that extending android.support.v4.view.AbsSavedState
may be a better choice than BaseSavedState
or android.view.AbsSavedState
since it will allow you to pass a class loader to the superclass:
SavedState(Parcel source, ClassLoader classLoader) {
super(source, classLoader); //available in android.support.v4.view.AbsSavedState
this.stateBundle = source.readBundle(classLoader);
}
I have faced the same problem and instead of parcleable I used bundle
intent.setExtrasClassLoader(getClassLoader());
/* Send optional extras */
Bundle bundle = new Bundle();
bundle.putParcelable("object", mObject);
bundle.putString("stringVal", stringVal);
intent.putExtra("bundle",bundle);
And in my intent class I used this to retrieve the value:
Bundle oldBundle = intent.getBundleExtra("bundle");
ResultReceiver receiver = oldBundle.getParcelable("object");
String stringVal = oldBundle.getString("stringVal");
intent.setExtrasClassLoader(getClassLoader());
Hope it will be helpful for some.