【问题标题】:Using Parcelable with circular references将 Parcelable 与循环引用一起使用
【发布时间】:2009-11-11 02:10:32
【问题描述】:

看起来 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 处理此问题的好策略?

【问题讨论】:

  • 那么你有没有想过这个问题?

标签: android parcelable


【解决方案1】:

也许答案在于一组更智能的 writeToParcel 和 createFromParcel 方法?

在我的脑海中,您可以保留一个已完全写入给定 Parcel 的对象列表,并仅通过标签(可能是它们的本地 identityHashCode())来识别它们。 (请注意,这不是一个全局列表,它是每个包裹的明确列表;也许它本身是通过半全局 Map&lt;Parcel,Set&lt;Integer&gt; &gt; 存储的?您需要确保在包裹完全写入后该集合被遗忘。)

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,但这让我觉得效率有点低......

【讨论】:

    【解决方案2】:

    使用 Java 序列化。使您的类扩展 Externalizable 而不是 Parcelable 并使用 ObjectOutputStream 将其转换为字节数组。将此字节数组传递给另一端 [1] [2] 并使用 ObjectInputStream 对其进行反序列化。

    Android Parcelables 非常快,但这种速度是以牺牲所有额外功能为代价的,而这些功能通常存在于序列化框架中。

    Java 序列化被设计为强大而灵活,包括对许多事情的支持,包括处理循环引用。如果您声明自定义 serialVersionUID(以避免它在运行时进行反射计算)并在 readExternal/writeExternal 中手动读取/写入类内容,您将获得与 Parcelable 几乎相同的性能(其中“几乎”用于跟踪循环引用等等)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-09-15
      • 2011-09-11
      • 2017-04-20
      • 2014-04-11
      • 2012-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多