blog

Parcelableの実装方法

Serialisationとは、簡単に言うと、オブジェクト・データを一定のルールに従って追跡可能なバイナリ・ストリームに変換し、このバイナリ・ストリームを2者間で転送するプロセスのことです。この際、出...

Oct 16, 2020 · 19 min. read
シェア

はじめに

シリアル化とは、簡単に言えば、オブジェクト・データを一定の規則に従って、追跡可能な一連のバイナリ・ストリームに変換し、このバイナリ・ストリームを2者間で伝送するプロセスのことです。この際、出力側は合意されたルールに従ってオブジェクトを変換し、受信側は合意されたルールに従って変換して有効なデータを取得します。

Androidの日常的な開発では、SerializableとParcelableが最も一般的なシリアライズ方法であり、さまざまなシナリオに対応するために、それぞれの長所と短所についてよく比較されます。

Serializableを分析し)、Serializableの全体的な実装もレビューを行うには、単純な理解を持っていると考えられています:

  1. ツリーとしてSerializableオブジェクトは、トラバースと情報を取得するために各ノードを反映しています。情報を保存するために多くの中間変数の生成時に
  2. いくつかの実装可能なオブジェクトのメソッドを提供し、あなたは、暗号化と復号化などのオブジェクトを交換するなど、いくつかの処理を行うプロセスをデシリアライズ、シリアライズすることができますデフォルト値など。

この記事の目的は、Parcelableの実装の原理を分析し、1つは、その実装を理解することができます。2番目は、より良いSerializableと比較することができます。

この記事では、次の質問にお答えします、あなたが答えを知らない場合は、多分いくつかのヘルプ:

  1. Parcelable 実装方法
  2. なぜシリアライズとデシリアライズは同じ順序でなければならないのですか?
  3. Parcelは自分で実装できますか?
  4. サブクラスは Parcelable を実装する必要がありますか?
  5. Parcelable 本当にSerializableより速いのか?

Parcel

Parcelableの実装を一言で表すと、「マーカーから取得した情報を、補助情報とともに、1つずつ順番にストレージに書き込んでいく」というものです。したがって、Parcelableにとって、ストレージは非常に重要です。ストレージの主な実装はParcel.cppで行います。

Parcel.cppはデータ転送問題のIPC処理に対処するために登場したもので、Binderパッケージの中にParcel.cppが配置されていることからもこの点の登場がわかります。Java側から楽しめるコード間通信の目的だけでなく、Parcel.cppのストレージとしても見ることができます。プロセス間通信にはシリアライズが必要ですが、Javaで実装されているSerializableでは当然解決できません。Parcel.cppのおかげで、Parcelableはこの状況を利用して、Intentの顔など、シーンのより高いパフォーマンス要件に対処することができます。

Parcel.cpp は

platform/frameworks/native/libs/bilder/Parcel.cpp

Parcel.cppを理解するためには、以下のメンバ変数を理解する必要があります。

 uint8_t* mData; //キャッシュされたデータの先頭アドレス
 size_t mDataSize; //現在のデータサイズ
 size_t mDataCapacity; //総データサイズ
 mutable size_t mDataPos; //次のデータの先頭アドレス

図は次のようになります。

言い換えると、Parcel.cppは実際にはデータを格納するための連続したメモリ領域を提供し、Parcel.cppはデータを書き込むための多くのメンバ関数を提供し、書き込み操作のほとんどは、writeAligned()を呼び出します。
template<class T>
status_t Parcel::writeAligned(T val) {
 COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
 if ((mDataPos+sizeof(val)) <= mDataCapacity) {
 //現在の容量がまだ新しいデータに対応できるかどうかを計算する。
restart_write:
 // mData+mDataPosアドレスはポインタT*,値はvalである
 *reinterpret_cast<T*>(mData+mDataPos) = val;
 // オフセット・アドレス、つまりmDataPosを変更する
 return finishWrite(sizeof(val));
 }
 
 // 現在の容量では足りない。
 status_t err = growData(sizeof(val));
 // 容量アップに成功、再スタートへジャンプ_write 
 if (err == NO_ERROR) goto restart_write;
 // NO_ERROR 期待通りに実行される関数を表す
 return err;
}

