2010-04-08 22 views
19

Me gustaría serializar un objeto Bundle, pero parece que no puedo encontrar una manera simple de hacerlo. Usar Parcel no parece una opción, ya que quiero almacenar los datos serializados en un archivo.¿Cómo serializar un paquete?

¿Tienes alguna idea sobre cómo hacerlo?

La razón por la que quiero esto es para guardar y restablecer el estado de mi actividad, también cuando el usuario la mate. Ya creo un paquete con el estado que quiero guardar en onSaveInstanceState. Pero Android solo conserva este Bundle cuando el SISTEMA mata la actividad. Cuando el usuario mata la actividad, necesito almacenarla yo mismo. Por lo tanto, me gustaría serializarlo y almacenarlo en un archivo. Por supuesto, si tienes otra forma de lograr lo mismo, te agradecería eso también.

Editar: Decidí codificar mi estado como JSONObject en lugar de un paquete. El objeto JSON puede colocarse en un paquete como Serializable o almacenarse en un archivo. Probablemente no sea la manera más eficiente, pero es simple, y parece funcionar bien.

Respuesta

6

Utilizo SharedPreferences para evitar esa limitación, utiliza el mismo estilo putXXX() y getXXX() de almacenar y recuperar datos que la clase Bundle y es relativamente fácil de implementar si ha utilizado un paquete antes.

Así que en onCreate que tienen un cheque como este

if(savedInstanceState != null) 
{ 
    loadGameDataFromSavedInstanceState(savedInstanceState); 
} 
else 
{ 
    loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE)); 
} 

puedo guardar mis datos del juego a un paquete de onSaveInstanceState(), y cargar datos de un paquete en onRestoreInstanceState()

Y

también puedo guardar los datos del juego a SharedPreferences en onPause(), y cargar datos desde SharedPreferences en onResume()

onPause() 
{ 
    // get a SharedPreferences editor for storing game data to 
    SharedPreferences.Editor mySharedPreferences = getPreferences(MODE_PRIVATE).edit(); 

    // call a function to actually store the game data 
    saveGameDataToSharedPreferences(mySharedPreferences); 

    // make sure you call mySharedPreferences.commit() at the end of your function 
} 

onResume() 
{ 
    loadGameDataFromSharedPreferences(getPreferences(MODE_PRIVATE)); 
} 

No me sorprendería que algunas personas sientan que este es un uso incorrecto de SharedPreferences, pero hace el trabajo. He usado este método en todos mis juegos (casi 2 millones de descargas) durante más de un año y funciona.

+2

Claro que funciona, solo esperaba evitar tener 2 maneras de agrupar el estado, incluso si son muy similares. – hermo

+0

Esto es exactamente lo que tenía en mente para guardar un estado persistente. – Awemo

7

almacenar cualquier parcelable a un archivo es muy fácil:

FileOutputStream fos = context.openFileOutput(localFilename, Context.MODE_PRIVATE); 
Parcel p = Parcel.obtain(); // i make an empty one here, but you can use yours 
fos.write(p.marshall()); 
fos.flush(); 
fos.close(); 

disfrutar!

+9

Sí, eso también lo encontré. El problema es que no hay garantía de que pueda deshacerse de nuevo, decir si el sistema operativo está actualizado y Parcel ha cambiado. Pero si puedes vivir con eso, entonces está bien. – hermo

+12

