Étant donné une classe personnalisée org.example.app.MyClass implements Parcelable
, Je veux écrire un List<MyClass>
à une parcelle. J'ai fait le triage avec
List<MyClass> myclassList = ...
parcel.writeList(myclassList);
chaque fois que j'essaie de démêler la classe avec
List<MyClass> myclassList = new ArrayList<MyClass>();
parcel.readList(myclassList, null);
il y a un "BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass"
exception.
Qu'est-ce qui ne va pas ici? Pourquoi la classe n'est-elle pas trouvée?
Ne démasquez pas une classe personnalisée (c'est-à-dire fournie par votre application et non par le framework Android) avec le chargeur de classe de framework utilisé lorsque vous donnez null
comme ClassLoader Utilisez le chargeur de classe d'application:
parcel.readList(myclassList, getClass().getClassLoader());
Chaque fois qu'une méthode Parcel.read*()
a également un ClassLoader comme argument (par exemple Parcel.readList(List outVal, ClassLoader loader)
) et que vous souhaitez lire une classe d'application à partir d'un Parcel
, utilisez le chargeur de classe d'application qui peut être récupéré avec getClass().getClassLoader()
.
Contexte: Android est livré avec deux chargeurs de classe: le chargeur de classe système, qui est capable de charger toutes les classes système mais celles fournies par votre application et le chargeur de classe d'application, qui a défini le chargeur de classe système comme parent et est donc en mesure de charger toutes les classes. Si vous indiquez null comme chargeur de classe, Parcel.readParcelableCreator()
tilisera le chargeur de classe de framework , provoquant une ClassNotFoundException .
Merci à alexanderblom d'avoir fourni l'indice qui m'a conduit sur la bonne voie.
Je crois qu'une forme plus correcte de ceci serait:
List<MyClass> myclassList = new ArrayList<MyClass>();
parcel.readList(myclassList, MyClass.class.getClassLoader());
Parce qu'ici, vous utilisez explicitement le chargeur de classe pour le type générique de la liste.
J'ai rencontré le même problème et au lieu de parcleable j'ai utilisé le bundle
intent.setExtrasClassLoader(getClassLoader());
/* Send optional extras */
Bundle bundle = new Bundle();
bundle.putParcelable("object", mObject);
bundle.putString("stringVal", stringVal);
intent.putExtra("bundle",bundle);
Et dans ma classe d'intention, j'ai utilisé cela pour récupérer la valeur:
Bundle oldBundle = intent.getBundleExtra("bundle");
ResultReceiver receiver = oldBundle.getParcelable("object");
String stringVal = oldBundle.getString("stringVal");
intent.setExtrasClassLoader(getClassLoader());
J'espère que cela sera utile pour certains.
Vous pouvez obtenir cette erreur si vous sous-classe une vue personnalisée de manière incorrecte.
Supposons que vous sous-classiez BottomNavigationView
et que vous souhaitiez ajouter un état enregistré au super-état dans onSaveInstanceState()
.
Une implémentation incorrecte du passe-partout Parcelable (copiée à partir d'une autre classe ou d'un modèle) ressemblerait à ceci:
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);
}
}
Cela ne fonctionnerait pas car le super-état de BottomNavigationView
nécessite un chargeur de classe. Au lieu de cela, vous devriez inspecter soigneusement la classe SavedState
à partir de BottomNavigationView
et utiliser la bonne ClassLoaderCreator
plutôt que 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);
}
}
Notez que l'extension Android.support.v4.view.AbsSavedState
peut être un meilleur choix que BaseSavedState
ou Android.view.AbsSavedState
car il vous permettra de passer un chargeur de classe à la superclasse:
SavedState(Parcel source, ClassLoader classLoader) {
super(source, classLoader); //available in Android.support.v4.view.AbsSavedState
this.stateBundle = source.readBundle(classLoader);
}