上の図と組み合わせると、Parcel.cppは実質的に、さまざまな型のオブジェクトを次々と格納していることになります。したがって、Parcel.cppを保存に使用する場合は、どの場所に、どのような種類のデータが保存されているのかを明確にしなければならないというルールがありますが、これは後付けです。もちろん、Parcel.cppには、mDataPosの後の記憶領域にmemcpy()を介して、データを書き込むためのwrite()などのメソッドも用意されています。

関数 readAligned() に対応する、書き込み、読み込みもあります。

template<class T>
status_t Parcel::readAligned(T *pArg) const {
 COMPILE_TIME_ASSERT_FUNCTION_SCOPE(PAD_SIZE_UNSAFE(sizeof(T)) == sizeof(T));
 if ((mDataPos+sizeof(T)) <= mDataSize) {
 //読み込むデータが範囲外かどうかをチェックする
 if (mObjectsSize > 0) {
 // データが正しく読み込めるかチェックする
 status_t err = validateReadData(mDataPos + sizeof(T));
 if(err != NO_ERROR) {
 // この領域のデータは正しく読み取れないが、オフセット値はまだ修正されているはず mDataPos+= sizeof(T);
 return err;
 }
 }
 // 読み込むデータの物理アドレス
 const void* data = mData+mDataPos;
 // オフセット・アドレスを更新する
 mDataPos += sizeof(T);
 // dataParcelableオブジェクトのデータ型はTで、pArgはそれを指す。
 *pArg = *reinterpret_cast<const T*>(data);
 return NO_ERROR;
 } else {
 return NOT_ENOUGH_DATA;
 }
}

Parcel.cppは、記憶領域の内容に関して多くの制限を設けていません。必要であれば、Parcel.cppを使用して独自のシリアライズ・メソッドを実装し、Parcelableの利点を享受することができます。

Java側では、もちろん、開発者が直接Parcel.cppを操作することはできません。 その区別を容易にするために、以降のParcelの内容では、その名前がC++であるように、ParcelのJava側、Parcel.cppを指します。

Parcelは、JNIを通じて、Parcel.cppとの接続を確立し、対応する操作を実行するのに十分なNativeメソッドを定義しており、そのファイルは次の場所にあります:

frameworks/base/core/jni/android_os_Parcel.cpp

Parcelのインスタンス化では、NativeメソッドnativeCreate()を介してハンドル値のParcel.cppインスタンスを取得することができ、Parcelは、さまざまな操作を行うには、このハンドルをParcel.cppを操作する必要があります。ハンドルはParcelのメンバ変数mNativePtrに格納されています。

一般的に、Parcelとの接触が非常に少ない、Parcel.cppによってもたらされる利点を活用することができます、Parcelableを介して経験することができます。 Parcelableは、Parcelベースのシリアライゼーションスキームのセットを実装するシステムとして解釈することができます必要な場所に加えて、Parcelについて知っておく必要性をブロックします。

実際、Parcelの利用はかなり自由です。Parcelベースの一連のシリアライゼーション機構を実装する気があるのであれば、データの読み書きのルールや、オブジェクトに対応するデータの見つけ方など、シリアライゼーションプロセスにおけるいくつかの中間的な問題を解決することができます。Parcelはその他のシナリオにも使用できます。

Parcelable実装方法

Parcelを使用するには、Parcelableの実装が必要です:

  • writeToParcel() を実装し、シリアライズ時に書き出すデータを指定します。
  • Parcel をパラメータとするインスタンス化メソッドを実装し、デシリアライズ時に読み込むデータを指定します。
  • オブジェクト・インスタンスを作成するためのエントリ・ポイントを提供する、静的な最終メンバ変数 Creator を実装します。

アクティビティジャンプのプロセスからParcelの使い方を見るのも悪くありません。

データの書き込み

まず、Bundle.putParcelable()で追加のパラメータをIntentに渡すことができます。

 public void putParcelable(@Nullable String key, @Nullable Parcelable value) {
 // Parcelを解析し、ParcelのデータをmMapに入れる。
 unparcel();
 // K、VにParcelableを格納する
 mMap.put(key, value);
 mFlags &= ~FLAG_HAS_FDS_KNOWN;
 }

Bundleでは、追加データを格納するためにmMapが使用されます。unparcel()は、データを一様にmMapに入れ、その後に一様にシリアライズするために使用されます。

アクティビティ起動中に、AMSによってプロセス通信が完了し、その間にIntent.writeToParcel()が呼び出されて必要なデータがすべてシリアライズされ、転送が完了します。

