コンピュータクワガタ

かっぱのかっぱによるコンピュータ関連のサイトです

Androidアプリ入門 No.65 Intent アクションでデータを受け取る Parcelable

アクションでデータを受け取る

Parcelable

次に、Parcelableに関して説明する。Parcelという単語が小包という意味を持つように、データをまとめて渡すようなものをイメージしている。アクションで渡すデータとしてはプリミティブやArrayList、String、またそれらの配列は用意されているがオブジェクトを受け渡すようなしくみは、Serializableしか用意されていない。Serializableでは同一のアプリケーションでの明示的なIntentであれば問題ないが、暗黙的なIntentの場合(自分が作成したActivityがアクションを受け取るとは限らないため)Serializeしたオブジェクトが復元できる保証はない。そうした場合に、Parcelableを使用することでオブジェクトのようなものの受け渡しをすることができる。
まず、IntentTest2プロジェクトに、Parcelableインタフェースを実装したTestParcelable.javaクラスを作成する。受け渡す例は、Serializableと同様にintとStringデータの2つとなる。

package sample.dto;

import android.os.Parcel;
import android.os.Parcelable;

public class TestParcelable implements Parcelable {
    private int intData;
    private String stringData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(intData);
        out.writeString(stringData);
    }

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

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

    private TestParcelable(Parcel in) {
        intData = in.readInt();
        stringData = in.readString();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("intData=").append(intData);
        sb.append(" stringData=").append(stringData);

        return sb.toString();
    }
}

次に、レイアウトのmain.xmlを以下のようにする。IntentTestを呼び出すボタンを1つ追加する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <EditText
        android:id="@+id/urlEditText"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="http://www.google.co.jp" />
    <Button
        android:id="@+id/browserButton"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="ブラウザ表示" />
    <Button
        android:id="@+id/serializableButton"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="オブジェクト受け渡し" />
    <Button
        android:id="@+id/parcelableButton"
        android:layout_width="fill_parent" 
        android:layout_height="wrap_content"
        android:text="オブジェクト受け渡し2" />
</LinearLayout>

IntentTest2のMainActivity.javaは以下のようにする。parcelableButtonが押されたときに、TestParcelableを作成しExtraとして渡している。

package sample.it2;

import sample.dto.TestDto;
import sample.dto.TestParcelable;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;

public class MainActivity extends Activity {
    private EditText urlEditText;
    private Button browserButton;
    private Button serializableButton;
    private Button parcelableButton;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        urlEditText = (EditText) findViewById(R.id.urlEditText);
        browserButton = (Button) findViewById(R.id.browserButton);
        serializableButton = (Button) findViewById(R.id.serializableButton);
        parcelableButton = (Button) findViewById(R.id.parcelableButton);

        browserButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("net.kuwalab.web");
                intent.setData(Uri.parse(urlEditText.getText().toString()));
                startActivity(intent);
            }
        });

        serializableButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(v.getContext(), SubActivity.class);
                intent.setAction(Intent.ACTION_VIEW);
                TestDto testDto = new TestDto();
                testDto.setIntData(100);
                testDto.setStringData(urlEditText.getText().toString());
                intent.putExtra("testDto", testDto);
                startActivity(intent);
            }
        });

        parcelableButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent("net.kuwalab.web");
                intent.setData(Uri.parse(urlEditText.getText().toString()));
                TestParcelable testParcelable = new TestParcelable(55, urlEditText.getText()
                        .toString());
                intent.putExtra("testParcelable", testParcelable);
                startActivity(intent);
            }
        });
    }
}

対して、データを受け取るIntentTestプロジェクトのTestParcelable.javaは以下のようにする。注目はIntentTest2プロジェクトのTestParcelableには存在していた引数が2つあるコンストラクタがないことである。クラスの実体が異なっていても、使用できる事がわかる。重要なのは、TestParcelable(Parcel in)でデータを復元する順番と、writeToParcel(Parcel out, int flags)でデータを書き出す順番を同じにすることである。

package sample.dto;

import android.os.Parcel;
import android.os.Parcelable;

public class TestParcelable implements Parcelable {
    private int intData;
    private String stringData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(intData);
        out.writeString(stringData);
    }

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

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

    private TestParcelable(Parcel in) {
        intData = in.readInt();
        stringData = in.readString();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("intData=").append(intData);
        sb.append(" stringData=").append(stringData);

        return sb.toString();
    }
}

また、MainActivity.javaは以下のようにする。

package sample.it;

import sample.dto.TestParcelable;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Toast;

public class MainActivity extends Activity {
    private WebView webView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        webView = (WebView) findViewById(R.id.webView);
        webView.setWebViewClient(new WebViewClient());

        String url = "http://d.hatena.ne.jp/kuwalab/";
        if ("net.kuwalab.web".equals(getIntent().getAction())) {
            url = getIntent().getData().toString();
            Bundle extras = getIntent().getExtras();
            if (extras.containsKey("test")) {
                String extra = extras.getString("test");
                Toast.makeText(getApplicationContext(), "extra=" + extra, Toast.LENGTH_SHORT)
                        .show();
            } else {
                TestParcelable testParcelable = (TestParcelable) extras
                        .getParcelable("testParcelable");
                Toast.makeText(getApplicationContext(), testParcelable.toString(),
                        Toast.LENGTH_SHORT).show();
            }
        }

        webView.loadUrl(url);
    }
}

実行結果は以下になる。Parcelableを通じてデータの受け渡しができていることがわかる。

また、Parcelable.Creatorインタフェースを実装している。このクラスはデータの復元時、つまりIntentTestが呼び出された際に、実行されている。これを確認するために、IntentTest2のTestParcelable.javaを以下のように変更する。

package sample.dto;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

public class TestParcelable implements Parcelable {
    private int intData;
    private String stringData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(intData);
        out.writeString(stringData);
    }

    public static final Parcelable.Creator<TestParcelable> CREATOR =
        new Parcelable.Creator<TestParcelable>() {
        public TestParcelable createFromParcel(Parcel in) {
            Log.i("IntentTest#TestParcelable", "createFromParcel");
            return new TestParcelable(in);
        }

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

    private TestParcelable(Parcel in) {
        intData = in.readInt();
        stringData = in.readString();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("intData=").append(intData);
        sb.append(" stringData=").append(stringData);

        return sb.toString();
    }
}

実行すると、CREATOR#createFromParcelが呼び出されていることが、以下のログから確認することができる。

また、ここでinの順番を変えて正しく動作しないことを確認する。例えば、TestParcelable.javaを以下のように変更して実行する。具体的には、TestParcelable(Pacel in)のデータの復元の順番を入れ替えただけである。

package sample.dto;

import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;

public class TestParcelable implements Parcelable {
    private int intData;
    private String stringData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(intData);
        out.writeString(stringData);
    }

    public static final Parcelable.Creator<TestParcelable> CREATOR =
        new Parcelable.Creator<TestParcelable>() {
        public TestParcelable createFromParcel(Parcel in) {
            Log.i("IntentTest#TestParcelable", "createFromParcel");
            return new TestParcelable(in);
        }

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

    private TestParcelable(Parcel in) {
        stringData = in.readString();
        intData = in.readInt();
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("intData=").append(intData);
        sb.append(" stringData=").append(stringData);

        return sb.toString();
    }
}

実際に実行すると、渡したデータと異なるデータが表示されているのが分かる。そのため、Parcelableを使用する場合には、inにデータを格納する順番と、outでデータを復元する順番を同じにしないといけないことが確認できる。