Share via


對繫結進行疑難排解

重要

我們目前正在調查 Xamarin 平臺上的自定義系結使用方式。 請接受 這項調查 ,以通知未來的開發工作。

本文摘要說明產生系結時可能發生的伺服器常見錯誤,以及可能的原因和建議解決這些錯誤的方法。

概觀

系結 Android 連結庫 ( .aar.jar) 檔案很少是直接的事務;通常需要額外的努力來減輕 Java 與 .NET 之間差異所造成的問題。 這些問題會防止 Xamarin.Android 系結 Android 連結庫,並在組建記錄檔中呈現為錯誤訊息。 本指南將提供一些疑難解答問題的秘訣、列出一些較常見的問題/案例,並提供可成功系結Android連結庫的解決方案。

系結現有的 Android 連結庫時,必須記住下列幾點:

  • 連結庫的外部相依性 – Android 連結庫所需的任何 Java 相依性都必須包含在 Xamarin.Android 專案中,做為 ReferenceJar 或 EmbeddedReferenceJar

  • Android 連結庫的目標 Android API 層級 – 無法「降級」Android API 層級;請確定 Xamarin.Android 系結專案是以與 Android 連結庫相同的 API 層級(或更高層級)為目標。

  • 用來封裝 Android 連結庫的 Android JDK 版本 – 如果 Android 連結庫 建置的 JDK 版本與 Xamarin.Android 所使用的 JDK 版本不同,就可能發生系結錯誤。 可能的話,請使用安裝 Xamarin.Android 所使用的相同 JDK 版本重新編譯 Android 連結庫。

針對系結 Xamarin.Android 連結庫問題進行疑難解答的第一個步驟是啟用 診斷 MSBuild 輸出。 啟用診斷輸出之後,請重建 Xamarin.Android 系結專案,並檢查組建記錄檔,找出問題原因的線索。

它也可以證明有助於解譯 Android 連結庫,並檢查 Xamarin.Android 嘗試系結的類型和方法。 本指南稍後會詳細說明這一點。

反編譯 Android 連結庫

檢查 Java 類別的類別和方法,可以提供可協助系結連結庫的重要資訊。 JD-GUI 是圖形化公用程式,可從 JAR 中包含的 CLASS 檔案顯示 Java 原始程式碼。 它可以以獨立應用程式或 IntelliJ 或 Eclipse 的外掛程式的形式執行。

若要反編譯 Android 連結庫, 請開啟 。具有 Java 反編譯程式的 JAR 檔案。 如果連結庫是 。AAR 檔案必須從封存盤案擷取 檔案classes.jar 。 以下是使用 JD-GUI 分析 Picasso JAR 的範例螢幕快照:

Using the Java Decompiler to analyze picasso-2.5.2.jar

一旦您反編譯 Android 連結庫,請檢查原始程式碼。 一般而言,尋找 :

  • 具有模糊化 特性的類別 – 模糊化類別的特性包括:

    • 類別名稱包含 $,也就是 a$.class
    • 類別名稱完全遭到小寫字元入侵,亦即 a.class
  • import未參考連結庫的語句 – 識別未參考的連結庫,並使用 ReferenceJar 的建置動作EmbedddedReferenceJar,將這些相依性新增至 Xamarin.Android 系結專案

注意

根據當地法律或發佈 Java 連結庫的授權,可能會禁止或受限於法律限制。 如有必要,請先登記法律專業人員的服務,再嘗試將Java連結庫分解並檢查原始程式碼。

檢查API.XML

作為建置系結專案的一部分,Xamarin.Android 會產生 XML 檔名 obj/Debug/api.xml

Generated api.xml under obj/Debug

此檔案提供 Xamarin.Android 嘗試系結的所有 Java API 清單。 此檔案的內容可協助識別任何遺漏的類型或方法、重複的系結。 雖然此檔案的檢查很繁瑣且耗時,但它可以提供可能造成任何系結問題的線索。 例如, api.xml 可能會顯示屬性傳回不適當的類型,或有兩種類型共用相同的Managed名稱。

已知問題

本節將列出嘗試系結 Android 連結庫時所發生的一些常見錯誤訊息或徵兆。

問題:Java 版本不符

相較於連結庫的編譯方式,有時候不會產生類型,或可能發生非預期的當機,因為您使用的是較新的或較舊的 Java 版本。 使用與 Xamarin.Android 專案所使用的相同 JDK 版本重新編譯 Android 連結庫。

