读取和写入Parcelable对象数组

80

我有以下这个类,它可以从/向一个包裹读/写一个对象数组:

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

在上述代码中,当读取readParcelableArray时,我收到了一个ClassCastException错误:

  

ERROR / AndroidRuntime(5880):Caused by:java.lang.ClassCastException:[Landroid.os.Parcelable;

上述代码有什么问题?在写入对象数组时,是否应该先将数组转换为ArrayList

更新:

将对象数组转换为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 的类型是什么? - Chetan
我已经更新了问题,它是 MyClass[] 类型的。 - User7723337
4个回答

164

您需要使用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
4
顺便说一下,在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
@Michael:我尝试了一下我的建议,看起来两个版本都可以。 - Linard Arquint
显示剩余3条评论

37
根据 API,readParcelableArray 方法返回的是 Parcelable 数组 (Parcelable[]),不能简单地将其强制转换为 MyClass 数组 (MyClass[])。但现在我遇到了空指针异常。
没有详细的异常堆栈跟踪信息很难确定确切的原因。
假设你已经正确实现了 MyClass 的 Parcelable 接口,这是我们通常对可序列化/反序列化一组 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);
  }

6
应该使用 MyClass.class.getClassLoader() 而不是 getClass().getClassLoader(),因为类加载器被用来查找我们正在尝试读取的 Parceleable 类。 - Aswin Kumar
我不想要ClassABC,我想要MyClass。 - Srneczek

12

我有过类似的问题,解决方法如下。 我在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对象的可组合数组时,例如从Intent中:

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
@kaushik,感谢您的评论,我已经编辑了我的答案,请查看“toMyObject”函数的新版本。 - Giorgio Barchiesi
这个解决方法真是神奇! - CodingBruceLee

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

For lists, you can do the following:

  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是某个接口,你能展示一下代码吗?我不确定在这种情况下如何处理MyClass.CREATOR - isabsent
如果MyClass是接口,则其实现类都必须实现Parcelable接口并拥有自己的CREATOR - EpicPandaForce
对于你的问题,@isabsent:我也有一个接口类型(让我们称之为MyInterface)的Set,它实现了Parcelable,但没有具体的CREATOR。我使用dest.writeTypedList(new ArrayList<>(mySet))写入,使用ArrayList<MyInterface> data = (ArrayList<MyInterface>) in.readArrayList(MyInterface.class.getClassLoader());Parcel中读取。缺点是:存在一个丑陋的未经检查的转换警告。 - Danny
@heisenberg 理论上,如果您的类正确实现了 Parcelable 接口并正确处理了子类型,那么每个子类型都有自己的 CREATOR。在这种情况下,Android 会通过反射查找实际类上的 CREATOR 字段。 - EpicPandaForce
@EpicPandaForce 是的,我接口的每个子类型(实现了Parcelable)都有自己的“CREATOR”。但是我的被打包对象持有一个Set<MyInterface>(简单地类型化为MyInterface)。该Set可以包含MyInterfaceAMyInterfaceB等。只要我的集合必须类型化为MyInterface,我就不能使用CREATOR,或者我错了吗?也许你有一个针对这种情况的例子给我?为所有MyInterface使用一个BaseClass并实现一个CREATOR会有所帮助,但这样我需要一个新类来启用打包...谢谢! - Danny
那么,在这种情况下,您可以使用 putParcelableList,但我认为您需要进行转换。 - EpicPandaForce

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接