public void writeToParcel(Parcel out, int flags) {
 ..... // Bundleの他にも、mAction、mIdentifier、mPackage、mComponentなどの情報がoutに書き込まれる!
 out.writeBundle(mExtras);
 }
 
-> Parcel.writeBundle()
-> Bundle.writeToParcel()
-> BaseBundle.writeToParcelInner()
 void writeToParcelInner(Parcel parcel, int flags) {
 if (parcel.hasReadWriteHelper()) {
 // ReadWriteHelperがある、デフォルトはReadWriteHelperだ.DEFAULT,
 // mMapにデータを押し込む
 unparcel();
 }
 
 final ArrayMap<String, Object> map;
 synchronized (this) {
 // 上記のunparcel()は、実際にmParcelladDataを取り出す。
 // ここに行くと、データは転送され、mParcelladDataに格納されている。
 // 言い換えれば、これと上記のif判定は互いに排他的であるべきだ。
 if (mParcelledData != null) {
 if (mParcelledData == NoImagePreloadHolder.EMPTY_PARCEL) {
 // データなし、フラグとしてIntに0を書き込む
 parcel.writeInt(0);
 } else {
 int length = mParcelledData.dataSize();
 parcel.writeInt(length);
 parcel.writeInt(mParcelledByNative ? BUNDLE_MAGIC_NATIVE : BUNDLE_MAGIC);
 // ParcelableはAndroidで最も強力なシリアライゼーション・メソッドなのだ。.cpp.appendFrom() mParcelledDataのデータ内容をparcelにコピーする
 parcel.appendFrom(mParcelledData, 0, length);
 }
 return;
 }
 map = mMap;
 }
 // データなし、フラグとしてIntに0を書き込む
 if (map == null || map.size() <= 0) {
 parcel.writeInt(0);
 return;
 }
 // 実際、Parcelableは.cpp.mDataPos 
 int lengthPos = parcel.dataPosition();
 // 一時的な占有ビットを書き込む
 parcel.writeInt(-1);
 // 魔法の数字を書く
 parcel.writeInt(BUNDLE_MAGIC);
 
 // Parcelable.cpp.mDataPos,しかし、今回はもう少し占拠する+ マジックナンバー ロングパスのオフセット
 int startPos = parcel.dataPosition();
 // マップに書き込む
 parcel.writeArrayMapInternal(map);
 // 結局のところ、Parcel.cpp.mDataPos 
 int endPos = parcel.dataPosition();
 // lengthPosに戻る
 parcel.setDataPosition(lengthPos);
 int length = endPos - startPos;
 // 長さ情報を前の位置に書き込む
 parcel.writeInt(length);
 // Parcelを復元する.cpp.mDataPos正しい場所へ
 parcel.setDataPosition(endPos);
 }

以上、注意が必要です:

  1. mMap と mParcelladData は競合関係にあり、データはどちらか一方の場所からしか預けられません。
  2. データは最終的にParcel.cppに書き込まれます。書き込まれたデータは、最初のデータがマジックナンバー+実データの長さになります。
 void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
 //
 final int N = val.size();
 // どれだけのデータを書き込むか
 writeInt(N);
 int startPos;
 for (int i=0; i<N; i++) {
 if (DEBUG_ARRAY_MAP) startPos = dataPosition();
 // キーを書く
 writeString(val.keyAt(i));
 // オブジェクトを書く
 writeValue(val.valueAt(i));
 //
 }
 }
 
public final void writeValue(@Nullable Object v) {
 //このメソッドは、vのタイプによって異なる書き込みを行い、それ以外は省略する。
 ......
 else if (v instanceof Parcelable) {
 // VAL_PARCELABLE Pacelable型としてマークする
 writeInt(VAL_PARCELABLE);
 writeParcelable((Parcelable) v, 0);
 }
 ......
}
 public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
 if (p == null) {
 writeString(null);
 return;
 }
 // クラス名はこう書く
 writeParcelableCreator(p);
 // オブジェクトクラスが実装しているwriteToParcel()メソッドを呼び出す。
 p.writeToParcel(this, parcelableFlags);
 }

シリアライズ後、インテント内のParcelはこのように格納されます:

左と右の部分はActivity起動時に書き込まれたその他の情報、長さはバンドルデータの長さ、マジックはラベリング、Nはデータ量、そしてK/Vが連続し、オブジェクトタイプを示すIntが入っています。

