2013-08-08 14 views
39

eine benutzerdefinierte Klasse Gegeben org.example.app.MyClass implements Parcelable, ich mag ein List<MyClass> zu einem Paket schreiben. Ich habe die Rangierung mit„BadParcelableException: ClassNotFoundException wenn unmarshalling <myclass>“ während Parcel.read Methode verwenden, die einen Classloader als Argument hat

List<MyClass> myclassList = ... 
parcel.writeList(myclassList); 

wenn ich versuche, um die Klasse zu entordnen mit

List<MyClass> myclassList = new ArrayList<MyClass>(); 
parcel.readList(myclassList, null); 

gibt es eine "BadParcelableException: ClassNotFoundException when unmarshalling org.example.app.MyClass" Ausnahme.

Was ist hier falsch? Warum wird die Klasse nicht gefunden?

+1

Ich habe diesen Fehler in einem anderen Kontext - aufrufen 'bundle.keySet()' auf einem Bündel, das ein Parcelable enthielt. Wenn eine einzelne Testklasse mit dem fraglichen Code ausgeführt wurde, wurde sie übergeben, aber das Ausführen der gesamten Testsuite führte zur "BadParcelableException". Das "Fix" war 'bundle.setClassloader (MyClass.class.getClassLoader())' vor 'bundle.keySet()'. –

Antwort

84

Eine benutzerdefinierte Klasse, d. H. Eine, die von Ihrer Anwendung und nicht vom Android-Framework bereitgestellt wird, wird nicht mit dem Framework-Klassenlader entfernt, der verwendet wird, wenn Sie null als Classloader-Argument angeben. Verwenden Sie die Anwendung Klassenlader:

parcel.readList(myclassList, getClass().getClassLoader()); 

Immer wenn ein Parcel.read*() Methode auch ein Classloader als Argument hat (zB Parcel.readList(List outVal, ClassLoader loader)) und Sie eine Anwendungsklasse von einem Parcel zu lesen, verwenden Sie die Anwendung Klassenlader, der mit getClass().getClassLoader() abgerufen werden können .

Hintergrund: Android verfügt über zwei Klassenladeprogramme: Das Systemklassenladeprogramm, mit dem alle Systemklassen geladen werden können, die jedoch von der Anwendung bereitgestellt werden. Und der Application-Class-Loader, der den Systemklassenlader als übergeordnet festgelegt hat und daher alle Klassen laden kann. Wenn Sie null einen Klassenlader geben, Parcel.readParcelableCreator(), will use the framework class loader, verursacht in der ClassNotFoundException.

Dank alexanderblom für the hint Bereitstellung, die mich auf dem richtigen Weg führen

16

Ich glaube, eine richtige Form dafür wäre:

List<MyClass> myclassList = new ArrayList<MyClass>(); 
parcel.readList(myclassList, MyClass.class.getClassLoader()); 

Denn hier werden Sie explizit die Klasse Loader für den generischen Typ der Liste.

+6

Das ist im Grunde das Gleiche wie meine Antwort. Es gibt keinen Unterschied zwischen 'MyClass.class' und' getClass() ', beide geben dasselbe Klassenobjekt zurück, da' getClass() 'innerhalb von MyClass aufgerufen wird. Sie können hier auch jede Klasse verwenden, sofern es sich um eine benutzerdefinierte Klasse und nicht um eine vom Framework bereitgestellte Klasse handelt. – Flow

+6

Ich habe nicht gesagt, dass Sie falsch waren, nur dass diese Form geeigneter wäre. Bedenken Sie auch, dass Sie einen zusätzlichen Methodenaufruf an 'getClass()' machen, der nicht notwendig ist, wenn Sie die Klasse statisch referenzieren. –

3

Ich habe das gleiche Problem und statt parcleable konfrontiert habe ich Bündel

intent.setExtrasClassLoader(getClassLoader()); 
/* Send optional extras */ 
Bundle bundle = new Bundle(); 
bundle.putParcelable("object", mObject); 
bundle.putString("stringVal", stringVal); 

intent.putExtra("bundle",bundle); 

Und in meiner Absicht Klasse habe ich diese den Wert abzurufen:

Bundle oldBundle = intent.getBundleExtra("bundle"); 
ResultReceiver receiver = oldBundle.getParcelable("object"); 
String stringVal = oldBundle.getString("stringVal"); 
intent.setExtrasClassLoader(getClassLoader()); 

Hoffe, es wird hilfreich sein für etwas.

0

Sie können diesen Fehler erhalten, wenn Sie eine benutzerdefinierte Ansicht falsch ableiten.

Angenommen, Sie bilden die Unterklasse BottomNavigationView, und Sie möchten den gespeicherten Status zum Superzustand in onSaveInstanceState() hinzufügen.

Eine falsche Implementierung des Parcel vorformulierten (von einer anderen Klasse oder einer Vorlage kopiert) würde wie folgt aussehen:

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); 
    } 
} 

Dies würde erfordert nicht funktionieren, da die Super von BottomNavigationView einen Klassenlader. Stattdessen sollten Sie sorgfältig die SavedState Klasse von BottomNavigationView inspizieren und verwenden Sie den richtigen ClassLoaderCreator statt 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); 
    } 
} 

Beachten Sie, dass android.support.v4.view.AbsSavedState erstreckt, kann eine bessere Wahl sein als BaseSavedState oder android.view.AbsSavedState, da es Ihnen erlaubt, einen Klassenlader zu passieren die Oberklasse:

SavedState(Parcel source, ClassLoader classLoader) { 
    super(source, classLoader); //available in android.support.v4.view.AbsSavedState 
    this.stateBundle = source.readBundle(classLoader); 
}