Xamarin.android 如何在Xamarin中序列化对Android可调用包装器的托管对等方的引用

Xamarin.android 如何在Xamarin中序列化对Android可调用包装器的托管对等方的引用,xamarin.android,Xamarin.android,我有一个本机Android活动,它接收一个回调接口,作为启动它的意图的一部分: 公共接口ICallback:可序列化 { 无效调用(结果); } 我想在Xamarin中将回调实现为lambda: class CallbackWrapper:Java.Lang.Object,ICallback { 私人行动禁止吸烟; 公用回调包装器(操作onInvoke) { this.onInvoke=onInvoke; } 公共void调用(结果) { 本.onInvoke(结果); } } ... Put

我有一个本机Android活动,它接收一个回调接口,作为启动它的意图的一部分:

公共接口ICallback:可序列化
{
无效调用(结果);
}
我想在Xamarin中将回调实现为lambda:

class CallbackWrapper:Java.Lang.Object,ICallback
{
私人行动禁止吸烟;
公用回调包装器(操作onInvoke)
{
this.onInvoke=onInvoke;
}
公共void调用(结果)
{
本.onInvoke(结果);
}
}
...
PutExtra(CALLBACK_EXTRA,newcallbackwrapper(result=>{…}));
StartActivityForResult(意向);
第一个问题是,当我的回调从intent bundle反序列化时,会出现以下异常:

System.NotSupportedException
Unable to activate instance of type CallbackWrapper from native handle 0xff...

System.MissingMethodException
No constructor found for CallbackWrapper::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
我添加了构造函数,如异常中所述:

class CallbackWrapper:Java.Lang.Object,ICallback
{
公共CallbackWrapper(IntPtr句柄,JniHandleOwnership transfer):base(句柄,transfer)
{
}
...
}

异常已修复,但现在当活动调用我的处理程序时,
onInvoke
字段为
null
。如何获取对用于创建意图的onInvoke委托的引用?

解决方案-序列化原始对象的句柄

第一步是启用对象序列化。Java中的序列化是使用特殊命名的私有方法完成的,而不是通过接口方法。Xamarin允许您使用
Java.Interop.ExportAttribute
属性将这些方法注入生成的Android可调用包装器:

使用Java.Interop;
类CallbackWrapper:Java.Lang.Object,ICallback
{
...
[Export(“readObject”,Throws=new[]{typeof(Java.IO.IOException),typeof(Java.Lang.ClassNotFoundException)}]
私有void ReadObject(Java.IO.ObjectInputStream源)
{
}
[Export(“writeObject”,Throws=new[]{typeof(Java.IO.IOException),typeof(Java.Lang.ClassNotFoundException)}]
私有void WriteObject(Java.IO.ObjectOutputStream目标)
{
}
}
即使ACW实现了Serializable,ACW本身也没有有用的字段——这就是为什么需要通过readObject/writeObject方法对序列化托管状态的原因

请注意,要使其正常工作,您的项目需要引用Mono.Android.Export程序集,否则将出现构建时错误

第二部分是获取对
CallbackWrapper
的可序列化引用。这可以通过使用
System.Runtime.InteropServices.GCHandle
实现。第一步是创建对象的句柄,并在序列化过程中写入该句柄:

[Export(“writeObject”,Throws=new[]{typeof(Java.IO.IOException),typeof(Java.Lang.ClassNotFoundException)}]
私有void WriteObject(Java.IO.ObjectOutputStream目标)
{
var handle=GCHandle.Alloc(this);
destination.WriteLong(GCHandle.ToIntPtr(handle.ToInt64());
}
第二步是反序列化:

[Export(“readObject”,Throws=new[]{typeof(Java.IO.IOException),typeof(Java.Lang.ClassNotFoundException)}]
私有void ReadObject(Java.IO.ObjectInputStream源)
{
//从流中反序列化GCHandle
var handle=GCHandle.fromtintptr(新的IntPtr(source.ReadLong());
//将句柄转换为对象
var trueSelf=handle.Target作为NativeValidationHandler;
//从原始回调复制字段
this.onInvoke=trueSelf.onInvoke;
//松开这个把手
handle.Free();
}
句柄不需要是固定的句柄,因为我们永远不会访问对象的地址,我们只是使用句柄


注意,在上面的实现中,您只能反序列化回调一次,因为反序列化将释放句柄。或者,如果希望能够多次反序列化句柄,可以在构造函数中分配一次句柄,并提供释放该句柄的Dispose方法。在反序列化过程中释放句柄还意味着如果对象从未反序列化,则将永远不会收集该对象,因为句柄将阻止收集该对象。

如果要使用Serializable,则不正确。但我建议你使用包裹,因为

  • Parcelable是Android sdk的一部分,主要用于包裹目的
  • Parcelable比Serializable快,因为它不使用反射,而后者使用反射
  • 虽然有缺点,它有一些样板代码

  • 值得一读=>

    注意,我不控制回调的类型。因此,使用Parcelable还是Serializable不是我的选择。但如果回调是可打包的,那么同样的方法,但这次使用的是
    ExportFieldAttribute
    ,也会起作用。