2009-11-11 3 views
8

Il semble que Parcelable ne gère pas gracieusement les références circulaires comme le fait Serializable. Dans l'exemple suivant, la sérialisation Bar fonctionne très bien, mais l'écrire à une parcelle provoque une stackoverflow:Utilisation de Parcelable avec des références circulaires

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


} 

Je suis en train de le port du code au-dessus de l'utilisation Serializable à Parcelable qui utilise des références circulaires. Existe-t-il une bonne stratégie pour gérer cela avec Parcelable?

+0

Alors, avez-vous déjà compris cela? –

+0

Malheureusement, non – emmby

Répondre

1

Peut-être la réponse réside-t-elle dans un ensemble plus intelligent de méthodes writeToParcel et createFromParcel? En haut de ma tête, vous pouvez garder une liste d'objets que vous avez déjà complètement écrits dans une parcelle donnée et les identifier uniquement par une balise (leur identité localeHashCode(), peut-être). (Notez qu'il ne s'agit pas d'une liste globale, elle est explicitement Per-Parcel, peut-être elle-même stockée via un semi-global Map<Parcel,Set<Integer> >? Vous devez vous assurer que l'ensemble a été oublié une fois le colis entièrement écrit.)

le bit correspondant de writeToParcel() ressemblerait à quelque chose comme ceci:

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

le correspondant createFromParcel() serait un peu plus complexe.

Je m'attends à ce qu'il y ait des problèmes cachés avec cette méthode, mais c'est par où je commencerai. Comme je l'ai dit ici, il dépend de identityHashCode() être garanti pour être différent pour les différents objets - il est généralement sur JVM 32 bits (étant la valeur du pointeur C++ sous-jacent). Plain hashCode() pourrait être utile (peut-être avec l'ajout d'informations de frappe?), Ou peut-être une sorte de numéro de série.

Une autre option pourrait être de sérialisation tout simplement vos objets à un byte[] et écrire que dans le Parcel, mais il me semble un peu inefficace ...

0

Utilisez sérialisation Java. Faites votre classe étendre Externalizable au lieu de Parcelable et le convertir en tableau d'octets à l'aide ObjectOutputStream. Passez ce tableau d'octets à l'autre [1][2] et de le désérialiser à l'aide de ObjectInputStream.

Android Les parcelables sont très rapides, mais cette rapidité se fait au détriment de toutes les fonctionnalités supplémentaires, traditionnellement présentes dans les frameworks de sérialisation.

La sérialisation Java a été conçue pour être puissante et flexible et inclut la prise en charge de nombreuses fonctions, notamment la gestion des références circulaires. Si vous déclarez serialVersionUID personnalisé (pour éviter son calcul réfléchissant à l'exécution) et lisez/écrivez manuellement le contenu des classes dans readExternal/writeExternal, vous obtiendrez presque les mêmes performances qu'avec Parcelable (où "presque" est utilisé pour garder une trace des références circulaires et).

Questions connexes