2010-04-08 36 views
19

Mi piacerebbe serializzare un oggetto Bundle, ma non riesco a trovare un modo semplice per farlo. L'uso di Parcel non sembra un'opzione, dal momento che voglio archiviare i dati serializzati su file.Come serializzare un pacchetto?

Qualche idea su come procedere?

Il motivo per cui voglio salvare e ripristinare lo stato della mia attività, anche quando viene ucciso dall'utente. Ho già creato un pacchetto con lo stato che voglio salvare in onSaveInstanceState. Ma Android mantiene questo pacchetto solo quando l'attività viene uccisa dal SISTEMA. Quando l'utente uccide l'attività, ho bisogno di memorizzarlo da solo. Quindi mi piacerebbe serializzare e archiviarlo in un file. Certo, se hai qualche altro modo di realizzare la stessa cosa, sarei grato anche per questo.

Modifica: Ho deciso di codificare il mio stato come JSONObject anziché un pacchetto. L'oggetto JSON può quindi essere messo in un pacchetto come serializzabile o archiviato in un file. Probabilmente non è il modo più efficiente, ma è semplice e sembra funzionare bene.

risposta

6

Io uso SharedPreferences per ovviare a questa limitazione, utilizza lo stesso stile putXXX() e getXXX() di memorizzazione e recupero dei dati come fa la classe Bundle ed è relativamente semplice da implementare se prima si è utilizzato un pacchetto.

Quindi, in onCreate Ho un assegno come questo

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

risparmio i miei dati di gioco per un bundle in onSaveInstanceState(), e caricare i dati da un bundle in onRestoreInstanceState()

E

ho anche salvare i dati di gioco per SharedPreferences in onPause(), e caricare i dati da SharedPreferences in 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)); 
} 

Non sarei sorpreso se alcune persone ritengono che questo sia un uso scorretto di SharedPreferences, ma ha il compito di farlo. Ho usato questo metodo in tutti i miei giochi (quasi 2 milioni di download) da oltre un anno e funziona.

+2

Certo che funziona, speravo solo di evitare di avere 2 modi di raggruppare lo stato, anche se sono molto simili. – hermo

+0

Questo è esattamente ciò che avevo in mente per il salvataggio di uno stato persistente. – Awemo

7

la memorizzazione di qualsiasi Parcelable in un file è molto semplice:

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

godere!

+9

Sì, l'ho trovato anch'io. Il problema è che non vi è alcuna garanzia che è possibile annullare l'operazione nuovamente, diciamo se il sistema operativo è aggiornato e il pacchetto è cambiato. Ma se puoi vivere con quello, allora va bene. – hermo

+12

I dati che si recuperano dal metodo mashall() non devono essere collocati in alcun tipo di memoria persistente (su disco locale, attraverso una rete, ecc.). Per questo, è necessario utilizzare la serializzazione standard o un altro tipo di meccanismo di serializzazione generale. La rappresentazione del pacchetto Parcel è altamente ottimizzata per l'IPC locale e, come tale, non tenta di mantenere la compatibilità con i dati creati in diverse versioni della piattaforma. (http://developer.android.com/reference/android/os/Parcel.html#marshall()) – Oneiros

+0

Suppongo che non si debba trasferire il file salvato ad altri dispositivi, ma probabilmente è ok se si sta usando su un singolo dispositivo (ad esempio per il salvataggio di dati temporanei). –

4

convertirlo in 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

Questo va di nuovo contro la raccomandazione di memorizzare il contenuto di un pacco in qualsiasi forma di memoria persistente (che i Javadoc mettono in guardia contro). Supponiamo che tu vada ad aggiornare il tuo sistema operativo per qualche motivo, quindi il codice sopra farà crashare la tua app nel metodo 'restoreFromPreferences()' o restituirà qualche valore sconosciuto nel pacchetto. – Yinzara

0

Nel caso in cui si desidera memorizzare in memoria persistente, non si può fare affidamento sul meccanismo Parcelable né serializzabile. Quello che dovete fare da soli e sotto è il modo come faccio di solito è:

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

Voi rappresentate i dati come array di byte che si può facilmente memorizzare in un file, trasmettere via rete o memorizzare per database SQL utilizzando ormLite come segue:

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

e SerializedItem:

public class SerializedItem { 


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

// + getters and setters 
} 

PS: il codice di cui sopra dipende libreria GSON (che è abbastanza comune, solo per farvi sapere).