读写Parcelable对象数组


77

我有以下类,可从/向包裹读取和写入对象数组:

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

在上面的代码中,我ClassCastException在阅读时得到了readParcelableArray

错误/ AndroidRuntime(5880):由以下原因引起:java.lang.ClassCastException:[Landroid.os.Parcelable;

上面的代码有什么问题?在编写对象数组时,我应该首先将数组转换为ArrayList吗?

更新:

将Object数组转换为ArrayList并将其添加到地块是否可以?例如,编写时:

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

阅读时:

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

但是现在我得到了NullPointerException。以上方法正确吗?为什么要扔NPE?


mConversationMemberList的类型是什么?

我已经更新了问题,它是MyClass []类型的。
User7723337

Answers:


160

您需要使用Parcel.writeTypedArray()方法编写数组,然后使用方法将其读回Parcel.createTypedArray(),如下所示:

MyClass[] mObjList;

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

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

之所以不使用readParcelableArray()/writeParcelableArray()方法,是因为它readParcelableArray()确实会创建Parcelable[]一个结果。这意味着您不能将方法的结果强制转换为MyClass[]。相反,您必须创建一个MyClass与结果长度相同的数组,并将每个元素从结果数组复制到该MyClass数组。

Parcelable[] parcelableArray =
        parcel.readParcelableArray(MyClass.class.getClassLoader());
MyClass[] resultArray = null;
if (parcelableArray != null) {
    resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
}

1
谢谢,做到了!这应该在文档中加以澄清,因为方法命名使它听起来像应该写Parcelable数组,这显然是更多的代码。
汤姆(Tom)

是的,该方法的名称有点令人困惑。当我遇到此问题时,解决此问题的唯一方法是阅读Android源代码,这就是为什么我决定编写此答案的原因。
迈克尔

3
顺便说一句,if (parcelableArray != null) {...}可以简化内的语句resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
CrimsonX

1
@Michael:我认为应该是“ mObjList = new MyClass [size]; in.readTypedArray(mObjList,MyClass.CREATOR);” 而不是“ mObjList = in.readTypedArray(new MyClass [size],MyClass.CREATOR);”,因为in.readTypedArray的返回类型为void,但否则您的帖子对我有很大帮助
Linard Arquint

1
@LinardArquint,实际上代码示例不是很好。您应该in.createTypedArray()改用。请检查更新的示例。
Michael

37

错误/ AndroidRuntime(5880):由以下原因引起:java.lang.ClassCastException:[Landroid.os.Parcelable;

根据该API,readParcelableArray方法返回Parcelable数组(Parcelable []),该数组不能简单地转换为MyClass数组(MyClass [])。

但是现在我得到了空指针异常。

没有详细的异常堆栈跟踪,很难说出确切原因。


假设您已经使MyClass正确实现了Parcelable,这就是我们通常对可打包对象数组进行序列化/反序列化的方式:

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

}

希望这可以帮助。

您还可以使用以下方法:

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

  private ClassABC(Parcel in) {
      mObjList = new ArrayList<ClassABC>();
      in.readTypedList(mObjList, ClassABC.CREATOR);
  }

5
它不应该是MyClass.class.getClassLoader()不是 getClass().getClassLoader()?因为我认为使用类加载器来查找我们尝试读取的Parceleable类。
阿斯温·库玛

我确实不想要ClassABC,我想要MyClass
Srneczek 2015年

11

我有一个类似的问题,并以此方式解决了。我在MyClass中定义了一个辅助方法,用于将Parcelable数组转换为MyClass对象的数组:

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

每当我需要读取MyClass对象的可拆分数组时,例如从意图中读取:

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

编辑:这是我最近使用的同一功能的更新版本,以避免编译警告:

public static MyClass[] toMyObjects(Parcelable[] parcelables) {
    if (parcelables == null)
        return null;
    return Arrays.copyOf(parcelables, parcelables.length, MyClass[].class);
}

我想我现在将这个答案添加为书签。现在已经来过几次了,我回来复制了这个神奇的代码:D
Sufian

@Giorgio Barchiesi,在android studio中显示警告:源是Parcelable,目标是MyClass,但是应用程序按预期工作。怎么样 ?
kaushik 2015年

@kaushik,谢谢您的评论,我已经编辑了答案,请查看“ toMyObject”函数的新版本。
Giorgio Barchiesi

5

您需要使用Parcel.writeTypedArray()方法编写数组,然后使用方法将其读回Parcel.readTypedArray(),如下所示:

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

对于列表,您可以执行以下操作:

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

如果MyClass有的话,您能显示代码interface吗?我不确定如何处理MyClass.CREATOR这种情况。
缺席

@isabsent如果MyClass为接口,则其实现必须全部实现Parcelable并具有自己CREATOR的。
EpicPandaForce

@isabsent解决您的问题:我也有一个Set实现的接口类型(让其命名为MyInterfaceParcelable,但没有具体CREATOR的。我曾经dest.writeTypedList(new ArrayList<>(mySet))写过文章和ArrayList<MyInterface> data = (ArrayList<MyInterface>) in.readArrayList(MyInterface.class.getClassLoader());从中读过文章Parcel。缺点:未经检查的演员表会有丑陋的警告。
丹尼

从理论上说,@ heisenberg如果您的类正确实现了Parcelable并正确处理了子类型,则每个子类型都有其CREATOR,在这种情况下,Android会通过反射在实际类上查找CREATOR字段。
EpicPandaForce

@EpicPandaForce是的,我接口的每个子类型(实现Parcelable)都有自己的CREATOR。但是我被包裹的对象持有一个Set<MyInterface>(简单键入为MyInterface)。那Set可以包含MyInterfaceAMyInterfaceB等等。只要我的集合必须键入MyInterface,我就不能使用CREATOR,否则我错了吗?在这种情况下,也许您有我的例子?对实现的所有MyInterface使用BaseClassCREATOR会有所帮助,但是我只需要一个新类即可启用该功能……谢谢!
丹尼
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.