web-dev-qa-db-fra.com

Comment écrire Java.util.Map dans une parcelle de manière intelligente?

J'ai un mappage générique de chaînes (clé, valeur) et ce champ fait partie d'un bean dont j'ai besoin d'être parcellaire . Je pourrais donc utiliser la méthode Parcel # writeMap. L'API Doc dit:

Veuillez utiliser writeBundle (Bundle) à la place. Aplatit une carte dans la parcelle à la position actuelle de dataPosition (), augmentez dataCapacity () si nécessaire. Le Les clés de carte doivent être des objets String. Les valeurs de la carte sont écrites en utilisant writeValue (Object) et doit suivre la spécification à cet endroit. Il est Il est fortement recommandé d'utiliser writeBundle (Bundle) à la place de ceci méthode, puisque la classe Bundle fournit une API de type sûr qui autorise éviter les erreurs de type mystérieuses au moment de la mise en forme.

Je pourrais donc parcourir chaque entrée de ma carte et la placer dans le paquet, mais je cherche toujours une façon plus intelligente de le faire. Existe-t-il une méthode dans le SDK Android qui me manque?

Au moment où je le fais comme ça:

final Bundle bundle = new Bundle();
final Iterator<Entry<String, String>> iter = links.entrySet().iterator();
while(iter.hasNext())
{
    final Entry<String, String>  entry =iter.next();
    bundle.putString(entry.getKey(), entry.getValue());
}
parcel.writeBundle(bundle);
61
Kitesurfer

J'ai fini par le faire un peu différemment. Il suit le modèle auquel vous vous attendez pour traiter Parcelables, il devrait donc vous être familier. 

public void writeToParcel(Parcel out, int flags){
  out.writeInt(map.size());
  for(Map.Entry<String,String> entry : map.entrySet()){
    out.writeString(entry.getKey());
    out.writeString(entry.getValue());
  }
}

private MyParcelable(Parcel in){
  //initialize your map before
  int size = in.readInt();
  for(int i = 0; i < size; i++){
    String key = in.readString();
    String value = in.readString();
    map.put(key,value);
  }
}

Dans ma demande, l'ordre des clés sur la carte importait. J'utilisais un LinkedHashMap pour conserver l'ordre et le faire de cette manière garantissait que les clés apparaissent dans le même ordre après avoir été extraites de la Parcel.

79
Anthony Naddeo

tu peux essayer:

bundle.putSerializable(yourSerializableMap);

si la carte choisie implémente sérialisable (comme HashMap) et que vous pouvez alors utiliser votre writeBundle facilement

25
STT LCU

Si key et value de la carte étendent Parcelable, vous pouvez avoir une solution géniale assez géniale à cela: 

Code

// For writing to a Parcel
public <K extends Parcelable,V extends Parcelable> void writeParcelableMap(
        Parcel parcel, int flags, Map<K, V > map)
{
    parcel.writeInt(map.size());
    for(Map.Entry<K, V> e : map.entrySet()){
        parcel.writeParcelable(e.getKey(), flags);
        parcel.writeParcelable(e.getValue(), flags);
    }
}

// For reading from a Parcel
public <K extends Parcelable,V extends Parcelable> Map<K,V> readParcelableMap(
        Parcel parcel, Class<K> kClass, Class<V> vClass)
{
    int size = parcel.readInt();
    Map<K, V> map = new HashMap<K, V>(size);
    for(int i = 0; i < size; i++){
        map.put(kClass.cast(parcel.readParcelable(kClass.getClassLoader())),
                vClass.cast(parcel.readParcelable(vClass.getClassLoader())));
    }
    return map;
}

Usage

// MyClass1 and MyClass2 must extend Parcelable
Map<MyClass1, MyClass2> map;

// Writing to a parcel
writeParcelableMap(parcel, flags, map);

// Reading from a parcel
map = readParcelableMap(parcel, MyClass1.class, MyClass2.class);
23
bcorso

Bonne question. Je ne connais aucune méthode de l'API autre que putSerializable et writeMap. La sérialisation est non recommandée pour des raisons de performances, et writeMap () n'est également pas recommandé pour des raisons un peu mystérieuses, comme vous l'avez déjà souligné.

