2009-11-11 35 views
8

似乎Parcelable並沒有像Serializable那樣正常處理循環引用。在下面的例子中,酒吧的序列化工作得很好,但它寫入包裹導致計算器:使用帶有循環引用的Parcelable

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


} 

我在試圖港口一些代碼使用序列化到使用循環引用Parcelable。用Parcelable處理這個問題有沒有好的策略?

+0

你有沒有想過這個? – 2010-11-10 03:24:43

+0

不幸的是,沒有 – emmby 2010-11-11 21:25:02

回答

1

也許答案在於更智能的一組writeToParcel和createFromParcel方法?

在我頭頂上,你可以保留一個已經完全寫入給定Parcel的對象列表,並且只能通過標記(它們的本地identityHashCode())來標識它們。 (請注意,這不是一個全局列表,它是明確的每個包裹;它本身可能是通過一個半全球性的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,但它給我的印象有點低效...

0

使用Java序列化。讓您的課程擴展Externalizable而不是Parcelable,並使用ObjectOutputStream將其轉換爲字節數組。將此字節數組傳遞給另一端[1][2]並使用ObjectInputStream對其進行反序列化。

Android Parcelables速度非常快,但速度是以所有額外功能爲代價的,傳統上這些功能都是在序列化框架中呈現的。

Java序列化被設計爲功能強大且靈活,並且包含對許多事情的支持,包括處理循環引用。如果您聲明自定義serialVersionUID(以避免它在運行時反射計算)並手動讀取/寫入readExternal/writeExternal中的類內容,您將獲得與Parcelable幾乎相同的性能(其中「幾乎」用於跟蹤循環引用等)。