【问题标题】:Read & writing arrays of Parcelable objects读写 Parcelable 对象数组
【发布时间】:2012-04-09 09:44:43
【问题描述】:

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

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):原因:java.lang.ClassCastException:[Landroid.os.Parcelable;

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

更新:

可以将 Object 数组转换为 ArrayList 并将其添加到 parcel 中吗?比如写的时候:

    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[] 类型的。

标签: android parcelable


【解决方案1】:

您需要使用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);
}

【讨论】:

  • 谢谢,成功了!这应该在文档中澄清,因为方法命名听起来像是 Parcelable 数组是应该写的,而这显然是更多的代码。
  • 是的,方法的名称有点混乱。当我遇到这个问题时,解决它的唯一方法是阅读 Android 资源,这就是我决定写这个答案的原因。
  • 顺便说一下if (parcelableArray != null) {...}里面的语句可以简化成resultArray = Arrays.copyOf(parcelableArray, parcelableArray.length, MyClass[].class);
  • @Michael:我认为应该是“mObjList = new MyClass[size]; in.readTypedArray(mObjList, MyClass.CREATOR);”而不是“mObjList = in.readTypedArray(new MyClass[size], MyClass.CREATOR);”,因为 in.readTypedArray 的返回类型为 void,否则您的帖子对我有很大帮助
  • @LinardArquint,实际上代码示例不是很好。您应该改用in.createTypedArray()。请检查更新的示例。
【解决方案2】:

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

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

【讨论】:

  • 不应该是MyClass.class.getClassLoader()而不是getClass().getClassLoader()吗?因为我认为类加载器被用来查找我们正在尝试读取的 Parceleable 类。
  • 正是我不想要ClassABC,我想要MyClass
【解决方案3】:

我有类似的问题,并以这种方式解决。 我在 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
  • @Giorgio Barchiesi,在 android studio 中显示警告:源是 Parcelable,目标是 MyClass,但应用程序按预期工作。怎么样?
  • @kaushik,感谢您的评论,我已经编辑了我的答案,请查看“toMyObject”函数的新版本。
【解决方案4】:

您需要使用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 并且有自己的CREATORs。
  • @isabsent 解决您的问题:我还有一个接口类型的Set(我们将其命名为MyInterface),它实现了Parcelable,但没有具体的CREATORs。我使用dest.writeTypedList(new ArrayList&lt;&gt;(mySet)) 写入并使用ArrayList&lt;MyInterface&gt; data = (ArrayList&lt;MyInterface&gt;) in.readArrayList(MyInterface.class.getClassLoader()); 从Parcel 读取。缺点:未经检查的演员表有一个丑陋的警告。
  • @heisenberg 理论上如果你的类正确实现 Parcelable 并正确处理子类型,那么每个子类型都有它的 CREATOR,Android 在这种情况下所做的就是通过反射在实际类上查找 CREATOR 字段。
  • @EpicPandaForce 是的,我的接口(实现 Parcelable)的每个子类型都有自己的CREATOR。但是我被打包的对象持有Set&lt;MyInterface&gt;(只需输入MyInterface)。 Set 可以包含MyInterfaceA、MyInterfaceB 等。只要我的集合必须输入MyInterface,我就不能使用CREATOR,还是我错了?也许你有这个场景的例子?使用带有CREATOR 实现的所有 MyInterface 的 BaseClass 会有所帮助,但是我需要一个新类仅用于启用该包裹...谢谢!
猜你喜欢
  • 1970-01-01
  • 2019-03-03
  • 2011-11-15
  • 2015-07-07
  • 2016-01-09
  • 1970-01-01
  • 1970-01-01
  • 2019-05-29
  • 2011-06-19
相关资源
最近更新 更多