valueの書き込みは、オブジェクトの実装クラスがwriteToParcel()を実装することに完全に委ねられています。Parcelable.writeToParcel()でデータを書き込むと、最終的にParcel.writeValue()に来るので、valueもフォーマットを崩し続けることができます。

データの読み出し

データの取得はBundle.getParcelable()で行います。

 public <T extends Parcelable> T getParcelable(@Nullable String key) {
 unparcel();
 Object o = mMap.get(key);
 if (o == null) {
 return null;
 }
 try {
 return (T) o;
 } 
 .....
}
-> BaseBundle.unparcel()
-> BaseBundle.initializeFromParcelLocked()
-> Parcel.readArrayMapInternal()
 void readArrayMapInternal(@NonNull ArrayMap outVal, int N,
 @Nullable ClassLoader loader) {
 // N ParcelableオブジェクトがParcelLocked()を通じてシリアライズされるのは、その前のステップであるinitializeFromParcelLocked()だ。.readInt() 
 ......
 // データの開始位置
 int startPos;
 while (N > 0) {
 if (DEBUG_ARRAY_MAP) startPos = dataPosition();
 // key
 String key = readString();
 // 値を読む。値にはintとデータ情報が含まれる。
 Object value = readValue(loader);
 ......
 // mMapに格納される
 outVal.append(key, value);
 N--;
 }
 outVal.validate();
 }

Parcel.cppからBundle.mMapにデータを取り出したら、K/Vを使って特定のオブジェクトを取得します。シリアライズでは、Parcel.writeValue()でオブジェクトを型ごとに書き出し、デシリアライズでは、Parcel.readValue()でオブジェクトを型ごとに読み出します。

-> Parcel.readValue()
-> Parcel.readParcelable()
 public final <T extends Parcelable> T readParcelable(@Nullable ClassLoader loader) {
 // オブジェクト宣言のpublic static final Creatorにたどり着く<Phone> CREATOR
 Parcelable.Creator<?> creator = readParcelableCreator(loader);
 if (creator == null) {
 return null;
 }
 if (creator instanceof Parcelable.ClassLoaderCreator<?>) {
 Parcelable.ClassLoaderCreator<?> classLoaderCreator =
 (Parcelable.ClassLoaderCreator<?>) creator;
 // createFromParcel()を実行してオブジェクトを作成し、オブジェクトのデータを
 return (T) classLoaderCreator.createFromParcel(this, loader);
 }
 
 return (T) creator.createFromParcel(this);
 }

readParcelableCreator()のコードは掲載しませんが、内容はリフレクションを使用してオブジェクトクラスの次の宣言を取得することです。

public static final Creator <xxx> CREATOR

このコードは、なぜCREATORが宣言されるのかという問題を解決し、Parcelableがどのようにデシリアライズされるのかも説明しています。結合されたコンテンツは、シリアライズでは、オブジェクトは、タイミングを実行する必要がありますwriteToParcel()の実装は、独自のデータを書き込む方法を決定するために、デシリアライズでは、CREATORコールを介してParcelパラメータを持つインスタンス化は、データのデシリアライズを埋める方法を決定します。

シリアライズとデシリアライズの実装では、オブジェクトクラスは、一貫性を維持するために、データを読み書きする理由は難しいことではありません。しかし、これは絶対ではありません。

とはいえ、Parcelの使用は非常に柔軟です。データの読み取りと書き込みの一貫性を保つためには、オブジェクトが内容を十分に制御できることも必要です。システムアクティビティ起動プロセスの助けを借りてParcelの変化の内容を参照してくださいには、有用なコンテンツの分布の内容を書き込む方法の問題を解決する必要性を見ることは困難ではない任意の分解を行うことはありません。したがって、シリアライズの際に特別な情報を書いておき、デシリアライズの前に取り出すことができます。Parcelでシリアライズする場合、ヘルパー情報やデータ情報を含め、すべての情報はメモリの連続した領域に格納されます。デシリアライズは、ヘルパー情報の助けを借りてデータ情報をいかに見つけるかという問題になります。Activityのスタート地点にとどまらず、Parcelを使って他のシナリオに対応する一連のシリアライズを実装しようとすれば、当然、情報の分散方法やデータ情報の見つけ方などを検討しなければなりません。

必要であれば、Parcelableを実装せず、Parcelを使って他の機能を提供することもできますし、Parcelをシリアライズの中核として実装することもできますが、ただ面倒なだけです。

まとめ

