Edited at

Parcelableを使う話

More than 1 year has passed since last update.

Androidで、ActivityやFragmentにパラメータを渡すには、Bundleを使うわけですが、そのBundleは基本的な型のデータ以外に、Parcelableインターフェースを実装したクラスのインスタンスも受け付けてくれます。

さて、そのParcelableインターフェースとはどういったものかというと、思いっきり端折って書いてしまえば、

interface Parcelable {

int describeContents();
void writeToParcel(Parcel dest, int flags);
}

こんな感じで、これらのメソッドを実装すれば、Bundleに渡すことができる(つまり、ActivityやFragmentに渡すことができる)ということになります。

で、そのメソッドの実装方法ですが、


  1. 自力で書く

  2. コードジェネレータ・プラグインを使う (https://plugins.jetbrains.com/plugin/7332?pr=idea)

  3. アノテーション・ライブラリを使う (https://github.com/johncarl81/parceler)

と言う方法があります。

筆者は自力で書いたことがない(めんどくさいじゃん)ので、ここでは、それ以外の方法を取りあげます。


やってみる

ここでは、


public class A {
int foo;
String bar;
B buzz;
}

public class B {
int hoge;
String fuga;
}

public class B2 extends B {
double piyo;
}

という3つのクラスを使って、

    public static void parcelableTest() {

A a = new A();
a.foo = 1;
a.bar = "2";
a.buzz = new B();
a.buzz.hoge = 3;
a.buzz.fuga = "4";

Bundle bundle = new Bundle();
bundle.putParcelable("PARCELABLE", a);

A a2 = bundle.getParcelable("PARCELABLE");

Log.d("parcelableTest",
"foo: " + a2.foo + "\n" +
"bar: " + a2.bar + "\n" +
"buzz.hoge: " + a2.buzz.hoge + "\n" +
"buzz.fuga: " + a2.buzz.fuga + "\n"
);

B2 b2 = new B2();
b2.hoge = 5;
b2.fuga = "6";
b2.piyo = 7.0;
a.buzz = b2;

bundle.putParcelable("PARCELABLE", a);
a2 = bundle.getParcelable("PARCELABLE");

if (a2.buzz instanceof B2) {
b2 = (B2) a2.buzz;

Log.d("parcelableTest",
"foo: " + a2.foo + "\n" +
"bar: " + a2.bar + "\n" +
"buzz.hoge: " + b2.hoge + "\n" +
"buzz.fuga: " + b2.fuga + "\n" +
"buzz.piyo: " + b2.piyo + "\n"
);
} else {
Log.d("parcelableTest",
"foo: " + a2.foo + "\n" +
"bar: " + a2.bar + "\n" +
"buzz.hoge: " + a2.buzz.hoge + "\n" +
"buzz.fuga: " + a2.buzz.fuga + "\n"
);
}
}

というようなコードを実行して、Parcelableの復元を確認します。


コードジェネレータ・プラグイン

AndroidStudioのPreferencesダイアログで、Parcelableをキーワードにして検索すると、「Android Parcelable code generator.」というプラグインが見つかります。

これをインストールした後、上記のA,B,B2クラスのそれぞれを開いたエディタ上で右クリック、「Generate」「Parcelable」を選んでいくと、それぞれのクラスに必要なコードが生成されて、

public class A implements Parcelable {

int foo;
String bar;
B buzz;

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

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.foo);
dest.writeString(this.bar);
dest.writeParcelable(this.buzz, 0);
}

public A() {
}

protected A(Parcel in) {
this.foo = in.readInt();
this.bar = in.readString();
this.buzz = in.readParcelable(B.class.getClassLoader());
}

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

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

public class B implements Parcelable {
int hoge;
String fuga;

public B() {
}

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

@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.hoge);
dest.writeString(this.fuga);
}

protected B(Parcel in) {
this.hoge = in.readInt();
this.fuga = in.readString();
}

public static final Creator<B> CREATOR = new Creator<B>() {
public B createFromParcel(Parcel source) {
return new B(source);
}

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

public class B2 extends B {
double piyo;

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

@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeDouble(this.piyo);
}

public B2() {
}

protected B2(Parcel in) {
super(in);
this.piyo = in.readDouble();
}

public static final Creator<B2> CREATOR = new Creator<B2>() {
public B2 createFromParcel(Parcel source) {
return new B2(source);
}

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

となります。やっていることは、なんとなくわかりますが、自分で書くのはめんどくさいですね。

先の検証コードを実行したときのログは、

30724-30724/jp.fogg.fragmentanimation.gene D/parcelableTest﹕ foo: 1

bar: 2
buzz.hoge: 3
buzz.fuga: 4
30724-30724/jp.fogg.fragmentanimation.gene D/parcelableTest﹕ foo: 1
bar: 2
buzz.hoge: 5
buzz.fuga: 6
buzz.piyo: 7.0

となります。ちゃんと復元できてますね。


アノテーション・ライブラリ

アノテーション・ライブラリを使うために、build.gradleに準備をします。

必要なことをまとめてしまうと、

buildscript {

dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
}
}

apply plugin: 'com.neenbedankt.android-apt'

dependencies {
compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"
}

を追加します。

AndroidStudioが用意してくれるテンプレな環境だと、最初のclasspathはプロジェクト直下のbuild.gradle、それ以外はappの下のbuild.gradleになると思います。

元のA,B,B2クラスの修正は簡単で、

@Parcel

public class A {
int foo;
String bar;
B buzz;

public A() {}
}

@Parcel
public class B {
int hoge;
String fuga;

public B() {}
}

@Parcel
public class B2 extends B {
double piyo;

public B2() {super();}
}

原則としてはクラスに、@Parcelアノテーションを付加するだけです(引数無しのコンストラクタが必須なので、上の例では、明示的に追加しました)。

簡単ですね。

この魔法の肝は、元のクラスをParcelableにするのではなく、Parcelableなラッパーを生成すると言うところです。

そのため、検証コードの

        bundle.putParcelable("PARCELABLE", a);

a2 = bundle.getParcelable("PARCELABLE");

の部分に修正が必要です。

        bundle.putParcelable("PARCELABLE", Parcels.wrap(a));

a2 = Parcels.unwrap(bundle.getParcelable("PARCELABLE"));

このようにします。

実行時のログは、こちら。

31654-31654/jp.fogg.fragmentanimation.anno D/parcelableTest﹕ foo: 1

bar: 2
buzz.hoge: 3
buzz.fuga: 4
31654-31654/jp.fogg.fragmentanimation.anno D/parcelableTest﹕ foo: 1
bar: 2
buzz.hoge: 5
buzz.fuga: 6
buzz.piyo: 7.0

ちゃんとできてますね。


まとめ


  • 文明の利器を活用すれば、Parcelableは怖くない。

  • 偉大な先人の成果に敬意を表しつつ、活用させていただきましょう。