アンドロイド - Parcelableを実装してIntentにデータを渡す方法

By JS | Last updated: October 09, 2019

Androidは、プロセス間でデータを転送するときにバインダーを介して Parcelというオブジェクトに渡します。 Parcelは抽象化されたオブジェクトにデータとオブジェクトを持っているコンテナとすることができます。

だから、私たちは、転送するオブジェクトをParcelに保存して、他のプロセスに転送します。 Parcelableはインタフェースであり、Parcelにオブジェクトをwrite / readするように作られています。

もし私定義したクラスのオブジェクトを他のアクティビティに転送するには、 Parcelableをimplementsして実装してくれればされます。

Parcelableオブジェクトの実装

次のコードは、MyDataという名前のクラスを定義し、Parcelableを実装しました。 アンドロイドスタジオでクラスを作成すると、次のように基本的なコードを自動生成してくれます。

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

class MyData() : Parcelable{

    constructor(parcel: Parcel) : this() {
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<MyData> {
        override fun createFromParcel(parcel: Parcel): MyData {
            return MyData(parcel)
        }

        override fun newArray(size: Int): Array<MyData?> {
            return arrayOfNulls(size)
        }
    }

}
  • constructor:Parcelからデータを読み取るオブジェクトを作成するときに使用します。
  • writeToParcel:オブジェクトのデータをParcelにwrtieするときに使用されます。
  • describeContents:データがどのような種類であることを説明します。 Parcelableオブジェクトがfile descriptorが含まれている場合はCONTENTS_FILE_DESCRIPTORを返して、そのほかは0を返すようにします。
  • Parcelable.Creator:必ず実装しなければならstatic classです。 Parcelからオブジェクトを作成するときに使用します。

describeContents()の結果の値にどのような操作をした経験がないのですが。 Android Referenceを見ると、ParcelableオブジェクトがFile descriptorが含まれている場合はCONTENTS_FILE_DESCRIPTORを返して、そのほかは0を返すようになっています。

Parcelableオブジェクトの実装2

上記のクラスは、メンバ変数が1つもないクラスです。 メンバ変数を追加するたびに、 constructorwriteToParcelにデータをread / writeするコードを実装ヘジュオヤます。

Javaで提供されるSerializableを使用すると、変数が追加されるたびに実装する必要がありません。しかし、AndroidのParcelableを利用すれば、read/writeコードを直接実装してくれるとします。 SerializableとParcelableは互いに長所と短所があるので、どのようなことを使用するかは、状況に応じて決定する必要があります。個人的に、AndroidはParcelableに実装されているので、同じように実装することを好む。

次のコードは、上記のクラスで nameversionlastModifiedという3つの変数を追加しました。そしてconstructorとwriteToParcelにread/writeコードを実装しました。

class MyData() : Parcelable {
    var name : String? = null
    var version : Int = 0
    var lastModified : Int = 0

    constructor(parcel: Parcel) : this() {
        name = parcel.readString()
        version = parcel.readInt()
        lastModified = parcel.readInt()
    }

    constructor(name: String?, version: Int, lastModified: Int) : this() {
        this.name = name
        this.version = version
        this.lastModified = lastModified
    }

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeInt(version)
        parcel.writeInt(lastModified)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<MyData> {
        override fun createFromParcel(parcel: Parcel): MyData {
            return MyData(parcel)
        }

        override fun newArray(size: Int): Array<MyData?> {
            return arrayOfNulls(size)
        }
    }

}

上記のコードを分けてみると、 writeToParcel()メソッドでは、Parcelにデータをwriteします。 writeString()writeInt()など型にメソッドがサポートしており、引数をParcelに保存します。

override fun writeToParcel(parcel: Parcel, flags: Int) {
    parcel.writeString(name)
    parcel.writeInt(version)
    parcel.writeInt(lastModified)
}

コンストラクタメソッドでは、Parcelからデータを読み取ります。 readString()を呼び出すと、writeした順にデータを読み込みます。そのため、どのようなデータを取得するのですか?という考えはしなくてされます。

constructor(parcel: Parcel) : this() {
    name = parcel.readString()
    version = parcel.readInt()
    lastModified = parcel.readInt()
}

読み書きのプロセスは、すべての抽象化されています。 IntentなどのAndroid上で提供されるオブジェクトにParcelableオブジェクトを保存すれば、Androidはバインダーを介してオブジェクトが他のプロセスに渡されるときParcelにオブジェクトを保存して配信して再度Parcelからオブジェクトにインポートするプロセスをすべての世話をしてくれます。

従ってこの部分について、私たちが気を使わなけれことはありません。ただ、Parcelableオブジェクトにwrite / readコードのみ気を使って実装さくれればされます。

オブジェクトをParcelに、Parcelをオブジェクトに変換するプロセス

上記のように、ParcelにParcelableオブジェクトをwrite / readするプロセスは、すべての抽象化されているので、この部分について考えなくてもされます。しかし、気にするので、サンプルコードを作成してみました。

ParcelableがどのようにParcelに変換されるか関心お持ちでない場合は、この部分は省略浮かべ構いません。

次のコードは、MyDataオブジェクトをParcelにし、他のプロセスに渡したと仮定して、再度Parcelオブジェクトを解放MyDataに変換するプロセスです。

// MyDataオブジェクトを作成の
val myData = MyData("myDatabase", 1, 20191009)
// Parcelオブジェクトを作成し、MyDataオブジェクトを保存
val p1 = Parcel.obtain()
p1.writeValue(myData)
Log.d(TAG, "origin MyData{${myData?.name}," +
        " ${myData?.version}, ${myData?.lastModified}}")

// Parcelオブジェクトを別のプロセスに渡すためにByteに変換
val bytes: ByteArray = p1.marshall()

//他のプロセスに渡されたと仮定
// bytesオブジェクトをunmarshallてp2というParcelに保存
val p2 = Parcel.obtain()
p2.unmarshall(bytes, 0, bytes.size)
p2.setDataPosition(0)

// p2からデータを読み取るMyDataに変換
val delivered: MyData = p2.readValue(MyData::class.java.classLoader) as MyData
Log.d(TAG, "delivered MyData{${delivered?.name}," +
        " ${delivered?.version}, ${delivered?.lastModified}}")

予想したように、ログはこう出力されます。

com.codechacha.sample D/MainActivity: origin MyData{myDatabase, 1, 20191009}
com.codechacha.sample D/MainActivity: delivered MyData{myDatabase, 1, 20191009}

上記のコードのように、オブジェクトをParcelに変換し、このParcelをBytesに変換して転送した後、再び逆の順序でオブジェクトを作成することができます。

次は、Androidプラットフォームで定義されている Parcel.javaのコードです。 コードを見れば Parcel.writeValue()Parcelable.writeToParcel()を呼び出します。

public final void writeValue(@Nullable Object v) {
    if (v == null) {
        writeInt(VAL_NULL);
      ....
    } else if (v instanceof Parcelable) {
        writeInt(VAL_PARCELABLE);
        writeParcelable((Parcelable) v, 0);
    }
    ....
}

public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
    ....
    p.writeToParcel(this, parcelableFlags);
}