問題:至少需要一個 Java 連結庫

即使 有 ,您還是會收到「至少需要一個 Java 連結庫」錯誤。已新增 JAR。

可能的原因:

請確定建置動作已設定為 EmbeddedJar。 因為有多個建置動作。JAR 檔案 (例如 InputJarEmbeddedJarReferenceJarEmbeddedReferenceJar),系結產生器無法自動猜測預設要使用的檔案。 如需建置動作的詳細資訊,請參閱 建置動作

問題:系結工具無法載入 。JAR 連結庫

繫結連結庫產生器無法載入 。JAR 連結庫。

可能的原因

一些。Java 工具無法載入使用程式碼混淆的 JAR 連結庫(透過 Proguard 之類的工具)。 由於此工具會使用 Java 反映和 ASM 位元組程式代碼工程連結庫,因此這些相依工具可能會在 Android 執行時間工具通過時拒絕混淆的連結庫。 其因應措施是手動系結這些連結庫,而不是使用系結產生器。

問題:在產生的輸出中遺漏 C# 類型。

系結.dll組建,但遺漏某些 Java 類型,或產生的 C# 來源不會建置,因為發生錯誤,指出遺漏類型。

可能的原因:

此錯誤可能會因為下列幾個原因而發生:

  • 所系結的連結庫可能會參考第二個 Java 連結庫。 如果系結連結庫的公用 API 使用來自第二個連結庫的類型,您也必須參考第二個連結庫的 Managed 系結。

  • 因為 Java 反映而插入連結庫,這與上述連結庫載入錯誤的原因類似,導致未預期的元數據載入。 Xamarin.Android 的工具目前無法解決這種情況。 在這種情況下,連結庫必須手動系結。

  • .NET 4.0 運行時間發生錯誤,當元件應該有時,無法載入元件。 此問題已在 .NET 4.5 運行時間中修正。

  • Java 允許從非公用類別衍生公用類別,但這在 .NET 中不受支援。 由於系結產生器不會產生非公用類別的系結,因此無法正確產生這類衍生類別。 若要修正此問題,請使用 Metadata.xml 中的 remove-node 移除這些衍生類別的元數據專案,或修正讓非公用類別公開的元數據。 雖然後者的解決方案會建立系結,讓 C# 來源能夠建置,但不應該使用非公用類別。

    例如:

    <attr path="/api/package[@name='com.some.package']/class[@name='SomeClass']"
        name="visibility">public</attr>
    
  • 混淆 Java 連結庫的工具可能會干擾 Xamarin.Android 系結產生器及其產生 C# 包裝函式類別的能力。 下列代碼段示範如何將Metadata.xml更新為取消混淆類別名稱:

    <attr path="/api/package[@name='{package_name}']/class[@name='{name}']"
        name="obfuscated">false</attr>
    

問題:由於參數類型不相符,產生的 C# 來源不會建置

產生的 C# 來源不會建置。 覆寫方法的參數類型不符。

可能的原因:

Xamarin.Android 包含各種 Java 欄位,這些欄位會對應至 C# 系結中的列舉。 這些可能會導致產生的系結中類型不相容。 若要解決此問題,必須修改從系結產生器建立的方法簽章,才能使用列舉。 如需詳細資訊,請參閱 更正列舉

問題:封裝中的 NoClassDefFoundError

java.lang.NoClassDefFoundError 會在封裝步驟中擲回。

可能的原因:

此錯誤最有可能的原因是必須將必要的 Java 連結庫新增至應用程式專案 (.csproj)。 .JAR 檔案不會自動解析。 Java 連結庫系結不一定會針對目標裝置或模擬器中不存在的使用者元件產生(例如 Google 地圖 maps.jar)。 這不是 Android Library 項目支持的情況,因為連結庫是 。JAR 內嵌在連結庫 dll 中。

問題:重複自定義 EventArgs 類型

建置因為重複的自定義 EventArgs 類型而失敗。 發生如下的錯誤:

error CS0102: The type `Com.Google.Ads.Mediation.DismissScreenEventArgs' already contains a definition for `p0'

可能的原因:

這是因為事件類型之間有一些衝突,這些類型來自多個介面「接聽程式」類型,且共用具有相同名稱的方法。 例如,如果如下列範例所示有兩個 Java 介面,則產生器會針對 MediationBannerListenerMediationInterstitialListener建立 DismissScreenEventArgs ,因而產生錯誤。

