web-dev-qa-db-ja.com

AndroidでParcelを使用する方法は?

Parcelを使用して、Parcelableを書き込んでから読み戻そうとしています。何らかの理由で、ファイルからオブジェクトを読み戻すと、nullとして返されます。

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


}

私は何が欠けていますか?

PDATE:テストを簡単にするために、元の例ではファイルの読み取りと書き込みを削除しました。

54
emmby

ああ、ようやく問題が見つかりました。実際には2つありました。

  1. CREATORは保護されているのではなく、公開されている必要があります。しかし、もっと重要なのは、
  2. データを非整列化した後、setDataPosition(0)を呼び出す必要があります。

修正された作業コードは次のとおりです。

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


}
70
emmby

気をつけて!ファイルへのシリアル化にParcelを使用しないでください

パーセルは、汎用のシリアル化メカニズムではありません。このクラス(および任意のオブジェクトをパーセルに配置するための対応するParcelable API)は、高性能IPC=トランスポートとして設計されています。したがって、パーセルデータを永続的に配置することは適切ではありませんストレージ:パーセル内のデータの基礎となる実装の変更により、古いデータが読み取れなくなる可能性があります。

from http://developer.Android.com/reference/Android/os/Parcel.html

20
Carl D'Halluin

Parcelableは、データバンドル内のAndroidで最も頻繁に使用されますが、より具体的には、メッセージを送受信するハンドラー内で使用されます。たとえば、AsyncTaskまたは、バックグラウンドで実行する必要があるが、結果のデータをメインスレッドまたはRunnableにポストするActivity

以下に簡単な例を示します。次のようなRunnableがある場合:

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

    }
}

ご覧のとおり、このrunnableはコンストラクターでHandlerを取ります。これは、次のようないくつかのActivityから呼び出されます。

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

さて、残っているのはこの質問の核心です。クラスをParcelableとして定義する方法です。単純なクラスでは見られないことがいくつかあるため、表示するのにかなり複雑なクラスを選択しました。以下はProductInfoクラスで、ParcelsとunParcelsをきれいにしています:

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

CREATORは重要であり、結果のコンストラクターはパーセルを取得します。より複雑なデータ型を含めたため、Parcelableオブジェクトの配列をパーセルおよびアンパーセルする方法を確認できます。これは、Gsonを使用してJSONをこの例のように子を持つオブジェクトに変換する場合の一般的なことです。

15
David S.

パーセルのコンセプトをよりよく理解するには、以下のリンクを試してください

http://prasanta-paul.blogspot.com/2010/06/Android-parcelable-example.html

お役に立てれば :)

5
kAnNaN

私も同様の問題を抱えていました。 emmbythis の次のスニペットのみが助けになりました。

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

Parcelableを実装する各クラスに保持する必要があります

1
A_rmas