バインドのトラブルシューティング

重要

現在、Xamarin プラットフォームでのカスタム バインディングの使用を調査しています。 今後の開発作業の発展のために、このアンケートにご回答ください。

この記事では、バインドの生成時に発生する可能性のあるいくつかの一般的なエラー、考えられる原因、推奨される解決方法について簡単に説明します。

概要

Android ライブラリ (.aar または .jar) ファイルのバインドが簡単な作業であることはほとんどありません。通常は、Java と .NET の違いに起因する問題を軽減するための追加作業が必要となります。 これらの問題により、Xamarin.Android で Android ライブラリをバインドできなくなり、ビルド ログにエラー メッセージとして示されます。 このガイドでは、問題のトラブルシューティングのヒント、より一般的な問題/シナリオ、Android ライブラリを正常にバインドするための考えられる解決策を示します。

既存の Android ライブラリをバインドする場合は、次の点に注意する必要があります。

  • ライブラリの外部依存関係 – Android ライブラリに必要なすべての Java 依存関係は、ReferenceJar または EmbeddedReferenceJar として Xamarin.Android プロジェクトに含める必要があります。

  • Android ライブラリがターゲット としている Android API レベル – Android API レベルを "ダウングレード" することはできません。Xamarin.Android バインド プロジェクトが Android ライブラリと同じ API レベル (またはそれ以上) をターゲットにしていることを確認してください。

  • Android ライブラリのパッケージ化に使用された Android JDK のバージョン – Android ライブラリ が Xamarin.Android で使用されている JDK とは異なるバージョンの JDK でビルドされた場合、バインド エラーが発生する可能性があります。 可能であれば、Xamarin.Android のインストールで使用されているものと同じバージョンの JDK を使用して、Android ライブラリを再コンパイルしてください。

Xamarin.Android ライブラリのバインドに関する問題のトラブルシューティングを行うには、まず、MSBuild の診断出力を有効にします。 診断出力を有効にしたら、Xamarin.Android バインド プロジェクトをリビルドし、ビルド ログを調べて問題の原因に関する手掛かりを見つけます。

また、Android ライブラリを逆コンパイルし、Xamarin.Android がバインドしようとしている型やメソッドを調べることも役立ちます。 これについては、このガイドで後ほど詳しく説明します。

Android ライブラリの逆コンパイル

Java のクラスと 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 バインド プロジェクトにそれらの依存関係を追加します

Note

Java ライブラリの逆コンパイルは、現地の法律または Java ライブラリを公開する際のライセンスに基づき、禁止されている場合や法的規制の対象となる場合があります。 Java ライブラリを逆コンパイルしてソース コードを調べる前に、必要に応じて法律専門家に相談してください。

api.xml の検査

バインド プロジェクトのビルドの一環として、Xamarin.Android では obj/Debug/api.xml という XML ファイルが生成されます。

Generated api.xml under obj/Debug

このファイルは、Xamarin.Android がバインドしようとしているすべての Java API のリストを提供します。 このファイルの内容は、欠落している型またはメソッドや、重複するバインドの特定に役立ちます。 このファイルの検査は面倒で時間がかかりますが、バインドの問題の考えられる原因に関する手掛かりが得られます。 たとえば、api.xml によって、プロパティから不適切な型が返されていることや、同じマネージド名を共有する 2 つの型が存在することが明らかになります。

既知の問題

このセクションでは、Android ライブラリをバインドしようとしたときに発生する可能性のある一般的なエラー メッセージや症状について説明します。

問題: Java バージョンの不一致

ライブラリのコンパイルに使用されたバージョンよりも新しいバージョンまたは古いバージョンの Java を使用しているために、型が生成されなかったり、予期しないクラッシュが発生したりすることがあります。 Xamarin.Android プロジェクトで使用しているものと同じバージョンの JDK で Android ライブラリを再コンパイルします。

問題: 少なくとも 1 つの Java ライブラリが必要です

.JAR が追加されているにもかかわらず、Java ライブラリが少なくとも 1 つ必要であることを示すエラーが表示されます。

考えられる原因:

