web-dev-qa-db-fra.com

Problème unmarshalling parcelables

J'ai quelques classes qui implémentent Parcelable et certaines de ces classes se contiennent en tant que propriétés. J'organise les classes dans un Colis pour les passer d'une activité à l'autre. Les ordonner à la parcelle fonctionne bien, mais lorsque j'essaie de les annuler, l'erreur suivante apparaît:

...
AndroidRuntime  E  Caused by: Android.os.BadParcelableException: ClassNotFoundException when unmarshalling: schemas.Arrivals.LocationType
AndroidRuntime  E   at Android.os.Parcel.readParcelable(Parcel.Java:1822)
AndroidRuntime  E   at schemas.Arrivals.LayoverType.<init>(LayoverType.Java:121)
AndroidRuntime  E   at schemas.Arrivals.LayoverType.<init>(LayoverType.Java:120)
AndroidRuntime  E   at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.Java:112)
AndroidRuntime  E   at schemas.Arrivals.LayoverType$1.createFromParcel(LayoverType.Java:1)
AndroidRuntime  E   at Android.os.Parcel.readTypedList(Parcel.Java:1509)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.Java:244)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType.<init>(BlockPositionType.Java:242)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.Java:234)
AndroidRuntime  E   at schemas.Arrivals.BlockPositionType$1.createFromParcel(BlockPositionType.Java:1)
...

La classe LayoverType (où elle échoue):

public class LayoverType implements Parcelable {    
    protected LocationType location;
    protected long start;
    protected long end;

    public LayoverType() {}

    public LocationType getLocation() {
        return location;
    }

    public void setLocation(LocationType value) {
        this.location = value;
    }

    public long getStart() {
        return start;
    }

    public void setStart(long value) {
        this.start = value;
    }

    public long getEnd() {
        return end;
    }

    public void setEnd(long value) {
        this.end = value;
    }


    // **********************************************
    //  for implementing Parcelable
    // **********************************************

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeParcelable(location, flags);
        dest.writeLong(start);
        dest.writeLong(end  );
    }

    public static final Parcelable.Creator<LayoverType> CREATOR = new Parcelable.Creator<LayoverType>() {
        public LayoverType createFromParcel(Parcel in) {
            return new LayoverType(in);
        }

        public LayoverType[] newArray(int size) {
            return new LayoverType[size];
        }
    };

    private LayoverType(Parcel dest) {
        location = (LocationType) dest.readParcelable(null);    // it's failing here
        start = dest.readLong();
        end   = dest.readLong();
    }
}

Voici la classe LocationType:

public class LocationType implements Parcelable {
    protected int locid;
    protected String desc;
    protected String dir;
    protected double lat;
    protected double lng;

    public LocationType() {}

    public int getLocid() {
        return locid;
    }

    public void setLocid(int value) {
        this.locid = value;
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String value) {
        this.desc = value;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String value) {
        this.dir = value;
    }

    public double getLat() {
        return lat;
    }

    public void setLat(double value) {
        this.lat = value;
    }

    public double getLng() {
        return lng;
    }

    public void setLng(double value) {
        this.lng = value;
    }

    // **********************************************
    //  for implementing Parcelable
    // **********************************************


    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt   (locid);
        dest.writeString(desc );
        dest.writeString(dir  );
        dest.writeDouble(lat  );
        dest.writeDouble(lng  );
    }

    public static final Parcelable.Creator<LocationType> CREATOR = new Parcelable.Creator<LocationType>() {
        public LocationType createFromParcel(Parcel in) {
            return new LocationType(in);
        }

        public LocationType[] newArray(int size) {
            return new LocationType[size];
        }
    };

    private LocationType(Parcel dest) {
        locid = dest.readInt   ();
        desc  = dest.readString();
        dir   = dest.readString();
        lat   = dest.readDouble();
        lng   = dest.readDouble();
    }
}

Mise à jour 2: Autant que je sache, le code suivant a échoué (à partir de la source de Parcel ):

Class c = loader == null ? Class.forName(name) : Class.forName(name, true, loader);

Pourquoi n'est-il pas capable de trouver la classe? Il existe et implémente Parcelable.

55
Jeremy Logan

Parce que cela n'a pas été répondu dans "réponse" mais dans le commentaire, je posterai une réponse: Comme @ Max-Gontar a indiqué que vous devriez utiliser LocationType.class.getClassLoader () pour obtenir le bon ClassLoader et vous débarrasser de l'exception ClassNotFound, c'est-à-dire:

in.readParceleable(LocationType.class.getClassLoader());

82
Ognyan

J'ai eu le même problème avec la configuration suivante: un gestionnaire crée un message et l'envoie via un messager à un service distant.

le message contient un paquet dans lequel je mets mon descendant Parcelable:

final Message msg = Message.obtain(null, 0);
msg.getData().putParcelable("DOWNLOADFILEURLITEM", downloadFileURLItem);

messenger.send(msg);

J'ai eu la même exception lorsque le service distant a essayé de se séparer. Dans mon cas, je m'étais assuré que le service à distance était bien un processus os distinct. Par conséquent, je devais configurer le chargeur de classes actuel pour qu'il soit utilisé par le processus de suppression de la pile de serveurs du côté service:

final Bundle bundle = msg.getData();
bundle.setClassLoader(getClassLoader());