以下は、 Parcel.readValue()関連のコードです。 コードに沿って行ってみれば、私たちは上で定義し CREATOR.createFromParcel()を呼び出してオブジェクトを生成してくれます。

public final Object readValue(@Nullable ClassLoader loader) {
    ...
    case VAL_PARCELABLE:
        return readParcelable(loader);
    ...
}

public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
    Parcelable.Creator<?> creator = readParcelableCreator(loader);
    if (creator == null) {
        return null;
    }
    if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
      Parcelable.ClassLoaderCreator<?> classLoaderCreator =
          (Parcelable.ClassLoaderCreator<?>) creator;
      return (T) classLoaderCreator.createFromParcel(this, loader);
    }
    return (T) creator.createFromParcel(this);
}

このような過程を全てのAndroidのフレームワークが分かっと考えてください。私たちは、Parcelableクラスにread / writeコードを作成するだけです。

実際に私Parcelをbytesに変換し、これをバインダーに転送するためのコードは見ていない。この部分は推測なので、事実とは異なる場合があります。

IntentにParcelableオブジェクト転送する

IntentにParcelableオブジェクトを入れて、他のアクティビティに渡すことができます。

次は、インテントにParcelableオブジェクトを追加するコードです。

val INTENT_EXTRA_MY_DATA = "intent_extra_my_data"
val myData = MyData("myDatabase", 1, 20191009)

val intent = Intent(this, SubActivity::class.java)
intent.putExtra(INTENT_EXTRA_MY_DATA, myData)
startActivity(intent)

Intent.putExtraというメソッドを使用すると、ParcelableをIntentに追加することができます。 startActivityを呼び出すと、インテントに設定したアクティビティが実行されます。アクティビティが実行されるとParcelableオブジェクトも上記の過程を経て活動に渡されます。

以下は、startActivityによって実行されたアクティビティにParcelableオブジェクトを取得するコードです。

val INTENT_EXTRA_MY_DATA = "intent_extra_my_data"
val myData = intent?.getParcelableExtra<MyData>(MainActivity.INTENT_EXTRA_MY_DATA)
val text = "MyData{${myData?.name}, ${myData?.version}, ${myData?.lastModified}}"
textView.text = text
Log.d(TAG, "Received: $text")

getParcelableExtra<클래스타입>を使用すると、Parcelableオブジェクトを渡すことができます。インテントは、Parcelを <Class type>に明示したクラスにオブジェクトに変換します。

ログは、予想した通り、渡されたデータの情報が出力されました。

com.codechacha.sample D/SubActivity: Received: MyData{myDatabase, 1, 20191009}

まとめ

プロセス間でデータを転送が必要な場合、オブジェクトのクラスにParcelableインタフェースを実装する必要があります。 Parcelableオブジェクトに実装しなければならないことは伝えたい変数のwrite / readコードです。 Parcelableは抽象化されているので、あとは、Androidが分かっています。

この記事で使用したコードは、GitHubにあります。

参考

Related Posts

codechachaCopyright ©2019 codechacha