Androidで、ActivityやFragmentにパラメータを渡すには、Bundleを使うわけですが、そのBundleは基本的な型のデータ以外に、Parcelableインターフェースを実装したクラスのインスタンスも受け付けてくれます。
さて、そのParcelableインターフェースとはどういったものかというと、思いっきり端折って書いてしまえば、
interface Parcelable {
int describeContents();
void writeToParcel(Parcel dest, int flags);
}
こんな感じで、これらのメソッドを実装すれば、Bundleに渡すことができる(つまり、ActivityやFragmentに渡すことができる)ということになります。
で、そのメソッドの実装方法ですが、
- 自力で書く
- コードジェネレータ・プラグインを使う (https://plugins.jetbrains.com/plugin/7332?pr=idea)
- アノテーション・ライブラリを使う (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は怖くない。
- 偉大な先人の成果に敬意を表しつつ、活用させていただきましょう。