Xamarin.AndroidでISerializableする話

結論=>無意味。JSONあたりにシリアライズして文字列としてやり取りしよう。

以下結論に至るまでことの顛末を記す。
Androidな開発でクラスをシリアライズすることは多々ある。Activityの遷移とか保存とかでBundkeにシリアライズして受け渡したりとか。そこでクラスを作る。Javaのシリアライザを使いたいのでJava.Lang.Objectから派生しよう。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable { }

ところがこれだと復元時に例外が発生し、復元されない。

E/AndroidRuntime(15922): FATAL EXCEPTION: main
E/AndroidRuntime(15922): Process: App1.App1, PID: 15922
E/AndroidRuntime(15922): android.runtime.JavaProxyThrowable: System.NotSupportedException: Unable to activate instance of type App1.Moc from native handle 0x1d (key_handle 0x2bc79163). ---> System.MissingMethodException: No constructor found for App1.Moc::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership) ---> Java.Interop.JavaLocationException: Exception of type 'Java.Interop.JavaLocationException' was thrown.

原因はXamarinがインスタンスの復旧処理を行う場合(IntPtr, Android.Runtime.JniHandleOwnership)なコンストラクタを介して行うからだそうだ。つまり下記で回避できる。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable {
    public JavaSerializable() { }
    public JavaSerializable(
        IntPtr handle,
        Android.Runtime.JniHandleOwnership transfer
    ) : base(handle, transfer) { }
}


しかしこれでエラーは出なくなるもののよくよく見るとメンバが規定値で復元されていない。調べてみるとJavaのISerializableはコンパイラがreadObject, writeObjectメソッドを生成して処理するけどXamarinはそんなことしてくれないので自分で作って保存復元処理を書く必要がある。それを踏まえてついでにコードの再利用を鑑みると下の感じに。

class JavaSerializable : Java.Lang.Object, Java.IO.ISerializable {
    public JavaSerializable() { }
    public JavaSerializable(
        IntPtr handle,
        Android.Runtime.JniHandleOwnership transfer
    ) : base(handle, transfer) { }

    [Export("readObject", Throws = new[] {
        typeof (Java.IO.IOException),
        typeof (Java.Lang.ClassNotFoundException)})]
    private void __ReadObject(Java.IO.ObjectInputStream source) {
        var byteArray = new byte[source.Available()];
        source.Read(byteArray);
        using (var memStream = new MemoryStream()) {
            var binForm = new BinaryFormatter();
            memStream.Write(byteArray, 0, byteArray.Length);
            memStream.Seek(0, SeekOrigin.Begin);
            var obj = binForm.Deserialize(memStream);
            this.Obj = obj;
        }
    }

    [Export("writeObject", Throws = new[] {
        typeof (Java.IO.IOException),
        typeof (Java.Lang.ClassNotFoundException)})]
    private void __WriteObject(Java.IO.ObjectOutputStream destination) {
        var bf = new BinaryFormatter();
        using (var ms = new MemoryStream()) {
            bf.Serialize(ms, this.Obj);

            destination.Write(ms.ToArray());
        }
    }

    public object Obj { get; set; }
}


C#だと*thisの差し替えなんてことはできないので折衷案としてプロパティObjに保存したいクラスを格納して~といった感じになるなった。しかしObjがobject型なのはキモい。ジェネリックを使ってタイプセーフにしたいところである。が、ISerializableなクラスをジェネリッククラスにすると復元時にジェネリックパラメータが不明なので復元できない。困った。

さて途中でお気づきだと思うが自分でbyteにシリアライズ、デシリアライズするならクラス化の必要なくbyteでBndleにputすればよい気がするし、今時ならJSONシリアライズしてやりとりしたほうが絶対再利用性が高い。

という感じで結論に至る。