Los datos que recupera del método mashall() no deben colocarse en ningún tipo de almacenamiento persistente (en el disco local, a través de una red, etc.). Para eso, debe usar la serialización estándar u otro tipo de mecanismo de serialización general. La representación ordenada de Parcel está altamente optimizada para IPC local y, como tal, no intenta mantener la compatibilidad con los datos creados en diferentes versiones de la plataforma. (http://developer.android.com/reference/android/os/Parcel.html#marshall()) – Oneiros

+0

Supongo que no debe transferir el archivo guardado a otros devies, pero probablemente esté bien si está utilizando en un solo dispositivo (para guardar datos temporales, por ejemplo). –

4

convertirlo en SharedPreferences:

private void saveToPreferences(Bundle in) { 
    Parcel parcel = Parcel.obtain(); 
    String serialized = null; 
    try { 
     in.writeToParcel(parcel, 0); 

     ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
     IOUtils.write(parcel.marshall(), bos); 

     serialized = Base64.encodeToString(bos.toByteArray(), 0); 
    } catch (IOException e) { 
     Log.e(getClass().getSimpleName(), e.toString(), e); 
    } finally { 
     parcel.recycle(); 
    } 
    if (serialized != null) { 
     SharedPreferences settings = getSharedPreferences(PREFS, 0); 
     Editor editor = settings.edit(); 
     editor.putString("parcel", serialized); 
     editor.commit(); 
    } 
} 

private Bundle restoreFromPreferences() { 
    Bundle bundle = null; 
    SharedPreferences settings = getSharedPreferences(PREFS, 0); 
    String serialized = settings.getString("parcel", null); 

    if (serialized != null) { 
     Parcel parcel = Parcel.obtain(); 
     try { 
      byte[] data = Base64.decode(serialized, 0); 
      parcel.unmarshall(data, 0, data.length); 
      parcel.setDataPosition(0); 
      bundle = parcel.readBundle(); 
     } finally { 
      parcel.recycle(); 
     } 
    } 
    return bundle; 
} 
+4

Esto va en contra de la recomendación de almacenar el contenido de una Parcela en cualquier forma de memoria persistente (que los Javadoc advierten). Supongamos que va y actualiza su sistema operativo por alguna razón, luego el código anterior bloqueará su aplicación en el método 'restoreFromPreferences()' o devolverá un valor desconocido en el paquete. – Yinzara

0

En caso de que desee almacenar en la memoria permanente no se puede confiar en el mecanismo parcelable ni serializable. Tienes que hacerlo por ti mismo y por debajo es la manera cómo lo general lo hago:

private static final Gson sGson = new GsonBuilder().create(); 
private static final String CHARSET = "UTF-8"; 
// taken from http://www.javacamp.org/javaI/primitiveTypes.html 
private static final int BOOLEAN_LEN = 1; 
private static final int INTEGER_LEN = 4; 
private static final int DOUBLE_LEN = 8; 

public static byte[] serializeBundle(Bundle bundle) { 
    try { 
     List<SerializedItem> list = new ArrayList<>(); 
     if (bundle != null) { 
      Set<String> keys = bundle.keySet(); 
      for (String key : keys) { 
       Object value = bundle.get(key); 
       if (value == null) continue; 
       SerializedItem bis = new SerializedItem(); 
       bis.setClassName(value.getClass().getCanonicalName()); 
       bis.setKey(key); 
       if (value instanceof String) 
        bis.setValue(((String) value).getBytes(CHARSET)); 
       else if (value instanceof SpannableString) { 
        String str = Html.toHtml((Spanned) value); 
        bis.setValue(str.getBytes(CHARSET)); 
       } else if (value.getClass().isAssignableFrom(Integer.class)) { 
        ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN); 
        b.putInt((Integer) value); 
        bis.setValue(b.array()); 
       } else if (value.getClass().isAssignableFrom(Double.class)) { 
        ByteBuffer b = ByteBuffer.allocate(DOUBLE_LEN); 
        b.putDouble((Double) value); 
        bis.setValue(b.array()); 
       } else if (value.getClass().isAssignableFrom(Boolean.class)) { 
        ByteBuffer b = ByteBuffer.allocate(INTEGER_LEN); 
        boolean v = (boolean) value; 
        b.putInt(v ? 1 : 0); 
        bis.setValue(b.array()); 
       } else 
        continue; // we do nothing in this case since there is amazing amount of stuff you can put into bundle but if you want something specific you can still add it 
//      throw new IllegalStateException("Unable to serialize class + " + value.getClass().getCanonicalName()); 

       list.add(bis); 
      } 
      return sGson.toJson(list).getBytes(CHARSET); 
     } 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    throw new IllegalStateException("Unable to serialize " + bundle); 
} 

public static Bundle deserializeBundle(byte[] toDeserialize) { 
    try { 
     Bundle bundle = new Bundle(); 
     if (toDeserialize != null) { 
      SerializedItem[] bundleItems = new Gson().fromJson(new String(toDeserialize, CHARSET), SerializedItem[].class); 
      for (SerializedItem bis : bundleItems) { 
       if (String.class.getCanonicalName().equals(bis.getClassName())) 
        bundle.putString(bis.getKey(), new String(bis.getValue())); 
       else if (Integer.class.getCanonicalName().equals(bis.getClassName())) 
        bundle.putInt(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getInt()); 
       else if (Double.class.getCanonicalName().equals(bis.getClassName())) 
        bundle.putDouble(bis.getKey(), ByteBuffer.wrap(bis.getValue()).getDouble()); 
       else if (Boolean.class.getCanonicalName().equals(bis.getClassName())) { 
        int v = ByteBuffer.wrap(bis.getValue()).getInt(); 
        bundle.putBoolean(bis.getKey(), v == 1); 
       } else 
        throw new IllegalStateException("Unable to deserialize class " + bis.getClassName()); 
      } 
     } 
     return bundle; 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    throw new IllegalStateException("Unable to deserialize " + Arrays.toString(toDeserialize)); 
} 

Usted representa los datos como matriz de bytes que puede almacenar fácilmente en un archivo, enviar a través de la red o almacenar la base de datos SQL utilizando ormLite como de la siguiente manera:

@DatabaseField(dataType = DataType.BYTE_ARRAY) 
    private byte[] mRawBundle; 

y SerializedItem:

public class SerializedItem { 


private String mClassName; 
private String mKey; 
private byte[] mValue; 

// + getters and setters 
} 

PD: el código anterior depende de la biblioteca Gson (que es bastante común, sólo para que lo sepan).