DownloadFileURLItem urlItem = (DownloadFileURLItem)
bundle.getParcelable("DOWNLOADFILEURLITEM");

Bundle.setClassLoader définit le chargeur de classes utilisé pour charger les classes Parcelable appropriées. Dans un service distant, vous devez le réinitialiser sur le chargeur de classes actuel.

34
Andre Steingress

J'ai trouvé que le problème était que je ne transmettais pas mes applications ClassLoader à la fonction unmarshalling:

in.readParceleable(getContext().getClassLoader());

Plutôt que:

in.readParceleable(null); 

OR

in.readParceleable(MyClass.class.getClassLoader()); 
9
mxcl

J'ajoute simplement mes 2 centimes ici, car j'ai perdu plus d'une demi-journée à me gratter la tête à ce sujet. Vous pourriez obtenir cette erreur si vous ne mettez pas les méthodes d'écriture et ne les lit pas dans le même ordre. Par exemple, ce qui suit serait faux:

 @Override
    // Order: locid -> desc -> lat -> dir -> lng
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt   (locid);
        dest.writeString(desc);
        dest.writeDouble(lat);
        dest.writeString(dir);
        dest.writeDouble(lng);
    }
    // Order: locid -> desc -> dir -> lat -> lng
    private LocationType(Parcel dest) {
        locid = dest.readInt();
        desc  = dest.readString();
        dir   = dest.readString();
        lat   = dest.readDouble();
        lng   = dest.readDouble();
    }

En passant, l'auteur l'a fait correctement, mais cela pourrait aider quelqu'un un jour.

8
Amokrane Chentir

Je ne connais pas très bien Parcelable, mais si cela ressemble à la sérialisation, chaque appel pour écrire un objet implémentant l'interface provoquera un appel récursif à writeToParcel (). Par conséquent, si quelque chose sur la pile d'appels échoue ou écrit une valeur null, la classe qui a initié l'appel peut ne pas être construite correctement. 

Essayez: Tracez la pile d'appels writeToParcel () dans toutes les classes à partir du premier appel à writeToParcel () et vérifiez que toutes les valeurs sont envoyées correctement.

6
mkamowski

J'ai aussi eu ClassNotFoundException et la publication de ma solution car les réponses fournies ici m'ont conduit dans la bonne direction. Mon scénario est que j'ai des objets parcelables imbriqués. L'objet A contient une liste de tableaux de l'objet B. Les deux implémentent Parcelable. Ecrire la liste des objets B dans la classe A:

@Override
public void writeToParcel(Parcel dest, int flags) {
    ...
    dest.writeList(getMyArrayList());

}

Lecture de la liste en classe A:

public ObjectA(Parcel source) {
    ...
    myArrayList= new ArrayList<B>();
    source.readList(myArrayList, B.class.getClassLoader());
}

Je vous remercie! 

6
rxDroid

Au lieu d'utiliser writeParcelable et readParcelable, utilisez writeToParcel et createFromParcel directement. Donc, le meilleur code est:

@Override
public void writeToParcel(Parcel dest, int flags) {
    location.writeToParcel(dest, flags);
    dest.writeLong(start);
    dest.writeLong(end  );
}

public static final Parcelable.Creator<LayoverType> CREATOR = new Parcelable.Creator<LayoverType>() {
    public LayoverType createFromParcel(Parcel in) {
        return new LayoverType(in);
    }

    public LayoverType[] newArray(int size) {
        return new LayoverType[size];
    }
};

private LayoverType(Parcel dest) {
    location = LocationType.CREATOR.createFromParcel(dest);
    start = dest.readLong();
    end   = dest.readLong();
}
5
tannewt

Eh bien, j’ai eu le même problème et je l’ai résolu de façon très stupide, sans savoir si sa solution est appelée.

disons que vous avez cette classe que vous voulez passer à une autre activité

public class Person implements  Parcelable,Serializable{
public  String Name;
public  int Age;

@Override
public void writeToParcel(Parcel dest, int flags) {

 dest.writeString(name);
dest.writeInt(age);

}

public SeriesInfo(Parcel in) {

age= in.readInt(); //her was my problem as I have put age befor name
//while in the writeToParcel function I have defined
//dest.writeInt(age) after  in.readString();???!!!!
name= in.readString();

}
}

Ça y est quand j'ai changé le:

dest.writeString(name);
dest.writeInt(age);

à

dest.writeInt(age);
dest.writeString(name);

Le problème a été résolu???!!!!

1
Ahmad Moussa

Si vous avez un objet avec une propriété de type objet List, vous devez transmettre le chargeur de classe lorsque vous lisez la propriété, par exemple:

public class Mall implements Parcelable {

    public List<Outstanding> getOutstanding() {
        return outstanding;
    }

    public void setOutstanding(List<Outstanding> outstanding) {
        this.outstanding = outstanding;
    }        

    protected Mall(Parcel in) {
        outstanding = new ArrayList<Outstanding>();
        //this is the key, pass the class loader
        in.readList(outstanding, Outstanding.class.getClassLoader());
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeList(outstanding);
    }

    public static final Parcelable.Creator<Mall> CREATOR = new Parcelable.Creator<Mall>() {
        public Mall createFromParcel(Parcel in) {
            return new Mall(in);
        }

        public Mall[] newArray(int size) {
            return new Mall[size];
        }
    };
}

Remarque: Il est important que la classe Outstanding implémente l'interface Parceable.

0
shontauro