2012-09-06 15 views
26

Un comportamento che sto osservando mentre passa i dati serializzabili come intento extra è piuttosto strano, e volevo solo chiarire se c'è qualcosa che non mi mancherà .LinkedList messo in Intento extra viene ricostituito in ArrayList quando si recupera nell'attività successiva

Quindi la cosa che stavo cercando di fare è che nel ActivtyA ho messo un esempio LinkedList nella intent ho creato per iniziare l'attività successiva - ActivityB.

LinkedList<Item> items = (some operation); 
Intent intent = new Intent(this, ActivityB.class); 
intent.putExtra(AppConstants.KEY_ITEMS, items); 

Nel onCreate di ActivityB, ho cercato di recuperare il più LinkedList come segue -

LinkedList<Item> items = (LinkedList<Item>) getIntent() 
          .getSerializableExtra(AppConstants.KEY_ITEMS); 

In esecuzione di questo, ho più volte avuto un ClassCastException in ActivityB, presso la linea di cui sopra. Fondamentalmente, l'eccezione ha detto che stavo ricevendo un ArrayList. Una volta ho cambiato il codice sopra per ricevere un ArrayList invece, tutto ha funzionato bene.

Ora non riesco a capire dalla documentazione esistente se questo è il comportamento previsto su Android quando si passano implementazioni di elenchi serializzabili. O forse, c'è qualcosa di fondamentalmente sbagliato w/cosa sto facendo.

Grazie.

+0

utilizzare Parcelable. –

+0

ma c'è qualche ragione particolare per cui per 'LinkedList' questo comportamento si verifica, mentre, se dovessi aggiungere un'istanza di' ArrayList' come dati extra sull'intento 'tutto andrebbe bene. E non avrò bisogno di usare 'Parcelable'? – anirvan

+0

+1 per la domanda interessante. Ho passato un po 'di tempo a pensare a questo ed ero così incuriosito che sono andato a capirlo da solo. Ora tu ed io siamo entrambi più intelligenti (vedi la mia risposta). –

risposta

49

posso dirvi perché questo sta accadendo, ma non stanno andando come si ;-)

Prima un po 'di informazioni di base:

Extra in un Intent sono fondamentalmente un Android Bundle che è fondamentalmente un HashMap di coppie chiave/valore. Così, quando si fa qualcosa di simile

intent.putExtra(AppConstants.KEY_ITEMS, items); 

Android crea un nuovo Bundle per gli extra e aggiunge una voce di mappa per la Bundle in cui la chiave è AppConstants.KEY_ITEMS e il valore è articoli (che è l'oggetto LinkedList).

Questo è tutto a posto, e se si dovesse esaminare il pacchetto extra dopo l'esecuzione del codice, si scoprirà che contiene uno LinkedList. Ora arriva la parte interessante ...

Quando si chiama startActivity() con l'intento contenente extra, Android deve convertire gli extra da una mappa di coppie chiave/valore in un flusso di byte. Fondamentalmente è necessario serializzare il pacchetto. Deve farlo perché può avviare l'attività in un altro processo e per fare ciò ha bisogno di serializzare/deserializzare gli oggetti nel Bundle in modo che possa ricrearli nel nuovo processo. Deve anche farlo perché Android salva i contenuti dell'Intent in alcune tabelle di sistema in modo che possa rigenerare l'Intent se è necessario in seguito.

Per serializzare Bundle in un flusso di byte, passa attraverso la mappa nel gruppo e ottiene ogni coppia chiave/valore. Quindi prende ogni "valore" (che è una specie di oggetto) e cerca di determinare che tipo di oggetto è in modo che possa serializzarlo nel modo più efficiente. Per fare ciò, controlla il tipo di oggetto con un elenco di tipi di oggetto noto .L'elenco di "tipi di oggetto conosciuti" contiene cose come Integer, Long, String, Map, Bundle e purtroppo anche List. Quindi se l'oggetto è un List (di cui esistono molti tipi diversi, incluso LinkedList) lo serializza e lo contrassegna come un oggetto di tipo List.

Quando il Bundle viene deserializzato, vale a dire: quando si esegue questa operazione:

LinkedList<Item> items = (LinkedList<Item>) 
     getIntent().getSerializableExtra(AppConstants.KEY_ITEMS); 

produce un ArrayList per tutti gli oggetti nel Bundle di tipo List.

Non c'è davvero nulla che tu possa fare per modificare questo comportamento di Android. Almeno ora sai perché lo fa.

solo in modo che si sa: in realtà ho scritto un piccolo programma di test per verificare questo comportamento e ho guardato il codice sorgente per Parcel.writeValue(Object v) che è il metodo che viene chiamato da Bundle quando converte la mappa in un flusso di byte.

Nota importante: Dal List è un'interfaccia questo significa che ogni classe che implementa List che si mette in un Bundle uscirà come un ArrayList. E 'anche interessante il fatto che Map è anche nella lista dei "tipi di oggetto conosciuto", che significa che non importa che tipo di Map oggetto si inserisce in un Bundle (ad esempio TreeMap, SortedMap, o qualsiasi classe che implementa l'interfaccia Map), ne otterrai sempre uno HashMap.

+3

Mio, oh mio - potrei prima dirti che * sì, davvero non mi piace *. Dopotutto, l'intero concetto di "* tipi di oggetti conosciuti *" sembra che qualcuno abbia voluto tagliare gli angoli quando ha costruito quella parte della logica. E anche, in questo caso, dovrebbero aver fatto in modo di impedire che tutti i tipi di oggetti "* not-so-known *" implementino 'Serializable', o, non supportando' Serializable' affatto all'interno di intent' extras'. Ad ogni modo, non posso ringraziarti abbastanza, e sì, siamo molto più intelligenti per quello che hai trovato. – anirvan

+3

Grazie mille per la risposta. Mi ha risparmiato ore di debugging. – Andree

+2

Grazie David, questo mi ha salvato ... Ho avuto lo stesso problema con savedInstanceState che è stato passato al mio frammento - e non riuscivo a capire perché Android ha insistito per darmi una lista di array ... – Patrick

0

La risposta di @David Wasser è corretta in termini di diagnosi del problema. Questo post è per condividere come l'ho gestito.

Il problema con qualsiasi oggetto List che esce come un ArrayList non è orribile, perché si può sempre fare qualcosa di simile

LinkedList<String> items = new LinkedList<>(
    (List<String>) intent.getSerializableExtra(KEY)); 

a cui si aggiungeranno tutti gli elementi della lista deserializzato ad un nuovo LinkedList.

Il problema è molto peggiore quando si tratta di Map, perché si potrebbe aver provato a serializzare un LinkedHashMap e ora si è perso l'ordine degli elementi.

Fortunatamente, c'è un modo (relativamente) indolore per questo: definire la propria classe di wrapper serializzabile. Si può fare per tipi specifici o farlo genericamente:

public class <T extends Serializable> Wrapper implements Serializable { 
    private T wrapped; 

    public Wrapper(T wrapped) { 
     this.wrapped = wrapped; 
    } 

    public T get() { 
     return wrapped; 
    } 
} 

Quindi è possibile utilizzare questo per nascondere il proprio List, Map, o altro tipo di dati dal controllo dei tipi di Android:

intent.putExtra(KEY, new Wrapper<>(items)); 

e versioni successive:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get(); 
Problemi correlati