Parcelシリアライゼーションの原則をまとめると、次のようになります:

  • Parcelがインスタンス化されると、Parcel.cppのインスタンスへのハンドルを取得します。
  • Parcelがデータをシリアライズするときは、I-Vという形式をとります。
  • シリアライズされるオブジェクトは Parcelable を実装しており、シリアライズ中に writeToParcel() が実行され、オブジェクトがデータの書き込みを行います。デシリアライズ中に CREATOR が呼び出され、オブジェクトのコンストラクタが呼び出されます。

Parcelable VS Serializable

次のようなシリアライズ・オブジェクトを用意してください:

public class Phone implements Parcelable, Serializable {
 private String brand;
 private int price;
 public Phone(String brand, int price) {
 this.brand = brand;
 this.price = price;
 }
 protected Phone(Parcel in) {
 // これはテスト用のクラス名だ。
 in.readString();
 brand = in.readString();
 price = in.readInt();
 }
 public static final Creator<Phone> CREATOR = new Creator<Phone>() {
 @Override
 public Phone createFromParcel(Parcel in) {
 return new Phone(in);
 }
 @Override
 public Phone[] newArray(int size) {
 return new Phone[size];
 }
 };
 @Override
 public int describeContents() {
 return 0;
 }
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(brand);
 dest.writeInt(price);
 }

次に、2つのアプローチのシリアライズとデシリアライズの時間を計算するテスト例を示します。

 private void doParcalable() {
 long writeStart;
 long writeEnd;
 long readStart;
 long readEnd;
 Parcel parcel;
 int dataStartPos;
 Phone curPhone;
 parcel = Parcel.obtain();
 curPhone = createPhone();
 writeStart = System.nanoTime();
 dataStartPos = parcel.dataPosition();
 parcel.writeParcelable(curPhone, 0);
 writeEnd = System.nanoTime();
 int length = parcel.marshall().length;
 parcel.setDataPosition(dataStartPos);
 readStart = System.nanoTime();
 Phone.CREATOR.createFromParcel(parcel);
 readEnd = System.nanoTime();
 Log.d(TAG, "parcel: " +
 (writeEnd - writeStart) / 1_000 + " ; unparcel: " +
 (readEnd - readStart) / 1_000 +
 " ; Size: " + length);
 }
 
 private void doSerializable() {
 long writeStart;
 long writeEnd;
 long readStart;
 long readEnd;
 ByteArrayOutputStream dataOut;
 ByteArrayInputStream dataIn;
 try {
 ObjectOutputStream out;
 ObjectInputStream in;
 dataOut = new ByteArrayOutputStream();
 out = new ObjectOutputStream(dataOut);
 Phone phone = createPhone();
 writeStart = System.nanoTime();
 out.writeObject(phone);
 writeEnd = System.nanoTime();
 out.flush();
 byte[] data = dataOut.toByteArray();
 int lenght = data.length;
 dataIn = new ByteArrayInputStream(data);
 readStart = System.nanoTime();
 in = new ObjectInputStream(dataIn);
 in.readObject();
 readEnd = System.nanoTime();
 Log.d(TAG, "Serialiazable: " + (writeEnd - writeStart) / 1_000
 + " ; unparcel: " + (readEnd - readStart) / 1_000
 + "  ; Size: " + lenght);
 } catch (IOException e) {
 e.printStackTrace();
 } catch (ClassNotFoundException e) {
 e.printStackTrace();
 }
 }

実行結果は

実行結果から、ParcelableはSerializableよりも高速です。時間に影響する要因としては

  1. 中間プロセス。Serializableの原則によると、シリアライズとデシリアライズのプロセスでは、データを取得したり保存したりするために、多くの中間変数を作成しなければなりません。一方、Parcelableはその必要がなく、必要なさまざまなデータを直接Parcel.cppに書き込みます。
  2. リフレクション: Serializableは多くのリフレクションを使用し、リフレクションには時間がかかります。Parcelableはエントリーポイントを取得するためにリフレクションをほとんど使用せず、データはオブジェクトによって読み込まれ書き出されるため、Serializableでデータを取得するために必要なリフレクションのほとんどが省略されます。
  3. 格納メソッド。つまり、データが格納される場所、データそのものやヘルパー情報だけでなく

Serializableは、writeObject()とreadObject()を実装することで、独自のオブジェクト書き込み処理を定義できることを考慮して、例に以下を追加します。

 //Phone
 private void writeObject(java.io.ObjectOutputStream out) throws IOException {
 out.writeUTF(brand);
 out.writeInt(price);
 }
 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
 brand = in.readUTF();
 price = in.readInt();
 }

