2012-09-06 14 views
26

Un comportamiento Estoy observando wrt pasando datos serializables como intento extra es bastante extraño, y solo quería aclarar si hay algo que no me estoy perdiendo .LinkedList puesto en Intent extra obtiene refundición a ArrayList al recuperar en la actividad siguiente

Así que lo que estaba tratando de hacer es que, en ActivtyA pongo un ejemplo LinkedList en el intent creé para el inicio de la siguiente actividad - ActivityB.

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

En el onCreate de ActivityB, trataba de recuperar el LinkedList extra de la siguiente manera -

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

En la ejecución de este, llegué en repetidas ocasiones un ClassCastException en ActivityB, en la línea de arriba. Básicamente, la excepción decía que estaba recibiendo un ArrayList. Una vez que cambié el código de arriba para recibir un ArrayList, todo funcionó bien.

Ahora no puedo deducir de la documentación existente si este es el comportamiento esperado en Android al pasar implementaciones de listas serializables. O tal vez, hay algo fundamentalmente malo con lo que estoy haciendo.

Gracias.

+0

use Parcelable en su lugar. –

+0

, pero ¿hay algún motivo en particular para que 'LinkedList' ocurra este comportamiento, mientras que, si tuviera que agregar una instancia 'ArrayList' como datos adicionales en el' intento', todo estaría bien. Y, ¿no necesitaré usar 'Parcelable'? – anirvan

+0

+1 para la pregunta interesante. Pasé un tiempo pensando en esto y estaba tan intrigado que fui a resolverlo por mí mismo. Ahora tú y yo somos más inteligentes (ver mi respuesta). –

Respuesta

49

os por qué ocurre esto puedo decir, pero que no van a gustar ;-)

Primero un poco de información de fondo:

Extras en un Intent son, básicamente, un Android Bundle que es básicamente un HashMap de pares clave/valor. Así que cuando haces algo como

intent.putExtra(AppConstants.KEY_ITEMS, items); 

Android crea un nuevo Bundle por los extras y añade una entrada de mapa a la Bundle donde la clave es AppConstants.KEY_ITEMS y el valor es artículos (que es su objeto LinkedList).

Todo está bien y bien, y si tuviera que mirar el paquete de extras después de que se ejecute su código, encontrará que contiene un LinkedList. Ahora viene la parte interesante ...

Cuando llamas al startActivity() con el contenido que contiene extras, Android necesita convertir los extras de un mapa de pares clave/valor en un flujo de bytes. Básicamente se necesita serializar el paquete. Tiene que hacer eso porque puede iniciar la actividad en otro proceso y para hacerlo necesita serializar/deserializar los objetos en el paquete para que pueda recrearlos en el nuevo proceso. También necesita hacer esto porque Android guarda los contenidos del Intent en algunas tablas del sistema para que pueda regenerar el Intento si lo necesita más tarde.

Para serializar el Bundle en una secuencia de bytes, pasa por el mapa en el paquete y obtiene cada par de clave/valor. Luego toma cada "valor" (que es algún tipo de objeto) e intenta determinar qué tipo de objeto es para poder serializarlo de la manera más eficiente. Para hacer esto, comprueba el tipo de objeto con una lista de tipos de objetos conocidos.La lista de "tipos de objetos conocidos" contiene cosas como Integer, Long, String, Map, Bundle y lamentablemente también List. Por lo tanto, si el objeto es un List (del que hay muchos tipos diferentes, incluido LinkedList) lo serializa y lo marca como un objeto de tipo List.

Cuando se deserializa el Bundle, es decir: cuando se hace esto:

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

Produce un ArrayList para todos los objetos del tipo de BundleList.

No hay nada que puedas hacer para cambiar este comportamiento de Android. Al menos ahora sabes por qué hace esto.

Sólo para que sepa: De hecho, me escribió un pequeño programa de prueba para verificar este comportamiento y me han mirado el código fuente de Parcel.writeValue(Object v) que es el método que es llamada desde Bundle cuando convierte el mapa en un flujo de bytes.

Nota importante: Desde List es una interfaz esto significa que cualquier clase que implementa List que usted pone en un Bundle saldrá como un ArrayList. También es interesante que Map también está en la lista de "tipos de objetos conocidos", que significa que no importa qué tipo de Map objeto que puso en un Bundle (por ejemplo TreeMap, SortedMap, o cualquier clase que implementa la interfaz Map), siempre obtendrás un HashMap.

+3

Mi, oh mi - ¿podría decir primero que * sí, realmente no me gusta esto *. Después de todo, todo el concepto de "* tipos de objetos conocidos *" parece que alguien quiso cortar las esquinas cuando construyó esa parte de la lógica. Y también, si este fuera el caso, deberían haber evitado que todos los tipos de objetos "* no-conocidos" implementen 'Serializable', o que no admitan' Serializable' en absoluto dentro de la intención ' extras'. De todos modos, no puedo agradecerte lo suficiente, y sí, somos mucho más inteligentes por lo que has encontrado. – anirvan

+3

Muchas gracias por la respuesta. Me ahorró horas de depuración. – Andree

+2

Gracias David, esto me salvó ... Tuve el mismo problema con savedInstanceState que se pasó a mi fragmento, y no pude entender por qué Android insistió en darme una ArrayList ... – Patrick

0

La respuesta por @David Wasser es correcta en términos de diagnóstico del problema. Esta publicación es para compartir cómo lo manejé.

El problema con cualquier objeto List saliendo como un ArrayList no es horrible, porque siempre se puede hacer algo como

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

que se sumarán todos los elementos de la lista deserializado a un nuevo LinkedList.

El problema es mucho peor cuando se trata de Map, porque es posible que haya intentado serializar un LinkedHashMap y ahora haya perdido el orden de los elementos.

Afortunadamente, hay una forma (relativamente) indolora de evitar esto: defina su propia clase de contenedor serializable. Puede hacerlo para tipos específicos o hacerlo de forma genérica:

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

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

    public T get() { 
     return wrapped; 
    } 
} 

A continuación, se puede usar esto para ocultar su List, Map, u otro tipo de datos a partir de la comprobación de tipos de Android:

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

y posterior:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get(); 
Cuestiones relacionadas