9

Parcelable は、Serializable のように循環参照を適切に処理していないようです。次の例では、Bar のシリアル化は正常に機能しますが、Parcel に書き込むとスタック オーバーフローが発生します。

I/TestRunner( 1571): java.lang.StackOverflowError
I/TestRunner( 1571):    at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571):    at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571):    at com.XXX.util.ParcelableTest$Bar.writeToParcel(ParcelableTest.java:209)
I/TestRunner( 1571):    at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571):    at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571):    at com.XXX.util.ParcelableTest$Baz.writeToParcel(ParcelableTest.java:246)
I/TestRunner( 1571):    at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571):    at android.os.Parcel.writeValue(Parcel.java:1029)
I/TestRunner( 1571):    at com.XXX.util.ParcelableTest$Bar.writeToParcel(ParcelableTest.java:209)
I/TestRunner( 1571):    at android.os.Parcel.writeParcelable(Parcel.java:1106)
I/TestRunner( 1571):    at android.os.Parcel.writeValue(Parcel.java:1029)


public void testCircular() throws Exception {

    final Bar bar = new Bar();
    final Baz baz = new Baz(bar);
    bar.baz = baz;

    // First, serialize
    final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
    new ObjectOutputStream(bytes).writeObject(bar);
    final ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytes.toByteArray());
    final Bar bar2 = (Bar) new ObjectInputStream(bytesIn).readObject();

    assertNotNull(bar2);
    assertNotNull(bar2.baz);
    assertEquals( bar2, bar2.baz.bar );


    // Now try same thing using parcelable
    final Parcel p = Parcel.obtain();
    p.writeValue(bar); // FAIL!  StackOverflowError
    p.setDataPosition(0);
    final Bar bar3 = (Bar) p.readValue(Bar.class.getClassLoader());

    assertNotNull(bar3);
    assertNotNull(bar3.baz);
    assertEquals( bar3, bar3.baz.bar );

}


protected static class Bar implements Parcelable, Serializable {
    private static final long serialVersionUID = 1L;
    public static final Parcelable.Creator<Bar> CREATOR = new Parcelable.Creator<Bar>() {
        public Bar createFromParcel(Parcel source) {
            final Bar f = new Bar();
            f.baz = (Baz) source.readValue(Bar.class.getClassLoader());
            return f;
        }

        public Bar[] newArray(int size) {
            throw new UnsupportedOperationException();
        }

    };


    public Baz baz;

    public Bar() {
    }

    public Bar( Baz baz ) {
        this.baz = baz;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int ignored) {
        dest.writeValue(baz);
    }


}


protected static class Baz implements Parcelable, Serializable {
    private static final long serialVersionUID = 1L;
    public static final Parcelable.Creator<Baz> CREATOR = new Parcelable.Creator<Baz>() {
        public Baz createFromParcel(Parcel source) {
            final Baz f = new Baz();
            f.bar = (Bar) source.readValue(Baz.class.getClassLoader());
            return f;
        }

        public Baz[] newArray(int size) {
            throw new UnsupportedOperationException();
        }

    };


    public Bar bar;

    public Baz() {
    }

    public Baz( Bar bar ) {
        this.bar = bar;
    }

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel dest, int ignored) {
        dest.writeValue(bar);
    }


}

Serializable を使用するコードから、循環参照を使用する Parcelable にコードを移植しようとしています。これを Parcelable で処理するための適切な戦略はありますか?

4

2 に答える 2

1

おそらく答えは、よりインテリジェントな一連の writeToParcel メソッドと createFromParcel メソッドにありますか?

思いつきで、特定のパーセルに既に完全に書き込んだオブジェクトのリストを保持し、タグ (ローカルの identityHashCode() など) だけでそれらを識別することができました。(これはグローバル リストではなく、明示的にパーセルごとであることに注意してください。おそらく、それ自体がセミグローバル を介して保存さMap<Parcel,Set<Integer> >れます。パーセルが完全に書き込まれたら、セットが忘れられていることを確認する必要があります。)

関連するビットは次のwriteToParcel()ようになります。

HashSet<Integer> set = getWrittenSetFor(dest);
final int tag = identityHashCode();
if (set.contains(tag)) {
    // Already sent
    dest.writeInt(tag);
} else {
    set.put(tag);
    dest.writeInt(tag);
    dest.writeValue(this);
}

対応createFromParcel()するものはもう少し複雑です。

この方法には潜在的な問題があると思いますが、ここから始めます。identityHashCode()ここに書いたように、オブジェクトごとに異なることが保証されているかどうかに依存します。通常は 32 ビット JVM 上にあります (基礎となる C++ ポインターの値です)。プレーンhashCode()は価値があるかもしれません (おそらく入力情報を追加することで?)、またはおそらくある種のシリアル番号です。

別のオプションは、オブジェクトを に単純にシリアルbyte[]化し、それを に書き込むことかもしれませParcelんが、少し効率が悪いと思います...

于 2011-01-12T17:20:49.010 に答える
0

Java シリアライゼーションを使用します。ObjectOutputStream を使用して、クラスを拡張し、バイト配列に変換しますExternalizableParcelableこのバイト配列を反対側[1] [2]に渡し、 ObjectInputStream を使用してデシリアライズします。

Android Parcelables は非常に高速ですが、その速度は、シリアライゼーション フレームワークに伝統的に存在するすべての追加機能を犠牲にしてもたらされます。

Java シリアライゼーションは、強力で柔軟になるように設計されており、循環参照の処理など、多くのことをサポートしています。カスタムを宣言しserialVersionUID(実行時のリフレクション計算を避けるため)、readExternal/writeExternal で手動でクラスの内容を読み書きすると、Parcelable とほぼ同じパフォーマンスが得られます (循環参照などの追跡に「ほとんど」が費やされます)。 .

于 2016-12-16T08:00:21.193 に答える