解决Xamarin.Android绑定第三方库时类型丢失的问题(二)

96
临岁之寒
0.4 2019.02.09 14:00 字数 1096

在Crasheye的SDK时,我再一次遇到了绑定问题,之前的问题请看解决Xamarin.Android绑定第三方库时类型丢失的问题(一),这一次出现的问题更多,也更棘手,其中几条查阅官方文档也没有发现解决方案。
问题是这样的:


下面我们一条一条来看,第一条问题报告如下:
CrashEyeTestPlus/obj/Debug/generated/src/Com.Xsj.Crasheye.Dao.Base.BaseDao.cs(29,29): Error CS0111: Type 'BaseDao' already defines a member called 'Delete' with the same parameter types (CS0111) (CrashEyeTestPlus)

查看问题源,发现文件中生成了两个一模一样的方法,连参数也是一样的:


    // Metadata.xml XPath method reference: path="/api/package[@name='com.xsj.crasheye.dao.base']/class[@name='BaseDao']/method[@name='delete' and count(parameter)=1 and parameter[1][@type='ID']]"
        [Register ("delete", "(Ljava/io/Serializable;)I", "GetDelete_Ljava_io_Serializable_Handler")]
        public virtual unsafe int Delete (global::Java.Lang.Object id)
        {......}

          ........

        // Metadata.xml XPath method reference: path="/api/package[@name='com.xsj.crasheye.dao.base']/class[@name='BaseDao']/method[@name='delete' and count(parameter)=1 and parameter[1][@type='T']]"
        [Register ("delete", "(Ljava/lang/Object;)I", "GetDelete_Ljava_lang_Object_Handler")]
        public virtual unsafe int Delete (global::Java.Lang.Object entity)
        {.......}

为了搞清楚怎么回事,我还是和上次一样,在AS里查看一下原生的接口是什么样子:

public abstract class BaseDao<T, ID extends Serializable> implements YoDao<T, ID> {
    .....
    public int delete(ID id) {
        return this.deleteByFields(this.whereClauseByPK(), this.whereArgsByPK(id));
    }

    public int delete(T entity) {
        int count = 0;
        if (entity != null) {
            ID id = this.getPK(entity);
            if (id != null) {
                count = this.delete(id);
            }
        }

        return count;
    }
    .....
}

可见T和ID都是泛型,C#对Java的泛型支持并不太好,所以Xamarin在封装的时候,把T和ID都当成了Object类型对待,因此导致两个方法封装成了一模一样的外观。为了令两个方法能相互区别,我将delete(ID id) 的参数类型修改为Serializable类型(ID本来就是一个Serializable)。在Metedata.xml文件中加入如下代码:
<attr path="/api/package[@name='com.xsj.crasheye.dao.base']/class[@name='BaseDao']/method[@name='delete' and count(parameter)=1 and parameter[1][@type='ID']]/parameter[1]" name="managedType">Java.IO.ISerializable</attr>

第二个问题是CrashEyeTestPlus/obj/Debug/generated/src/Com.Xsj.Crasheye.Dao.Base.IYoDao.cs(7,7): Error CS0111: Type 'IYoDao' already defines a member called 'Delete' with the same parameter types (CS0111) (CrashEyeTestPlus)

这个问题的原因和上面是一样的,BaseDao的delete方法就是对IYoDao接口的实现,

public interface YoDao<T, ID extends Serializable> {
      ......
    int delete(ID var1);
    int delete(T var1);
      .......
}

所以我们同样把delete(ID var1)的参数类型修改过来即可。在Metedata.xml文件中加入如下代码:

<attr path="/api/package[@name='com.xsj.crasheye.dao.base']/interface[@name='YoDao']/method[@name='delete' and count(parameter)=1 and parameter[1][@type='ID']]/parameter[1]" name="managedType">Java.IO.ISerializable</attr>

第三个问题是 CrashEyeTestPlus/obj/Debug/generated/src/Com.Xsj.Crasheye.Dao.Impl.SessionDaoImpl.cs(23,23): Error CS0534: 'SessionDaoImpl' does not implement inherited abstract member 'BaseDao.SetPK(Object, Object)' (CS0534) (CrashEyeTestPlus)
查看一下生成的SessionDaoImpl文件,能够看到

    public partial class SessionDaoImpl : global::Com.Xsj.Crasheye.Dao.Base.BaseDao, global::Com.Xsj.Crasheye.Dao.ISessionDao {

      ......
    public virtual unsafe global::Com.Xsj.Crasheye.Session.Session SetPK (global::Com.Xsj.Crasheye.Session.Session entity, global::Java.Lang.Long id){
      ......
     }
}

