Lesen und Schreiben von Arrays von Paketobjekten

79

Ich habe folgende Klasse, die ein Array von Objekten von / in ein Paket liest und schreibt:

class ClassABC extends Parcelable {
    MyClass[] mObjList;

    private void readFromParcel(Parcel in) {
        mObjList = (MyClass[]) in.readParcelableArray(
                com.myApp.MyClass.class.getClassLoader()));
    }

    public void writeToParcel(Parcel out, int arg1) {
        out.writeParcelableArray(mObjList, 0);
    }

    private ClassABC(Parcel in) {
        readFromParcel(in);
    }

    public int describeContents() {
        return 0;
    }

    public static final Parcelable.Creator<ClassABC> CREATOR =
            new Parcelable.Creator<ClassABC>() {

        public ClassABC createFromParcel(Parcel in) {
            return new ClassABC(in);
        }

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

Im obigen Code bekomme ich ClassCastExceptionbeim Lesen eine readParcelableArray:

FEHLER / AndroidRuntime (5880): Auslöser: java.lang.ClassCastException: [Landroid.os.Parcelable;

Was ist im obigen Code falsch? Sollte ich beim Schreiben des Objektarrays zuerst das Array in ein konvertieren ArrayList?

AKTUALISIEREN:

Ist es in Ordnung, ein Objektarray in ein zu konvertieren ArrayListund es dem Paket hinzuzufügen? Zum Beispiel beim Schreiben:

    ArrayList<MyClass> tmpArrya = new ArrayList<MyClass>(mObjList.length);
    for (int loopIndex=0;loopIndex != mObjList.length;loopIndex++) {
        tmpArrya.add(mObjList[loopIndex]);
    }
    out.writeArray(tmpArrya.toArray());

Beim Lesen:

    final ArrayList<MyClass> tmpList = 
            in.readArrayList(com.myApp.MyClass.class.getClassLoader());
    mObjList= new MyClass[tmpList.size()];
    for (int loopIndex=0;loopIndex != tmpList.size();loopIndex++) {
        mObjList[loopIndex] = tmpList.get(loopIndex);
    }

Aber jetzt bekomme ich eine NullPointerException. Ist der obige Ansatz korrekt? Warum wirft es eine NPE?

User7723337
quelle
Was ist der Typ von mConversationMemberList?
Chetan
Ich habe die Frage aktualisiert, sie ist vom Typ MyClass [].
User7723337

Antworten:

162

Sie müssen das Array mit der Parcel.writeTypedArray()Methode schreiben und mit der Parcel.createTypedArray()Methode zurücklesen , wie folgt:

MyClass[] mObjList;

public void writeToParcel(Parcel out) {
    out.writeTypedArray(mObjList, 0);
}

private void readFromParcel(Parcel in) {
    mObjList = in.createTypedArray(MyClass.CREATOR);
}

Der Grund, warum Sie die readParcelableArray()/ writeParcelableArray()-Methoden nicht verwenden sollten , ist, dass als Ergebnis readParcelableArray()wirklich ein erstellt wird Parcelable[]. Dies bedeutet, dass Sie das Ergebnis der Methode nicht in umwandeln können MyClass[]. Stattdessen müssen Sie ein MyClassArray mit der gleichen Länge wie das Ergebnis erstellen und jedes Element aus dem Ergebnisarray in das MyClassArray kopieren .

Parcelable[] parcelableArray =
        parcel.readParcelableArray(MyClass.class.getClassLoader());
MyClass[] resultArray = null;
if (parcelableArray != null) {
    resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
}
Michael
quelle
1
Danke, das hat den Trick gemacht! Dies sollte in der Dokumentation klargestellt werden, da die Benennung der Methode den Eindruck erweckt, dass ein Parcelable-Array geschrieben werden sollte, wenn dies eindeutig mehr Code ist.
Tom
Ja, der Name der Methode ist etwas verwirrend. Als ich mit diesem Problem konfrontiert wurde, war die einzige Möglichkeit, es zu lösen, das Lesen von Android-Quellen. Deshalb habe ich beschlossen, diese Antwort zu schreiben.
Michael
3
Übrigens können die Aussagen innerhalb der if (parcelableArray != null) {...}vereinfacht werdenresultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
CrimsonX
1
@Michael: Ich denke, es sollte "mObjList = new MyClass [size]; in.readTypedArray (mObjList, MyClass.CREATOR)" sein. anstelle von "mObjList = in.readTypedArray (neue MyClass [Größe], ​​MyClass.CREATOR);", weil in.readTypedArray den Rückgabetyp void hat, aber ansonsten hat mir Ihr Beitrag sehr geholfen
Linard Arquint
1
@ LinardArquint, eigentlich war das Codebeispiel nicht sehr gut. Sie sollten in.createTypedArray()stattdessen verwenden. Bitte überprüfen Sie das aktualisierte Beispiel.
Michael
37

FEHLER / AndroidRuntime (5880): Auslöser: java.lang.ClassCastException: [Landroid.os.Parcelable;

Laut API gibt die readParcelableArray-Methode das Parcelable-Array (Parcelable []) zurück, das nicht einfach in das MyClass-Array (MyClass []) umgewandelt werden kann.

Aber jetzt bekomme ich Null Pointer Exception.

Ohne die detaillierte Ablaufverfolgung des Ausnahmestapels ist es schwierig, die genaue Ursache zu ermitteln.


Angenommen, Sie haben MyClass dazu gebracht, Parcelable ordnungsgemäß zu implementieren. So gehen wir normalerweise vor, um ein Array von Parcelable-Objekten zu serialisieren / deserialisieren:

public class ClassABC implements Parcelable {

  private List<MyClass> mObjList; // MyClass should implement Parcelable properly

  // ==================== Parcelable ====================
  public int describeContents() {
    return 0;
  }

  public void writeToParcel(Parcel out, int flags) {
    out.writeList(mObjList);
  }

  private ClassABC(Parcel in) {
    mObjList = new ArrayList<MyClass>();
    in.readList(mObjList, getClass().getClassLoader());
   }

  public static final Parcelable.Creator<ClassABC> CREATOR = new Parcelable.Creator<ClassABC>() {
    public ClassABC createFromParcel(Parcel in) {
      return new ClassABC(in);
    }
    public ClassABC[] newArray(int size) {
      return new ClassABC[size];
    }
  };

}

Hoffe das hilft.

Sie können auch die folgenden Methoden verwenden:

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  private ClassABC(Parcel in) {
      mObjList = new ArrayList<ClassABC>();
      in.readTypedList(mObjList, ClassABC.CREATOR);
  }
yorkw
quelle
5
Sollte es nicht MyClass.class.getClassLoader()statt sein getClass().getClassLoader()? Weil ich denke, dass der Klassenlader verwendet wird, um die Parceleable-Klasse zu finden, die wir lesen möchten.
Aswin Kumar
genau ich will nicht ClassABC, ich will MyClass
Srneczek
11

Ich hatte ein ähnliches Problem und löste es auf diese Weise. Ich habe in MyClass eine Hilfsmethode definiert, um ein Array von Parcelable in ein Array von MyClass-Objekten zu konvertieren:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    MyClass[] objects = new MyClass[parcelables.length];
    System.arraycopy(parcelables, 0, objects, 0, parcelables.length);
    return objects;
}

Wann immer ich ein paketierbares Array von MyClass-Objekten lesen muss, z. B. aus einer Absicht:

MyClass[] objects = MyClass.toMyObjects(getIntent().getParcelableArrayExtra("objects"));

BEARBEITEN : Hier ist eine aktualisierte Version derselben Funktion, die ich in jüngerer Zeit verwende, um Kompilierungswarnungen zu vermeiden:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    if (parcelables == null)
        return null;
    return Arrays.copyOf(parcelables, parcelables.length, MyClass[].class);
}
Giorgio Barchiesi
quelle
Ich denke, ich werde diese Antwort jetzt mit einem Lesezeichen versehen. Ich bin jetzt schon ein paar Mal zurückgekommen, um diesen magischen Code zu kopieren: D
Sufian
@Giorgio Barchiesi, In Android Studio wird eine Warnung angezeigt: Quelle ist Parcelable und Ziel ist MyClass, aber die Anwendung funktioniert wie erwartet. Wie ?
Kaushik
@kaushik, danke für deinen Kommentar, ich habe meine Antwort bearbeitet, bitte sieh dir die neue Version der Funktion "toMyObject" an.
Giorgio Barchiesi
5

Sie müssen das Array mit der Parcel.writeTypedArray()Methode schreiben und mit der Parcel.readTypedArray()Methode zurücklesen , wie folgt:

MyClass[] mObjArray;

public void writeToParcel(Parcel out, int flags) {
    out.writeInt(mObjArray.length);
    out.writeTypedArray(mObjArray, flags);
}

protected MyClass(Parcel in) {
    int size = in.readInt();
    mObjArray = new MyClass[size];
    in.readTypedArray(mObjArray, MyClass.CREATOR);
}

Für Listen können Sie Folgendes tun:

  ArrayList<MyClass> mObjList;

  public void writeToParcel(Parcel out, int flags) {
      out.writeTypedList(mObjList);
  }

  protected MyClass(Parcel in) {
      mObjList = new ArrayList<>(); //non-null reference is required
      in.readTypedList(mObjList, MyClass.CREATOR);
  }
EpicPandaForce
quelle
Können Sie den Code anzeigen, wenn es welche MyClassgibt interface? Ich bin mir nicht sicher, wie ich MyClass.CREATORin diesem Fall damit umgehen soll .
isabsent
@isabsent Wenn MyClasses sich um eine Schnittstelle handelt, müssen alle Implementierungen implementiert werden Parcelableund über eigene CREATORs verfügen .
EpicPandaForce
@isabsent für Ihr Problem: Ich habe auch Seteinen Schnittstellentyp (nennen wir es MyInterface), der implementiert Parcelable, aber keine konkreten CREATORs. Ich habe immer dest.writeTypedList(new ArrayList<>(mySet))geschrieben und ArrayList<MyInterface> data = (ArrayList<MyInterface>) in.readArrayList(MyInterface.class.getClassLoader());gelesen Parcel. Nachteil: Es gibt eine hässliche Warnung für eine ungeprüfte Besetzung.
Danny
@heisenberg Theoretisch, wenn Ihre Klasse Parcelable korrekt implementiert und Subtypen richtig behandelt, hat jeder Subtyp seinen CREATOR. In diesem Fall sucht Android das Feld CREATOR in der tatsächlichen Klasse über Reflektion nach.
EpicPandaForce
@EpicPandaForce Ja, jeder Untertyp meiner Schnittstelle (der Parcelable implementiert) hat seinen eigenen CREATOR. Aber mein Objekt, das paketiert wird, enthält ein Set<MyInterface>(einfach getippt MyInterface). Das Setkann enthalten MyInterfaceA, MyInterfaceBusw. Solange mein Set eingegeben werden muss MyInterface, kann ich eine nicht verwenden CREATOR, oder bin ich falsch? Vielleicht haben Sie für mich ein Beispiel für dieses Szenario? Die Verwendung einer BaseClass für alle MyInterface mit einer CREATORImplementierung würde helfen, aber daher benötige ich eine neue Klasse nur, um dieses Paket zu aktivieren ... Vielen Dank!
Danny