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处理此问题是否有一个好的策略?
解决方法
也许答案在于一组更智能的writeToParcel和createFromParcel方法?
在我的脑海中,您可以保留已经完全写入给定Parcel的对象列表,并仅通过标记(可能是其本地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,但它让我觉得有点低效……