Paquete interior parcelable que se agrega a la parcela
En mi proyecto tengo un modelo que contiene información básica sobre el modelo. Por ejemplo, digamos que el modelo es un coche. Luego hay muchas diferentes variedades de coches y estos tienen diferentes datos asignados a ellos. Todos los modelos deben ser parcelables.
Las diferencias entre los diferentes coches es muy pequeña, podría ser sólo unos pocos campos de datos. Así que esto se resuelve mediante la creación de presentadores (sólo una clase que contiene datos) para los diferentes coches. El presentador entonces sabría qué datos adicionales debe contener. Debido a que el presentador en sí no es parcelable tendrá un paquete para todos sus datos que luego la clase de coche, a continuación, añadir a la parcelable. No quiero hacer que los presentadores sean parcelables.
- Fragmentos de Android que conservan datos
- RealmObject AND Parcelable
- Hacer una clase parcelable que contiene la lista de objetos personalizados
- Arraylist en objeto parcelable
- ¿Cómo ordenar y unmarshall un parcelable a un arsenal del byte con la ayuda del paquete?
Así que el coche toma el paquete del presentador y lo pone en su paquete:
public void writeToParcel(Parcel parcel, int flags) { parcel.writeBundle(getPresenter().getBundle()); }
Y luego lo descomprimirá con:
public Car(Parcel parcel) { getPresenter().setBundle(parcel.readBundle()); }
Esto funciona bien hasta que un objeto parcelable se agrega al paquete por el presentador. Entonces consigo este error:
11-16 15:06:37.255: E/AndroidRuntime(15193): FATAL EXCEPTION: main 11-16 15:06:37.255: E/AndroidRuntime(15193): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example/com.example.activity}: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2185) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2210) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.app.ActivityThread.access$600(ActivityThread.java:142) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1208) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Handler.dispatchMessage(Handler.java:99) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Looper.loop(Looper.java:137) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.app.ActivityThread.main(ActivityThread.java:4931) 11-16 15:06:37.255: E/AndroidRuntime(15193): at java.lang.reflect.Method.invokeNative(Native Method) 11-16 15:06:37.255: E/AndroidRuntime(15193): at java.lang.reflect.Method.invoke(Method.java:511) 11-16 15:06:37.255: E/AndroidRuntime(15193): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:791) 11-16 15:06:37.255: E/AndroidRuntime(15193): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:558) 11-16 15:06:37.255: E/AndroidRuntime(15193): at dalvik.system.NativeStart.main(Native Method) 11-16 15:06:37.255: E/AndroidRuntime(15193): Caused by: android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.example.model.engine 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Parcel.readParcelable(Parcel.java:2077) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Parcel.readValue(Parcel.java:1965) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Parcel.readMapInternal(Parcel.java:2226) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Bundle.unparcel(Bundle.java:223) 11-16 15:06:37.255: E/AndroidRuntime(15193): at android.os.Bundle.getString(Bundle.java:1055) 11-16 15:06:37.255: E/AndroidRuntime(15193): at com.example.cars.CarPresenter.getExtraString(CarPresenter.java:34) 11-16 15:06:37.255: E/AndroidRuntime(15193): ... 11 more
Así que de alguna manera no puede leer nada del Bundle.
Esto puede resolverse modificando la llamada readBundle a:
public Car(Parcel parcel) { getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader())); }
Sin embargo, ¿no significaría esto que sólo podría tener un tipo de parcelables en mi paquete? Por ejemplo, ¿qué pasa si otro presentador desea agregar otro objeto parcelable al paquete?
¿Podría alguien arrojar algo de luz sobre esto?
- Uso de parceler (@Parcel) con Realm.io (Android)
- Android, ¿Cómo usar el método readTypedList correctamente en una clase Parcelable?
- No entiendo por qué se produce esta ClassCastException
- NullPointerException intentando leer Parcel String
- Ampliación de una clase que implementa Parcelable
- Android Parcelable y objeto que pasa por referencia
- Poner un objeto en el mensaje de Handler
- El protocolo Parcelable requiere un objeto Parcelable.Creator llamado CREATOR (tengo CREATOR)
Usted escribió en un comentario:
Sin embargo, mi pregunta era más de por qué tengo que especificar un cargador de clases en este caso
Dianne Hackborn, ingeniero de framework de Android, escribe que:
Cuando el lote lee desde el paquete sólo extrae los datos. En realidad, no es un paquete de datos hasta más tarde cuando se recuperan las cosas del paquete.
En el código fuente Bundle , vemos que el método setClassLoader()
establece el campo mClassLoader
:
public void setClassLoader(ClassLoader loader) { mClassLoader = loader; }
Si no usamos setClassLoader()
o el constructor Bundle(ClassLoader loader)
, el campo mClassLoader
se establecerá en el ClassLoader
defecto:
public Bundle(int capacity) { //... mClassLoader = getClass().getClassLoader(); }
mClassLoader
se utiliza para unmarshal los datos parcelados en el método unparcel()
:
mParcelledData.readMapInternal(mMap, N, mClassLoader); mParcelledData.recycle(); mParcelledData = null;
Net, si no configura el ClassLoader
por defecto al sistema ClassLoader
al desmarcar sus datos parcelados, y obtendremos una excepción ClassNotFoundException
al unmarshalling nuestros datos personalizados de objeto Parcelable
,
ClassLoaders es una de las características menos comprendidas de Java. Proporcionan "espacios de nombres", y de hecho esos espacios de nombres pueden "anidar" en virtud del hecho de que si una clase no es encontrada por el ClassLoader actual puede comprobar un "padre" ClassLoader.
En el caso de una aplicación de Android hay dos ClassLoaders. Hay uno que sabe cómo cargar clases de su APK y uno que sabe cómo cargar clases desde el marco de Android. El primero tiene este último conjunto como el cargador de clase "Padre" por lo que en general no te das cuenta. Sin embargo, como Bundle es una clase de framework y es cargado por el cargador de clase de framework, necesitas decirle sobre el ClassLoader que se utiliza para encontrar clases en tu APK. El cargador de clases que Bundle utiliza de forma predeterminada es el Framework ClassLoader que es un espacio de nombres "inferior" que el que contiene sus clases APK.
Por lo tanto, diciendo al paquete que ClassLoader utilizar, le está diciendo que necesita comprobar el APK ClassLoader para las clases. Por defecto se comprueba el APK de la estructura, ya que es el ClassLoader el que cargó la clase Bundle y, por lo tanto, es el único "espacio de nombres" en el que se sabe encontrar clases.
Tu escribiste:
Esto puede resolverse modificando la llamada readBundle a:
getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
public Car(Parcel parcel) {
getPresenter().setBundle(parcel.readBundle(engine.class.getClassLoader()));
}
Sin embargo, ¿no significaría esto que sólo podría tener un tipo de parcelables en mi paquete? Por ejemplo, ¿qué pasa si otro presentador desea agregar otro objeto parcelable al paquete?
En realidad no. Cuando configura el classLoader en engine.class.getClassLoader()
, en realidad está proporcionando un cargador de clases que sabe cómo cargar todas las clases de su APK. Así que puede usar el mismo cargador de clases para descomprimir todas las demás clases personalizadas en su aplicación que implementan Parcelable.
El problema que está viendo es que, cuando no especifica un cargador de clases, el cargador de clases del sistema (por defecto) se usa para descompartir el Bundle
. El cargador de clases del sistema sólo sabe cómo cargar clases que son conocidas por el sistema Android y no sabe cómo cargar las clases específicas de la aplicación.
Sólo para aclarar, (en general) no hay un "cargador de clase por clase", hay un cargador de clases del sistema y luego hay un "cargador de clases por aplicación". Espero que esto responda tu pregunta.
Este es mi modelo que implementa Parcelable:
public class ProgramModel implements Parcelable { private int id = -1; private String title = ""; private String time = ""; private String day = ""; private String organization = ""; private String participants = ""; private String description = ""; private String descriptionFull = ""; private String location = ""; private String locationCaption = ""; private int locationCode = 0; private String locationMap = ""; private String imageUrl = ""; private String color = ""; private int colorId = -1; private int columnId = -1; private String startTime = ""; private String endTime = ""; private Date convertedTimeStart = null; private Date convertedTimeEnd = null; private String theme = ""; private ArrayList<TagModel> tags = new ArrayList<TagModel>(); private ArrayList<Integer> tagsId = new ArrayList<Integer>(); public ProgramModel() { } private ProgramModel(Parcel in) { id = in.readInt(); title = in.readString(); time = in.readString(); day = in.readString(); organization = in.readString(); participants = in.readString(); description = in.readString(); descriptionFull = in.readString(); location = in.readString(); locationCaption = in.readString(); locationCode = in.readInt(); locationMap = in.readString(); imageUrl = in.readString(); color = in.readString(); colorId = in.readInt(); columnId = in.readInt(); startTime = in.readString(); endTime = in.readString(); convertedTimeStart = new Date(in.readLong()); convertedTimeEnd = new Date(in.readLong()); theme = in.readString(); in.readTypedList(tags, TagModel.CREATOR); in.readList(tagsId, Integer.class.getClassLoader()); } public int getId() { return id; } public void setId(int id) { this.id = id; } // Other getters and setters @Override public int describeContents() { return 0; } // this is used to regenerate your object public static final Parcelable.Creator<ProgramModel> CREATOR = new Parcelable.Creator<ProgramModel>() { public ProgramModel createFromParcel(Parcel in) { return new ProgramModel(in); } public ProgramModel[] newArray(int size) { return new ProgramModel[size]; } }; @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(id); out.writeString(title); out.writeString(time); out.writeString(day); out.writeString(organization); out.writeString(participants); out.writeString(description); out.writeString(descriptionFull); out.writeString(location); out.writeString(locationCaption); out.writeInt(locationCode); out.writeString(locationMap); out.writeString(imageUrl); out.writeString(color); out.writeInt(colorId); out.writeInt(columnId); out.writeString(startTime); out.writeString(endTime); out.writeLong(convertedTimeStart.getTime()); out.writeLong(convertedTimeEnd.getTime()); out.writeString(theme); out.writeTypedList(tags); out.writeList(tagsId); } }
Escribir datos en intención en Actividad:
Intent intent = new Intent(FirstActivity.this, SecondActivity.class); intent.putExtra("ProgramModel", programDetailsModel); startActivity(intent);
Leer los datos del paquete en Actividad:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); savedInstanceState = getIntent().getExtras(); if (savedInstanceState != null) { fromBundleModel = savedInstanceState.getParcelable("ProgramModel"); } }