解決Xamarin.Android繫結第三方庫時型別丟失的問題(二)
在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。
其他的問題的原因和解決方案與上述大同小異,這裡就不再贅述了,希望本文能對你有幫助。