Использование Parcelable с циклическими ссылками - PullRequest
8 голосов
/ 11 ноября 2009

Похоже, что Parcelable не изящно обрабатывает циклические ссылки, как это делает Serializable. В следующем примере Serialization of 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?

Ответы [ 2 ]

1 голос
/ 12 января 2011

Возможно, ответ лежит в более интеллектуальном наборе методов writeToParcel и createFromParcel?

Вдобавок ко всему, вы можете хранить список объектов, которые вы уже полностью написали для данной посылки, и идентифицировать их только по тегу (возможно, их локальная 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 голосов
/ 16 декабря 2016

Использовать сериализацию Java. Сделайте ваш класс расширенным Externalizable вместо Parcelable и преобразуйте его в байтовый массив, используя ObjectOutputStream. Передайте этот байтовый массив на другую сторону [1] [2] и десериализуйте его, используя ObjectInputStream.

Android Parcelables очень быстрые, но эта скорость достигается за счет всей дополнительной функциональности, традиционно присутствующей в средах сериализации.

Сериализация Java была разработана, чтобы быть мощной и гибкой и включать поддержку многих вещей, включая обработку циклических ссылок. Если вы объявите пользовательский serialVersionUID (чтобы избежать его отражающих вычислений во время выполнения) и вручную прочитаете / запишите содержимое класса в readExternal / writeExternal, вы получите почти такую ​​же производительность, как и в Parcelable (где «почти» расходуется на отслеживание циклических ссылок и такие).

...