2009-11-11 18 views
8

Parece que Parcelable no maneja con gracia las referencias circulares como Serializable. En el siguiente ejemplo, la serialización de bar funciona bien, pero escribirla en una Parcela causa un stackoverflow:Uso Parcelable con referencias circulares

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


} 

Estoy tratando de puerto de un código sobre el uso de Serializable a parcelable que utiliza referencias circulares. ¿Hay una buena estrategia para manejar esto con Parcelable?

+0

¿Alguna vez te diste cuenta? –

+0

Lamentablemente, no – emmby

Respuesta

1

Quizás la respuesta se encuentre en un conjunto más inteligente de métodos writeToParcel y createFromParcel?

En la parte superior de mi cabeza, podría mantener una lista de objetos que ya había escrito por completo en una Parcela determinada e identificarlos solo por una etiqueta (su identityHashCode() local, tal vez). (Tenga en cuenta que esta no es una lista global, es explícitamente por parcela, tal vez almacenada a través de un Map<Parcel,Set<Integer> > semi-global? Debería asegurarse de que el conjunto se olvidó una vez que el paquete se escribió por completo.)

el bit correspondiente de writeToParcel() sería algo como esto:

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

la correspondiente createFromParcel() sería un poco más compleja.

Espero que haya problemas al acecho con este método, pero es donde comenzaría. Como lo mencioné aquí, depende de que identityHashCode() se garantice que sea diferente para diferentes objetos; por lo general, se trata de JVM de 32 bits (que es el valor del puntero C++ subyacente). Llano hashCode() puede valer la pena (¿tal vez con la adición de información de mecanografía?), O tal vez algún tipo de número de serie.

Otra opción podría ser simplemente serializar los objetos a un byte[] y escribir que en el Parcel, pero me parece un poco ineficiente ...

0

serialización uso de Java. Haga que su clase extienda Externalizable en lugar de Parcelable y conviértala en matriz de bytes usando ObjectOutputStream. Pase esta matriz de bytes al otro lado [1][2] y deserialícela usando ObjectInputStream.

Android Parcelables es muy rápido, pero esa velocidad tiene el costo de toda la funcionalidad adicional, tradicionalmente presente en los marcos de serialización.

La serialización de Java fue diseñada para ser potente y flexible e incluye soporte para muchas cosas, incluyendo el manejo de referencias circulares. Si declara serialVersionUID personalizado (para evitar su cálculo reflexivo en tiempo de ejecución) y lee/escribe manualmente los contenidos de la clase en readExternal/writeExternal, obtendrá casi el mismo rendimiento que con Parcelable (donde "casi" se usa para hacer un seguimiento de las referencias circulares y)

Cuestiones relacionadas