2009-10-26 4 views
50

J'essaie d'utiliser Parcel pour écrire puis relire un Parcelable. Pour une raison quelconque, lorsque je lis l'objet à partir du fichier, il revient null.Comment utiliser Parcel dans 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); 
    } 


} 

Que manque-t-il?

MISE À JOUR: Pour simplifier le test, j'ai supprimé la lecture et l'écriture de fichiers dans mon exemple d'origine.

Répondre

62

Ah, j'ai finalement trouvé le problème. Il y en avait deux en fait.

  1. CREATOR doit être public, non protégé. Mais plus important encore,
  2. Vous devez appeler setDataPosition(0) après unmarshalling vos données.

Voici la version révisée, le code de travail:

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

vous venez d'enregistrer ma journée. MERCI. – Matthias

+0

Juste ne pas essayer cela avec Bitmaps à l'intérieur de votre colis (capacité) :( –

+0

setDataPosition (0) m'a sauvé! Merci;) –

20

Prenez garde! Ne pas utiliser Colis pour la sérialisation dans un fichier

La parcelle n'est pas un mécanisme de sérialisation à usage général. Cette classe (et l'API Parcelable correspondante pour placer des objets arbitraires dans une parcelle) est conçue comme un transport IPC haute performance. En tant que tel, il n'est pas approprié de placer des données de parcelle dans un stockage persistant: des modifications de l'implémentation sous-jacente de l'une des données dans la parcelle peuvent rendre les données plus anciennes illisibles.

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

+14

Comment est-ce utile? – skaffman

+1

Pouvez-vous suggérer un mécanisme de sérialisation alternatif? – aaronsnoswell

+2

@aaronsnoswell Je recommande d'utiliser Kryo, ou d'implémenter l'interface Java Externalizable (si votre objet n'est pas complexe) – nobre

12

Je trouve que Parcelable est le plus souvent utilisé dans Android dans les données Bu ndles, mais plus spécifiquement dans un gestionnaire qui envoie et reçoit des messages. Par exemple, vous pouvez avoir un AsyncTask ou un Runnable qui doit s'exécuter en arrière-plan, mais afficher les données résultantes dans le thread principal ou Activity.

Voici un exemple simple. Si j'ai un Runnable qui ressemble à ceci:

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

    } 
} 

Comme vous pouvez le voir, ce runnable prend un gestionnaire dans son constructeur. On appelle cela de quelque Activity comme ceci:

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

Maintenant, tout ce qui reste est le cœur de cette question, comment vous définissez une classe comme Parcelable. J'ai choisi une classe assez complexe à montrer parce qu'il y a des choses que vous ne verriez pas avec une simple.Voici la classe ProductInfo, qui unParcels proprement et Parcels:

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

Le CREATOR est critique, comme le constructeur résultant prendre une parcelle. J'ai inclus les types de données les plus complexes afin que vous puissiez voir comment Arrays Parcel et unParcel d'objets Parcelable. C'est une chose courante en utilisant Gson pour convertir JSON en objets avec des enfants comme dans cet exemple.

1

J'ai aussi eu le même problème. seul l'extrait suivant de emmby et this m'a aidé.

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

Il doit être conservé dans chacune de la classe qui implémente Parcelable

Questions connexes