J'avais besoin de coliser une HashMap aujourd'hui, alors j'ai essayé d'écrire quelques méthodes utilitaires pour la parcellisation de la carte vers et depuis un paquet de la manière recommandée:

// Usage:

// read map into a HashMap<String,Foo>
links = readMap(parcel, Foo.class);

// another way that lets you use a different Map implementation
links = new SuperDooperMap<String, Foo>;
readMap(links, parcel, Foo.class);

// write map out
writeMap(links, parcel);

////////////////////////////////////////////////////////////////////
// Parcel methods

/**
 * Reads a Map from a Parcel that was stored using a String array and a Bundle.
 *
 * @param in   the Parcel to retrieve the map from
 * @param type the class used for the value objects in the map, equivalent to V.class before type erasure
 * @return     a map containing the items retrieved from the parcel
 */
public static <V extends Parcelable> Map<String,V> readMap(Parcel in, Class<? extends V> type) {

    Map<String,V> map = new HashMap<String,V>();
    if(in != null) {
        String[] keys = in.createStringArray();
        Bundle bundle = in.readBundle(type.getClassLoader());
        for(String key : keys)
            map.put(key, type.cast(bundle.getParcelable(key)));
    }
    return map;
}


/**
 * Reads into an existing Map from a Parcel that was stored using a String array and a Bundle.
 *
 * @param map  the Map<String,V> that will receive the items from the parcel
 * @param in   the Parcel to retrieve the map from
 * @param type the class used for the value objects in the map, equivalent to V.class before type erasure
 */
public static <V extends Parcelable> void readMap(Map<String,V> map, Parcel in, Class<V> type) {

    if(map != null) {
        map.clear();
        if(in != null) {
            String[] keys = in.createStringArray();
            Bundle bundle = in.readBundle(type.getClassLoader());
            for(String key : keys)
                map.put(key, type.cast(bundle.getParcelable(key)));
        }
    }
}


/**
 * Writes a Map to a Parcel using a String array and a Bundle.
 *
 * @param map the Map<String,V> to store in the parcel
 * @param out the Parcel to store the map in
 */
public static void writeMap(Map<String,? extends Parcelable> map, Parcel out) {

    if(map != null && map.size() > 0) {
        /*
        Set<String> keySet = map.keySet();
        Bundle b = new Bundle();
        for(String key : keySet)
            b.putParcelable(key, map.get(key));
        String[] array = keySet.toArray(new String[keySet.size()]);
        out.writeStringArray(array);
        out.writeBundle(b);
        /*/
        // alternative using an entrySet, keeping output data format the same
        // (if you don't need to preserve the data format, you might prefer to just write the key-value pairs directly to the parcel)
        Bundle bundle = new Bundle();
        for(Map.Entry<String, ? extends Parcelable> entry : map.entrySet()) {
            bundle.putParcelable(entry.getKey(), entry.getValue());
        }

        final Set<String> keySet = map.keySet();
        final String[] array = keySet.toArray(new String[keySet.size()]);
        out.writeStringArray(array);
        out.writeBundle(bundle);
        /**/
    }
    else {
        //String[] array = Collections.<String>emptySet().toArray(new String[0]);
        // you can use a static instance of String[0] here instead
        out.writeStringArray(new String[0]);
        out.writeBundle(Bundle.EMPTY);
    }
}

Edit: a modifié writeMap pour utiliser un entrySet tout en conservant le même format de données que dans ma réponse d'origine (indiqué de l'autre côté du commentaire à bascule). Si vous n'avez pas besoin ou ne voulez pas préserver la compatibilité de lecture, il peut être plus simple de stocker les paires clé-valeur à chaque itération, comme dans les réponses de @bcorso et @Anthony Naddeo.

12
Lorne Laliberte

Si la clé de votre carte est String, vous pouvez simplement utiliser Bundle, comme indiqué dans les javadocs:

