疑難解答系結

綁定 Android 程式庫(.aar.jar)檔案往往不是簡單的過程;通常需要額外的工作來減輕由 Java 和 .NET 之間差異所造成的問題。 這些問題會防止適用於 Android 的 .NET 系結 Android 連結庫,並在組建記錄檔中呈現為錯誤訊息。 本指南將提供一些疑難解答問題的秘訣、列出一些較常見的問題/案例,並提供可成功系結Android連結庫的解決方案。

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

  • 連結庫的外部相依性 – Android 連結庫 所需的任何 Java 相依性都必須透過 NuGet 套件或 AndroidLibrary 包含在適用於 Android 專案的 .NET 專案中。

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

提示

Binding Tooling GitHub 存放庫維基 是一個很好的資源,並包含其他疑難排解資訊,可協助解決特定問題情況。

解決 .NET for Android 函式庫綁定問題的第一步是啟用 診斷 MSBuild 輸出。 啟用診斷輸出之後,請重建適用於 Android 系結專案的 .NET,並檢查組建記錄檔,找出問題原因的線索。

它也可以證明有助於反編譯 Android 函式庫,並檢查 .NET for Android 嘗試綁定的類型和方法。 本指南稍後會詳細說明這一點。

反編譯 Android 程式庫

檢視 Java 類別及其方法可以提供有助於綁定函式庫的重要資訊。 JD-GUI 是圖形化公用程式,可從 JAR 中包含的 CLASS 檔案顯示 Java 原始程式碼

若要反編譯 Android 庫,請使用 Java 反編譯程式開啟 .JAR 檔案。 如果連結庫是 .AAR 檔案,Java 原始程式碼將會位於封存檔案中的 classes.jar 條目中。 以下是使用 JD-GUI 分析 Picasso JAR 的範例螢幕快照:

使用 Java Decompiler 分析picasso-2.5.2.jar

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

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

    • 類別名稱包含 $,也就是 a$.class
    • 類別名稱完全遭到小寫字元入侵,亦即 a.class
  • import未參考的連結庫的語句 – 識別未參考的連結庫,並使用來自 NuGet 的適當系結,或選擇 Build ActionAndroidLibrary,將這些相依性新增至 .NET for Android 系結專案。

注意

反編譯 Java 程式庫可能會根據當地法律或 Java 程式庫發佈時的授權條款被禁止或受到法律限制。 如有必要,在嘗試反編譯Java程式庫和檢查原始程式碼之前,請先尋求法律專業人士的服務。

檢查api.xml

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

在 obj/Debug 下產生api.xml

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

已知問題

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

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

繫結.dll 組建成功,但遺漏某些 Java 類型,或生成的 C# 代碼無法建置,因為發生錯誤,顯示有遺漏類型。

可能的原因:

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

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

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

    例如:

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

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

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

生成的 C# 原始碼無法編譯。 覆寫方法的參數類型不符。

可能的原因:

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

問題:重複自定義 EventArgs 類型

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

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

可能的原因:

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

// 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'

可能的原因:

這是因為使用共變傳回類型綁定 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 連結庫中的功能。 Android 版 .NET 可能不會自動載入 .so 連結庫。 當包裝的 Java 程式代碼執行時,Android 的 .NET 將無法進行 JNI 呼叫,而且錯誤訊息 java.lang.UnsatisfiedLinkError:找不到原生方法: 將會出現在應用程式的 logcat out 中。

修正此問題需要手動使用 Java.Lang.JavaSystem.LoadLibrary 呼叫來載入 .so 函式庫。 例如,假設 .NET for Android 專案已將共用連結庫libpocketsphinx_jni.so 包含在系結專案中,且具有 EmbeddedNativeLibrary 的建置動作,下列代碼段(在使用共用連結庫之前執行)將會載入 .so 連結庫:

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