其中Com.Xsj.Crasheye.Dao.ISessionDao的原生实现是这样的:

public interface SessionDao extends YoDao<Session, Long> {
}

因此可知,在SessionDaoImpl下T和ID已经被声明为Session和Long,但Xamarin并不知道,他仍要求SessionDaoImpl要有一个BaseDao.SetPK(Object, Object)的实现,但BaseDao并不知道T和ID的具体类型是什么,所以我们只能修改SessionDaoImpl中的SetPK方法的参数类型以匹配BaseDao.SetPK(Object, Object)。在Metedata.xml文件中加入如下代码:

  <attr path="/api/package[@name='com.xsj.crasheye.dao.impl']/class[@name='SessionDaoImpl']/method[@name='setPK' and count(parameter)=2 and parameter[1][@type='com.xsj.crasheye.session.Session'] and parameter[2][@type='java.lang.Long']]/parameter[1]" name="managedType">Java.Lang.Object</attr>
    <attr path="/api/package[@name='com.xsj.crasheye.dao.impl']/class[@name='SessionDaoImpl']/method[@name='setPK' and count(parameter)=2 and parameter[1][@type='com.xsj.crasheye.session.Session'] and parameter[2][@type='java.lang.Long']]/parameter[2]" name="managedType">Java.Lang.Object</attr>

但问题还没有结束,CrashEyeTestPlus/obj/Debug/generated/src/Com.Xsj.Crasheye.Dao.Impl.SessionDaoImpl.cs(23,23): Error CS0534: 'SessionDaoImpl' does not implement inherited abstract member 'BaseDao.SetPK(Object, Object)' (CS0534) (CrashEyeTestPlus)仍然还在。再看一下生成的SessionDaoImpl文件,会发现SetPK的声明已经发生改变:public virtual unsafe global::Com.Xsj.Crasheye.Session.Session SetPK (global::Java.Lang.Object entity, global::Java.Lang.Object id)。问题出在哪里呢?问题出在virtual声明上。
根据C#的语法要求,SessionDaoImpl的SetPK方法要实现BaseDao.SetPK,除了方法名、参数和返回类型要一一匹配之外,方法声明里还要有override,现在是virtual自然是不对的。

首先要移除virtual声明,添加如下代码可以解决:<attr path="/api/package[@name='com.xsj.crasheye.dao.impl']/class[@name='SessionDaoImpl']/method[@name='setPK' and count(parameter)=2 and parameter[1][@type='com.xsj.crasheye.session.Session'] and parameter[2][@type='java.lang.Long']]" name="final">true</attr>,但仅仅是移除virtual是不够的,因为Java的成员方法默认是虚方法,都是可以重写的,而C#的成员方法则默认是非虚的,是不可重写的,因此override在这里是必须的。
但官方文档并没有给出这种情况应该如何解决,我经过一番摸索,得出了下述的解决方案:
<attr path="/api/package[@name='com.xsj.crasheye.dao.impl']/class[@name='SessionDaoImpl']/method[@name='setPK' and count(parameter)=2 and parameter[1][@type='com.xsj.crasheye.session.Session'] and parameter[2][@type='java.lang.Long']]" name="visibility">public override</attr>将override关键字写在可见性声明里居然也可以,可见Metadata.xml本质就是一个模板文件;
进行到这一步,问题又变化了CrashEyeTestPlus/obj/Debug/generated/src/Com.Xsj.Crasheye.Dao.Impl.SessionDaoImpl.cs(67,67): Error CS0508: 'SessionDaoImpl.SetPK(Object, Object)': return type must be 'Object' to match overridden member 'BaseDao.SetPK(Object, Object)' (CS0508) (CrashEyeTestPlus),SessionDaoImpl.SetPK方法的返回类型不匹配。这个简单,添加如下代码即可:
<attr path="/api/package[@name='com.xsj.crasheye.dao.impl']/class[@name='SessionDaoImpl']/method[@name='setPK' and count(parameter)=2 and parameter[1][@type='com.xsj.crasheye.session.Session'] and parameter[2][@type='java.lang.Long']]" name="managedReturn">Java.Lang.Object</attr>
至此,关于SessionDaoImpl.SetPK的问题才算完全解决,但显然,SessionDaoImpl.SetPK方法的所有泛型的类型信息都被擦除了,我们在调用的时需要小心才行,保险起见,我们可以再封装一下SessionDaoImpl。

其他的问题的原因和解决方案与上述大同小异,这里就不再赘述了,希望本文能对你有帮助。

Xamarin开发笔记
Web note ad 1