/**
 * Please use {@link #writeBundle} instead.  Flattens a Map into the parcel
 * at the current dataPosition(),
 * growing dataCapacity() if needed.  The Map keys must be String objects.
 * The Map values are written using {@link #writeValue} and must follow
 * the specification there.
 *
 * <p>It is strongly recommended to use {@link #writeBundle} instead of
 * this method, since the Bundle class provides a type-safe API that
 * allows you to avoid mysterious type errors at the point of marshalling.
 */
public final void writeMap(Map val) {
    writeMapInternal((Map<String, Object>) val);
}

J'ai donc écrit le code suivant:

private void writeMapAsBundle(Parcel dest, Map<String, Serializable> map) {
    Bundle bundle = new Bundle();
    for (Map.Entry<String, Serializable> entry : map.entrySet()) {
        bundle.putSerializable(entry.getKey(), entry.getValue());
    }
    dest.writeBundle(bundle);
}

private void readMapFromBundle(Parcel in, Map<String, Serializable> map, ClassLoader keyClassLoader) {
    Bundle bundle = in.readBundle(keyClassLoader);
    for (String key : bundle.keySet()) {
        map.put(key, bundle.getSerializable(key));
    }
}

En conséquence, vous pouvez utiliser Parcelable au lieu de Serializable

1
repitch

Voici le mien un peu simple mais fonctionnant jusqu'à présent pour moi la mise en œuvre à Kotlin. Il peut être modifié facilement s'il ne répond pas aux besoins

Mais n'oubliez pas que K,V doit être Parcelable si différent de l'habituel String, Int,... etc

Écrire

parcel.writeMap(map)

Lis

parcel.readMap(map)

La lecture superposée

fun<K,V> Parcel.readMap(map: MutableMap<K,V>) : MutableMap<K,V>{

    val tempMap = LinkedHashMap<Any?,Any?>()
    readMap(tempMap, map.javaClass.classLoader)

    tempMap.forEach {
        map[it.key as K] = it.value as V
    }
    /* It populates and returns the map as well
       (useful for constructor parameters inits)*/
    return map
}
0
Jocky Doe

Toutes les solutions mentionnées ici sont valables mais personne n’est assez universel. Vous avez souvent des cartes contenant des valeurs et/ou des clés Strings, Integers, Floats etc. Dans un tel cas, vous ne pouvez pas utiliser <... expand Parcelable> et je ne souhaite pas écrire de méthodes personnalisées pour d'autres combinaisons clé/valeur. Dans ce cas, vous pouvez utiliser ce code:

@FunctionalInterface
public interface ParcelWriter<T> {
    void writeToParcel(@NonNull final T value,
                       @NonNull final Parcel parcel, final int flags);
}

@FunctionalInterface
public interface ParcelReader<T> {
    T readFromParcel(@NonNull final Parcel parcel);
}

public static <K, V> void writeParcelableMap(
        @NonNull final Map<K, V> map,
        @NonNull final Parcel parcel,
        final int flags,
        @NonNull final ParcelWriter<Map.Entry<K, V>> parcelWriter) {
    parcel.writeInt(map.size());

    for (final Map.Entry<K, V> e : map.entrySet()) {
        parcelWriter.writeToParcel(e, parcel, flags);
    }
}

public static <K, V> Map<K, V> readParcelableMap(
        @NonNull final Parcel parcel,
        @NonNull final ParcelReader<Map.Entry<K, V>> parcelReader) {
    int size = parcel.readInt();
    final Map<K, V> map = new HashMap<>(size);

    for (int i = 0; i < size; i++) {
        final Map.Entry<K, V> value = parcelReader.readFromParcel(parcel);
        map.put(value.getKey(), value.getValue());
    }
    return map;
}

C'est plus verbeux mais universel. Voici l'utilisation de l'écriture:

writeParcelableMap(map, dest, flags, (mapEntry, parcel, __) -> {
        parcel.write...; //key from mapEntry
        parcel.write...; //value from mapEntry
    });

et lis:

map = readParcelableMap(in, parcel ->
    new AbstractMap.SimpleEntry<>(parcel.read... /*key*/, parcel.read... /*value*/)
);
0
bio007