似乎Parcelable没有优雅地处理像Serializable那样的循环引用。在下面的示例中,Bar的序列化工作正常,但将其写入Parcel会导致堆栈溢出:
I/TestRunner( 1571): java.lang.StackOverflowError
I/TestRunner( 1571): at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571): at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571): at com.XXX.util.ParcelableTest$Bar.writeToParcel(ParcelableTest.java:209)
I/TestRunner( 1571): at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571): at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571): at com.XXX.util.ParcelableTest$Baz.writeToParcel(ParcelableTest.java:246)
I/TestRunner( 1571): at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571): at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571): at com.XXX.util.ParcelableTest$Bar.writeToParcel(ParcelableTest.java:209)
I/TestRunner( 1571): at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571): at android.os.Parcel.writeValue(Parcel.java:1029)
public void testCircular() throws Exception {
final Bar bar = new Bar();
final Baz baz = new Baz(bar);
bar.baz = baz;
// First, serialize
final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
new ObjectOutputStream(bytes).writeObject(bar);
final ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytes.toByteArray());
final Bar bar2 = (Bar) new ObjectInputStream(bytesIn).readObject();
assertNotNull(bar2);
assertNotNull(bar2.baz);
assertEquals( bar2, bar2.baz.bar );
// Now try same thing using parcelable
final Parcel p = Parcel.obtain();
p.writeValue(bar); // FAIL! StackOverflowError
p.setDataPosition(0);
final Bar bar3 = (Bar) p.readValue(Bar.class.getClassLoader());
assertNotNull(bar3);
assertNotNull(bar3.baz);
assertEquals( bar3, bar3.baz.bar );
}
protected static class Bar implements Parcelable, Serializable {
private static final long serialVersionUID = 1L;
public static final Parcelable.Creator<Bar> CREATOR = new Parcelable.Creator<Bar>() {
public Bar createFromParcel(Parcel source) {
final Bar f = new Bar();
f.baz = (Baz) source.readValue(Bar.class.getClassLoader());
return f;
}
public Bar[] newArray(int size) {
throw new UnsupportedOperationException();
}
};
public Baz baz;
public Bar() {
}
public Bar( Baz baz ) {
this.baz = baz;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int ignored) {
dest.writeValue(baz);
}
}
protected static class Baz implements Parcelable, Serializable {
private static final long serialVersionUID = 1L;
public static final Parcelable.Creator<Baz> CREATOR = new Parcelable.Creator<Baz>() {
public Baz createFromParcel(Parcel source) {
final Baz f = new Baz();
f.bar = (Bar) source.readValue(Baz.class.getClassLoader());
return f;
}
public Baz[] newArray(int size) {
throw new UnsupportedOperationException();
}
};
public Bar bar;
public Baz() {
}
public Baz( Bar bar ) {
this.bar = bar;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int ignored) {
dest.writeValue(bar);
}
}
我正在尝试将一些代码从使用Serializable转换为使用循环引用的Parcelable。使用Parcelable处理此问题是否有一个好的策略?
答案 0 :(得分:1)
也许答案在于一组更智能的writeToParcel和createFromParcel方法?
在我的头顶,您可以保留已经完全写入给定包裹的对象列表,并仅通过标签(可能是他们的本地identityHashCode())来识别它们。 (请注意,这不是一个全局列表,它是明确的per-Parcel;也许它本身是通过半全局Map<Parcel,Set<Integer> >
存储的?你需要确保一旦包裹被完全写入就忘记了这个集合。)
writeToParcel()
的相关位看起来像这样:
HashSet<Integer> set = getWrittenSetFor(dest);
final int tag = identityHashCode();
if (set.contains(tag)) {
// Already sent
dest.writeInt(tag);
} else {
set.put(tag);
dest.writeInt(tag);
dest.writeValue(this);
}
相应的createFromParcel()
会稍微复杂一些。
我希望这种方法存在潜在的问题,但这是我开始的地方。正如我在这里所说的那样,它取决于identityHashCode()
保证对于不同的对象是不同的 - 它通常在32位JVM上(作为底层C ++指针的值)。简单的hashCode()
可能是值得的(可能是添加了输入信息?),或者可能是某种序列号。
另一种选择可能是将对象简单地序列化为byte[]
并将其写入Parcel
,但这对我来说有点低效......
答案 1 :(得分:0)
使用Java序列化。使您的类扩展Externalizable
而不是Parcelable
,并使用ObjectOutputStream将其转换为字节数组。将此字节数组传递给 [1] [2] 的另一端,并使用ObjectInputStream对其进行反序列化。
Android Parcelables速度非常快,但速度是以所有额外功能为代价的,传统上存在于序列化框架中。
Java序列化旨在提供强大而灵活的功能,并支持许多内容,包括处理循环引用。如果您声明自定义serialVersionUID
(以避免它在运行时反射计算)并在readExternal / writeExternal中手动读取/写入类内容,您将获得与Parcelable几乎相同的性能(其中&#34;几乎& #34;用于跟踪循环引用等。)