再度プログラムを実行すると、次のような結果が得られます。

Serialiazableは確かに高速で、Serialiazableの原理を考え、どのように書き出すかを計画することで、情報を取得する過程の一部を反映する必要性を減らし、属性が多いときほど、その効果は顕著です。しかし、Parcelableに匹敵するところまでは至っていません。

ここで比較は終わりかと思いきや、 stackoverflowこんな質問がありました。

平均的なAndroidデバイスでの通常のJavaシリアライゼーションは、書き込みでParcelableの約3.6倍、読み込みで約1.6倍高速です。 また、これはJavaシリアライゼーションが高速なストレージ・メカニズムであることを証明しています。余談ですが、「Parcelableの方が圧倒的に速い」と盲目的に言う人はたいてい、デフォルトの自動シリアライズと比較しています。副次的なこととして、「Parcelableの方が速い」と盲目的に言う人はたいてい、デフォルトの自動シリアライズと比較します。通常言及されないのは、ドキュメントによると、標準のJava Serializableは、ストリームを書き込むためにwriteObjectを使用して、手動で行うこともできるということです。通常言及されていないのは、ドキュメントによると、標準的なJava Serializableは、writeObject()メソッドとreadObject()メソッドを使用して、手動で行うこともできるということです。 詳細については、JavaDocsを参照してください。 thisは、最良のパフォーマンスを得るための方法です。

質問者の証明コード・リポジトリ

この例は次のようなもので、大きなオブジェクト・チャートを形成できるオブジェクト・タイプを構築します。

public class TreeNode implements Serializable, Parcelable {
 private static final long serialVersionUID = 1L;
 public List<TreeNode> children;
 public String string0;
 public String string1;
 public String string2;
 public int int0;
 public int int1;
 public int int2;
 public boolean boolean0;
 public boolean boolean1;
 public boolean boolean2;
 public TreeNode() {
 }
 ......
 // ここではシリアライズのコードを省略する
}

このオブジェクトは木のノードを表します。そして、深さ5、葉ノードを除くノードあたり10文字の木を構築します。

 private TreeNode createNode(int level) {
 if (level < 4) {
 return createRootNode(level + 1);
 } else {
 return createSimpleNode();
 }
 }
 private TreeNode createRootNode(int level) {
 TreeNode root = createSimpleNode();
 root.children = new ArrayList<TreeNode>(10);
 for (int i = 0; i < 10; i++) {
 root.children.add(createNode(level));
 }
 return root;
 }
 private TreeNode createSimpleNode() {
 TreeNode root = new TreeNode();
 root.string0 = "aaaaaaaaaa";
 root.string1 = "bbbbbbbbbb";
 root.string2 = "cccccccccc";
 root.int0 = ;
 root.int1 = ;
 root.int2 = ;
 root.boolean0 = true;
 root.boolean1 = false;
 root.boolean2 = true;
 return root;
 }

そして、このオブジェクトをシリアライズするプログラムを実行すると、結果は次のようになります:

シリアライズの結果は3.6倍以上、デシリアライズの結果は1.6倍までとはいきませんが、Serializableの方が速いという驚きの結果です。

なぜでしょうか?その質問に対する回答には正確な答えが見つかりませんでした。分析してみたいと思います。

上記の出力から、parcelの方法ではシリアライズ後にserializeの方法よりも多くのバイト情報が生成され、その余分な部分はserializeの方法で生成されたすべてのバイト情報よりもさらに多くなっています。バイトの書き込みと読み出しは必然的に時間に影響し、データが多ければ多いほど処理に時間がかかることは理解できなくもありません。では、その余分なデータはどこから来るのでしょうか?

Serilazable

Serilazableの実装では、キャッシュの概念があり、オブジェクトが解析されると、それはHandleTableにキャッシュされ、次に同じタイプのオブジェクトが解析されたとき、それはバイナリストリームに書き込むことができ、対応するキャッシュインデックスをすることができます。しかし、Parcelにはそのような概念はありません。各シリアライゼーションは独立しており、各オブジェクトは新しいオブジェクトとして扱われ、新しいタイプの処理方法となります。

つまり、上記の結果の違いは、ヘルパー情報の違いにあります。有効な値が占有しなければならない記憶領域を除けば。