// Java:
public interface MediationBannerListener {
    void onDismissScreen(MediationBannerAdapter p0);
}
public interface MediationInterstitialListener {
    void onDismissScreen(MediationInterstitialAdapter p0);
}

這是根據設計,避免事件自變數類型的冗長名稱。 若要避免這些衝突,需要一些元數據轉換。 編輯 Transforms\Metadata.xml,並在 其中一個 argsType 介面上新增屬性(或在介面方法上):

<attr path="/api/package[@name='com.google.ads.mediation']/
        interface[@name='MediationBannerListener']/method[@name='onDismissScreen']"
        name="argsType">BannerDismissScreenEventArgs</attr>

<attr path="/api/package[@name='com.google.ads.mediation']/
        interface[@name='MediationInterstitialListener']/method[@name='onDismissScreen']"
        name="argsType">IntersitionalDismissScreenEventArgs</attr>

<attr path="/api/package[@name='android.content']/
        interface[@name='DialogInterface.OnClickListener']"
        name="argsType">DialogClickEventArgs</attr>

問題:類別未實作介面方法

會產生錯誤訊息,指出產生的類別不會實作所產生類別實作介面所需的方法。 不過,查看產生的程序代碼,您可以看到已實作 方法。

以下是錯誤的範例:

obj\Debug\generated\src\Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.cs(8,23):
error CS0738: 'Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter' does not
implement interface member 'Oauth.Signpost.Http.IHttpRequest.Unwrap()'.
'Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.Unwrap()' cannot implement
'Oauth.Signpost.Http.IHttpRequest.Unwrap()' because it does not have the matching
return type of 'Java.Lang.Object'

可能的原因:

這是使用 covariant 傳回型別系結 Java 方法所發生的問題。 在這裡範例中,方法 Oauth.Signpost.Http.IHttpRequest.UnWrap() 必須傳回 Java.Lang.Object。 不過,方法 Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.UnWrap() 的傳回型別為 HttpURLConnection。 有兩種方式可以修正此問題:

  • 新增 的部分類別宣告 HttpURLConnectionRequestAdapter ,並明確實作 IHttpRequest.Unwrap()

    namespace Oauth.Signpost.Basic {
        partial class HttpURLConnectionRequestAdapter {
            Java.Lang.Object OauthSignpost.Http.IHttpRequest.Unwrap() {
                return Unwrap();
            }
        }
    }
    
  • 從產生的 C# 程式代碼中移除共變數。 這牽涉到將下列轉換新增至 Transforms\Metadata.xml ,這會導致產生的 C# 程式代碼具有 的 Java.Lang.Object傳回類型:

    <attr
        path="/api/package[@name='oauth.signpost.basic']/class[@name='HttpURLConnectionRequestAdapter']/method[@name='unwrap']"
        name="managedReturn">Java.Lang.Object
    </attr>
    

問題:內部類別/屬性的名稱衝突

繼承物件的可見度衝突。

在 Java 中,衍生類別的可見度與其父系不一樣。 Java 只會為您修正此問題。 在 C# 中,這必須明確,因此您必須確定階層中的所有類別都有適當的可見度。 下列範例示範如何將 Java 套件名稱從 com.evernote.android.job 變更為 Evernote.AndroidJob

<!-- Change the visibility of a class -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']" name="visibility">public</attr>

<!-- Change the visibility of a method -->
<attr path="/api/package[@name='namespace']/class[@name='ClassName']/method[@name='MethodName']" name="visibility">public</attr>

問題: 系結所需的 .so 連結庫未載入

某些系結專案也可能相依於 .so 連結庫中的功能。 Xamarin.Android 可能不會自動載入 .so 連結庫。 當包裝的 Java 程式代碼執行時,Xamarin.Android 將無法進行 JNI 呼叫,而且錯誤訊息 java.lang.UnsatisfiedLinkError:找不到原生方法: 會出現在應用程式的註銷中。

修正此問題的作業是手動載入 .so 連結庫,並呼叫 Java.Lang.JavaSystem.LoadLibrary。 例如,假設 Xamarin.Android 專案已使用 EmbeddedNativeLibrary 的建置動作包含在系結專案中的共用連結庫libpocketsphinx_jni.so,下列代碼段(在使用共用連結庫之前執行)會載入 .so 連結庫:

Java.Lang.JavaSystem.LoadLibrary("pocketsphinx_jni");

摘要

在本文中,我們列出與 Java 系結相關聯的常見疑難解答問題,並說明如何解決這些問題。