Android
Parcelable

Parcelableを使う話

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は怖くない。
  • 偉大な先人の成果に敬意を表しつつ、活用させていただきましょう。