TreeNodeオブジェクトが直列化され、次のTreeNodeが再び直列化されるとどうなるでしょうか。

  • Serilazableの場合、1バイトのデータ型ラベル + 2バイトのオブジェクト・タイプ・キャッシュ・インデックス位置 + 1バイトの新しいオブジェクト・マーキングが必要です。
  • Parcel の場合:2 バイト * クラス名の長さ + タイプ・マーカの 4 バイト * 差分のないエントリあたりの属性数

Serilazableは、ある型に対して初めてシリアライズする際には、より多くのヘルパー情報を必要としますが、既にシリアライズされたオブジェクトに対して再度シリアライズする際には、より少ない情報しか必要としません。

Serilazableは、有効な値のリテラルが発生した場合、さらに多くのスペースを必要とします。そのため、オブジェクトの非常に大きなチャートを直列化するとき、そしてオブジェクトのほとんどが同じ型であるとき、Parcelはより多くのヘルパー情報を必要とし、その結果、書き込みと読み取りに必要なデータのバイト数が増えます。

結局のところ、メモリ内でのデータのやり取りは、他のデバイスでのI/Oのやり取りと同じ桁のパフォーマンスではありません。一方、直列化プロセスは、直感的に、それが生成する情報量によって効率にも影響を与えます。例では、両者が直面する他の要因を可能な限り同じようなレベルに引き寄せて比較する方が公平です。

まとめ

Parcelの概要について少しまとめましたが、ここでは結論として両者のシリアル化について比較します。

Parcel

  1. オブジェクトが単独でデータを書き込んだり読み込んだりできるようにするための、入口メソッドと出口メソッドがあります。
  2. ユーザーによるニーモニック情報の追加が必要
  3. 連続したメモリ空間に情報を格納
  4. 一般にJava側では、情報はI-V方式で格納されます
  5. サブクラスは必ずしも

シリアライズ可能。

  1. リフレクションによって完全な情報を取得し、オブジェクトの説明やプロパティの説明を解析します。
  2. 多くの中間変数を生成し、説明のキャッシュがあるときに再利用できます。
  3. エントリメソッドがあり、実装するとParcelのようにオブジェクトに読み書きするデータを決定させることができます。
  4. ストレージ・メソッドのIO依存性
  5. サブクラスは

答えてください。

Parcelable 実装方法

オブジェクトが単独でデータを書き込んだり読み込んだりできるようにするための入室メソッドと退室メソッドがあります。一般的には、Parcelパラメータを持つインスタンス化関数であるwriteToParcel()を実装し、Parcelable.Creator型の静的メンバ変数CREATORを宣言する必要があります。 データは、Parcel.cppが書き込み可能な連続したメモリ領域を取得して格納します。Parcelでは、このメモリ領域に必要な情報を書き込むことができます。オブジェクトに格納されるデータは通常、I(ラベル付きオブジェクト・タイプ)-V(実際のデータ)として格納されます。デシリアライズの際、CREATORを介してオブジェクトの初期化関数にアクセスし、データを投入することができます。

シリアライズとデシリアライズを同じ順序で行う理由

オブジェクトへのデータの書き込みとオブジェクトからのデータの読み出しは、すべてオブジェクト自身に任されているため、「オブジェクトは自分が扱っているParcelの中身を知っている」というルールがあります。順番を守ることは必要ですが、絶対ではありません。なぜなら、オブジェクトは自分のデータがどこにあるかを知っているので、それを使うか使わないか、あるいはどのように使うかを決めることができるからです。

Parcelを自分で実装できますか?

はい、できます。Parcelをベースとした一連のシリアライゼーションを実装することは可能ですが、提供されている出入口メソッドを使わないのであれば、実装が面倒なだけです。

サブクラスが Parcelable を実装する必要があるかどうか

サブクラスが情報を必要としない場合、オブジェクトが独自のデータを読み書きすることを決定するため、状況に依存し、関連するコードのサブクラスに呼び出す必要はありません。

Parcelableは本当にSerializableよりも高速ですか?

Serializableは本当にオーバーヘッドが多いのでしょうか?

Read next

React Hooksの理由

これはコンポーネントを作成するためのシンプルで効果的な方法で、当時はクラスシステムがなかったため、最初に使用されました。 React v0.13.0で導入され、コンポーネントをクラスで作成できるようになりました。 クラスでコンポーネントを作成する場合、...

Oct 16, 2020 · 5 min read