2009-10-26 11 views
50

Estoy tratando de usar Parcel para escribir y luego volver a leer un Parcelable. Por alguna razón, cuando leo el objeto desde el archivo, vuelve como null.¿Cómo usar Parcel en Android?

public void testFoo() { 
    final Foo orig = new Foo("blah blah"); 

    // Wrote orig to a parcel and then byte array 
    final Parcel p1 = Parcel.obtain(); 
    p1.writeValue(orig); 
    final byte[] bytes = p1.marshall(); 


    // Check to make sure that the byte array seems to contain a Parcelable 
    assertEquals(4, bytes[0]); // Parcel.VAL_PARCELABLE 


    // Unmarshall a Foo from that byte array 
    final Parcel p2 = Parcel.obtain(); 
    p2.unmarshall(bytes, 0, bytes.length); 
    final Foo result = (Foo) p2.readValue(Foo.class.getClassLoader()); 


    assertNotNull(result); // FAIL 
    assertEquals(orig.str, result.str); 
} 


protected static class Foo implements Parcelable { 
    protected static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() { 
     public Foo createFromParcel(Parcel source) { 
      final Foo f = new Foo(); 
      f.str = (String) source.readValue(Foo.class.getClassLoader()); 
      return f; 
     } 

     public Foo[] newArray(int size) { 
      throw new UnsupportedOperationException(); 
     } 

    }; 


    public String str; 

    public Foo() { 
    } 

    public Foo(String s) { 
     str = s; 
    } 

    public int describeContents() { 
     return 0; 
    } 

    public void writeToParcel(Parcel dest, int ignored) { 
     dest.writeValue(str); 
    } 


} 

¿Qué me falta?

ACTUALIZACIÓN: Para simplificar la prueba, he eliminado la lectura y escritura de archivos en mi ejemplo original.

Respuesta

62

Ah, finalmente encontré el problema. Hubo dos de hecho.

  1. CREATOR debe ser público, no estar protegido. Pero más importante aún,
  2. Debe llamar al setDataPosition(0) después de desasignar sus datos.

Aquí está la versión revisada, el código de trabajo:

public void testFoo() { 
    final Foo orig = new Foo("blah blah"); 
    final Parcel p1 = Parcel.obtain(); 
    final Parcel p2 = Parcel.obtain(); 
    final byte[] bytes; 
    final Foo result; 

    try { 
     p1.writeValue(orig); 
     bytes = p1.marshall(); 

     // Check to make sure that the byte stream seems to contain a Parcelable 
     assertEquals(4, bytes[0]); // Parcel.VAL_PARCELABLE 

     p2.unmarshall(bytes, 0, bytes.length); 
     p2.setDataPosition(0); 
     result = (Foo) p2.readValue(Foo.class.getClassLoader()); 

    } finally { 
     p1.recycle(); 
     p2.recycle(); 
    } 


    assertNotNull(result); 
    assertEquals(orig.str, result.str); 

} 

protected static class Foo implements Parcelable { 
    public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() { 
     public Foo createFromParcel(Parcel source) { 
      final Foo f = new Foo(); 
      f.str = (String) source.readValue(Foo.class.getClassLoader()); 
      return f; 
     } 

     public Foo[] newArray(int size) { 
      throw new UnsupportedOperationException(); 
     } 

    }; 


    public String str; 

    public Foo() { 
    } 

    public Foo(String s) { 
     str = s; 
    } 

    public int describeContents() { 
     return 0; 
    } 

    public void writeToParcel(Parcel dest, int ignored) { 
     dest.writeValue(str); 
    } 


} 
+3

que acaba de guardar mi día. GRACIAS. – Matthias

+0

Simplemente no intente esto con Bitmaps dentro de su paquete (capaz) :( –

+0

setDataPosition (0) me salvó! Gracias;) –

20

¡cuidado! No use Parcel para la serialización en un archivo

El parcelamiento no es un mecanismo de serialización de propósito general. Esta clase (y la correspondiente API Parcelable para colocar objetos arbitrarios en una Parcela) está diseñada como un transporte de IPC de alto rendimiento. Como tal, no es apropiado colocar ningún paquete de datos en el almacenamiento persistente: los cambios en la implementación subyacente de cualquiera de los datos en el paquete pueden hacer que los datos anteriores sean ilegibles.

de http://developer.android.com/reference/android/os/Parcel.html

+14

¿Cómo es útil? – skaffman

+1

¿Puede sugerir un mecanismo de serialización alternativo? – aaronsnoswell

+2

@aaronsnoswell Recomiendo usar Kryo, o implementar la interfaz Java Externalizable (si su objeto no es complejo) – nobre

12

encuentro que Parcelable se usa con mayor frecuencia en Android dentro de los datos Bu ndles, pero más específicamente dentro de un Handler que está enviando y recibiendo mensajes. Como ejemplo, puede tener un AsyncTask o un Runnable que debe ejecutarse en segundo plano pero publicar los datos resultantes en el hilo principal o Activity.

Aquí hay un ejemplo simple. Si tengo un Runnable que tiene este aspecto:

package com.example; 

import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.net.HttpURLConnection; 
import java.net.URL; 

import android.os.Bundle; 
import android.os.Handler; 
import android.os.Message; 
import android.util.Log; 

import com.example.data.ProductInfo; 
import com.google.gson.Gson; 
import com.google.gson.reflect.TypeToken; 
import com.squareup.okhttp.OkHttpClient; 

public class AsyncRunnableExample extends Thread { 
    public static final String KEY = "AsyncRunnableExample_MSG_KEY"; 

