web-dev-qa-db-fra.com

"BadParcelableException: ClassNotFoundException lors de la suppression du marshall <myclass>" lors de l'utilisation de la méthode Parcel.read qui a un ClassLoader comme argument

É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?

48
Flow

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.

102
Flow

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.

18
Tanner Perrien

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.

4
Wasif Kirmani

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);
}
3
David Rawson