使用Parcelable与循环引用Parcelable

2023-09-04 13:40:15 作者:怀念童年的紙灰机〃

看来Parcelable不正常处理循环引用像Serializable接口一样。在下面的例子中,律师的序列化工作得很好,但它写入包裹导致计算器:

It appears that Parcelable doesn't gracefully handle circular references like Serializable does. In the following example, the Serialization of Bar works just fine, but writing it to a Parcel causes a stackoverflow:

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使用循环引用试图港口一些code以上。是否有处理这与Parcelable一个好的策略?

I'm trying to port some code over from using Serializable to Parcelable that uses circular references. Is there a good strategy for handling this with Parcelable?

推荐答案

也许答案就在一个更智能的一套writeToParcel和createFromParcel方法?

Perhaps the answer lies in a more intelligent set of writeToParcel and createFromParcel methods?

关闭我的头顶,你可以让你已经完全写入到一个给定的包对象的列表,并确定他们只能通过标签()当地identityHash code(也许)。 (请注意,这不是一个全局列表,它是明确每个包裹,包裹,集&LT;整数GT;&GT; 您也许本身通过一个半全局地图&LT存储倒是需要确保该组被遗忘,一旦包裹是完全写的。)

Off the top of my head, you could keep a list of objects you had already fully written to a given Parcel and identify them only by a tag (their local identityHashCode(), perhaps). (Note that this is not a global list, it is explicitly per-Parcel; perhaps itself stored via a semi-global Map<Parcel,Set<Integer> > ? You'd need to be sure the set was forgotten once the parcel was fully written.)

的相关位 writeToParcel()会是这个样子:

The relevant bit of writeToParcel() would look something like this:

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()会稍微复杂一些。

我希望有隐藏的问题用这种方法,但它是在那里我开始。当我把它放在这儿,这取决于 identityHash code()被保证是针对不同的对象不同 - 它通常是在32位JVM(即底层的C ++指针的值)。平原散code()可能是值得的(可能是通过增加输入信息的?),或者某种形式的序列号。

I expect there are lurking problems with this method, but it's where I'd start. As I've put it here, it depends on identityHashCode() being guaranteed to be different for different objects - it usually is on 32-bit JVMs (being the value of the underlying C++ pointer). Plain hashCode() might be worthwhile (perhaps with the addition of typing information?), or perhaps some sort of serial number.

另一种选择可能是只是普通的序列化对象与字节[] 并编写成包裹 ,但它令我有点低效......

Another option might be to just plain serialize your objects to a byte[] and write that into the Parcel, but it strikes me as a bit inefficient...