    private static final String TAG = AsyncRunnableExample.class.getSimpleName(); 
    private static final TypeToken<ProductInfo> PRODUCTINFO = 
       new TypeToken<ProductInfo>() { 
       }; 
    private static final Gson GSON = new Gson(); 

    private String productCode; 
    OkHttpClient client; 
    Handler handler; 

    public AsyncRunnableExample(Handler handler, String productCode) 
    { 
     this.handler = handler; 
     this.productCode = productCode; 
     client = new OkHttpClient(); 
    } 

    @Override 
    public void run() { 
     String url = "http://someserver/api/" + productCode; 

     try 
     { 
      HttpURLConnection connection = client.open(new URL(url)); 
      InputStream is = connection.getInputStream(); 
      InputStreamReader isr = new InputStreamReader(is); 

      // Deserialize HTTP response to concrete type. 
      ProductInfo info = GSON.fromJson(isr, PRODUCTINFO.getType()); 

      Message msg = new Message(); 
      Bundle b = new Bundle(); 
      b.putParcelable(KEY, info); 
      msg.setData(b); 
      handler.sendMessage(msg); 

     } 
     catch (Exception err) 
     { 
      Log.e(TAG, err.toString()); 
     } 

    } 
} 

Como se puede ver, este ejecutable recibe un gestor en su constructor. Esto se llama desde algún Activity así:

static class MyInnerHandler extends Handler{ 
     WeakReference<MainActivity> mActivity; 

     MyInnerHandler(MainActivity activity) { 
      mActivity = new WeakReference<MainActivity>(activity); 
     } 

     @Override 
     public void handleMessage(Message msg) { 
      MainActivity theActivity = mActivity.get(); 
      ProductInfo info = (ProductInfo) msg.getData().getParcelable(AsyncRunnableExample.KEY); 

      // use the data from the Parcelable 'ProductInfo' class here 

      } 
     } 
    } 
    private MyInnerHandler myHandler = new MyInnerHandler(this); 

    @Override 
    public void onClick(View v) { 
     AsyncRunnableExample thread = new AsyncRunnableExample(myHandler, barcode.getText().toString()); 
     thread.start(); 
    } 

Ahora, todo lo que queda es el corazón de esta pregunta, ¿cómo se define una clase como Parcelable. Elegí una clase bastante compleja para mostrar porque hay algunas cosas que no verías con una simple.Aquí es la clase ProductInfo, que paquetes y unParcels limpiamente:

public class ProductInfo implements Parcelable { 

    private String brand; 
    private Long id; 
    private String name; 
    private String description; 
    private String slug; 
    private String layout; 
    private String large_image_url; 
    private String render_image_url; 
    private String small_image_url; 
    private Double price; 
    private String public_url; 
    private ArrayList<ImageGroup> images; 
    private ArrayList<ProductInfo> related; 
    private Double saleprice; 
    private String sizes; 
    private String colours; 
    private String header; 
    private String footer; 
    private Long productcode; 

    // getters and setters omitted here 

    @Override 
    public void writeToParcel(Parcel dest, int flags) { 
     dest.writeLong(id); 
     dest.writeString(name); 
     dest.writeString(description); 
     dest.writeString(slug); 
     dest.writeString(layout); 
     dest.writeString(large_image_url); 
     dest.writeString(render_image_url); 
     dest.writeString(small_image_url); 
     dest.writeDouble(price); 
     dest.writeString(public_url); 
     dest.writeParcelableArray((ImageGroup[])images.toArray(), flags); 
     dest.writeParcelableArray((ProductInfo[])related.toArray(), flags); 
     dest.writeDouble(saleprice); 
     dest.writeString(sizes); 
     dest.writeString(colours); 
     dest.writeString(header); 
     dest.writeString(footer); 
     dest.writeLong(productcode); 
    } 

    public ProductInfo(Parcel in) 
    { 
     id = in.readLong(); 
     name = in.readString(); 
     description = in.readString(); 
     slug = in.readString(); 
     layout = in.readString(); 
     large_image_url = in.readString(); 
     render_image_url = in.readString(); 
     small_image_url = in.readString(); 
     price = in.readDouble(); 
     public_url = in.readString(); 
     images = in.readArrayList(ImageGroup.class.getClassLoader()); 
     related = in.readArrayList(ProductInfo.class.getClassLoader()); 
     saleprice = in.readDouble(); 
     sizes = in.readString(); 
     colours = in.readString(); 
     header = in.readString(); 
     footer = in.readString(); 
     productcode = in.readLong(); 
    } 

    public static final Parcelable.Creator<ProductInfo> CREATOR = new Parcelable.Creator<ProductInfo>() { 
     public ProductInfo createFromParcel(Parcel in) { 
      return new ProductInfo(in); 
     } 

     public ProductInfo[] newArray(int size) { 
      return new ProductInfo[size]; 
     } 
    }; 

    @Override 
    public int describeContents() { 
     return 0; 
    } 
} 

El CREATOR es crítico, ya que es el constructor resultante de tomar un paquete. Incluí los tipos de datos más complejos para que pueda ver cómo Parcel y unParcel Arrays de objetos Parcelable. Esto es algo común cuando se usa Gson para convertir JSON en objetos con niños como en este ejemplo.

1

Yo también tuve un problema similar. solo el siguiente fragmento de emmby y this me ayudó.

public static final Parcelable.Creator<Foo> CREATOR = new Parcelable.Creator<Foo>() { 
     public Foo createFromParcel(Parcel source) { 
      final Foo f = new Foo(); 
      f.str = (String) source.readValue(Foo.class.getClassLoader()); 
      return f; 
     } 

     public Foo[] newArray(int size) { 
      throw new UnsupportedOperationException(); 
     } 

Se debe tener en cada uno de la clase que implementa parcelable