ビルド アクションが EmbeddedJar に設定されていることを確認します。 .JAR ファイルのビルド アクションは複数あるため (InputJarEmbeddedJarReferenceJarEmbeddedReferenceJar など)、バインド ジェネレーターは既定で使用するアクションを自動的に推測することはできません。 ビルド アクションの詳細については、「Build Actions (ビルド アクション)」をご覧ください。

問題: バインディング ツールで .JAR ライブラリ

バインド ライブラリ ジェネレーターが .JAR ライブラリの読み込みに失敗します。

考えられる原因

(Proguard などのツールによる) コード難読化を使用する一部の .JAR ライブラリは、Java ツールで読み込むことができません。 これらのツールでは Java リフレクションと ASM バイト コード エンジニアリング ライブラリを使用するため、難読化されたライブラリは、Android ランタイム ツールでは通過しても、これらの依存ツールでは拒否される場合があります。 これを回避するには、バインド ジェネレーターを使用するのではなく、これらのライブラリを手動でバインドします。

問題: 生成された出力に C# 型がありません。

バインド .dll はビルドされますが、一部の Java の型が欠落しています。または、欠落している型があることを示すエラーによって、生成された C# ソースがビルドされません。

考えられる原因:

このエラーは、次のようないくつかの理由で発生する可能性があります。

  • バインドされているライブラリが、別の Java ライブラリを参照している可能性があります。 バインドされたライブラリのパブリック API が別のライブラリの型を使用する場合、そのライブラリのマネージド バインドも参照する必要があります。

  • 上記のライブラリ読み込みエラーの理由と同様に、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 には、C# バインディングで列挙型にマップされるさまざまな Java フィールドが含まれています。 これらによって、生成されたバインディングで型の非互換性が発生する可能性があります。 これを解決するには、バインド ジェネレーターから作成されたメソッド シグネチャを変更して、列挙型を使用する必要があります。 詳細については、列挙型の修正に関する記事をご覧ください。

問題: パッケージ化での NoClassDefFoundError

パッケージ化手順で java.lang.NoClassDefFoundError がスローされます。

考えられる原因:

このエラーの最も考えられる理由は、必須の Java ライブラリをアプリケーション プロジェクト (.csproj) に追加する必要があることです。 .JAR ファイルは自動的に解決されるわけではありません。 Java ライブラリのバインドは、ターゲット デバイスまたはエミュレーター (Google Maps の maps.jar など) に存在しないユーザー アセンブリに対して常に生成されるとは限りません。 ライブラリ .JAR はライブラリ dll に埋め込まれているため、これは Android ライブラリ プロジェクトのサポートには当てはまりません。

問題: カスタム EventArgs 型の重複

カスタム EventArgs 型の重複が原因でビルドが失敗します。 次のようなエラーが発生します。

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

考えられる原因:

これは、同じ名前のメソッドを共有する複数の "リスナー" 型のインターフェイスによって、イベントの型の競合が発生するためです。 たとえば、次の例のように 2 つの 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'

考えられる原因:

この問題は、Java メソッドを共変の戻り値の型にバインドしたときに発生します。 この例では、Oauth.Signpost.Http.IHttpRequest.UnWrap() メソッドは Java.Lang.Object を返す必要があります。 しかし、Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.UnWrap() メソッドの戻り値の型は HttpURLConnection です。 この問題を修正するには、次の 2 つの方法があります。

  • 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 では、派生クラスの可視性がその親と同じである必要はありません。 これは自動的に修正されます。 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: Native メソッドが見つかりません。

これを解決するには、Java.Lang.JavaSystem.LoadLibrary を呼び出して .so ライブラリを手動で読み込みます。 たとえば、Xamarin.Android プロジェクトに、ビルド アクションが EmbeddedNativeLibrary のバインド プロジェクトに含まれる共有ライブラリ libpocketsphinx_jni.so があるとします。次のスニペット (共有ライブラリを使用する前に実行) によって .so ライブラリが読み込まれます。

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

まとめ

この記事では、Java バインディングに関連する一般的なトラブルシューティングの問題を示し、それらの解決方法を説明しました。