Résolution des problèmes de connexions

La liaison d’une bibliothèque Android (un fichier .aar ou un .jar) est rarement une affaire simple ; il nécessite généralement des efforts supplémentaires pour atténuer les problèmes résultant des différences entre Java et .NET. Ces problèmes empêchent .NET pour Android de lier la bibliothèque Android et de se présenter en tant que messages d’erreur dans le journal de génération. Ce guide fournit des conseils pour résoudre les problèmes, répertorier certains des problèmes/scénarios les plus courants et fournir des solutions possibles pour lier correctement la bibliothèque Android.

Lors de la liaison d’une bibliothèque Android existante, il est nécessaire de garder à l’esprit les points suivants :

  • Dépendances externes pour la bibliothèque : toutes les dépendances Java requises par la bibliothèque Android doivent être incluses dans le projet .NET pour Android via un package NuGet ou en tant que AndroidLibrary.

  • Niveau de l’API Android ciblant la bibliothèque Android : il n’est pas possible de « rétrograder » le niveau de l’API Android ; assurez-vous que le projet de liaison .NET pour Android cible le même niveau d’API (ou supérieur) que la bibliothèque Android.

Conseil

Le wiki du référentiel GitHub Binding Tooling est une ressource intéressante et contient des informations de résolution des problèmes supplémentaires qui peuvent vous aider dans des cas spécifiques.

La première étape de résolution des problèmes liés à la liaison d’une bibliothèque .NET pour Android consiste à activer la sortie MSBuild de diagnostic. Après avoir activé la sortie de diagnostic, régénérez le projet de liaison .NET pour Android et examinez le journal de build pour rechercher des indices sur la cause du problème.

Il peut également être utile de décompiler la bibliothèque Android et d’examiner les types et méthodes que .NET pour Android essaie de lier. Ceci est abordé plus en détail plus loin dans ce guide.

Décompilation d’une bibliothèque Android

L’inspection des classes et des méthodes des classes Java peut fournir des informations précieuses qui vous aideront à lier une bibliothèque. JD-GUI est un utilitaire graphique qui peut afficher du code source Java à partir des fichiers CLASS contenus dans un fichier JAR.

Pour décompiler une bibliothèque Android, ouvrez le fichier . Fichier JAR avec le décompileur Java. Si la bibliothèque est un . Fichier AAR , le code source Java se trouvera dans l’entrée classes.jar du fichier d’archivage. Voici un exemple de capture d’écran de l’utilisation de JD-GUI pour analyser le fichier JAR Picasso :

Utilisation du décompileur Java pour analyser picasso-2.5.2.jar

Une fois que vous avez décompilé la bibliothèque Android, examinez le code source. En général, recherchez :

  • Les classes qui ont des caractéristiques d’obfuscation : les caractéristiques des classes obfuscatées sont les suivantes :

    • Le nom de la classe comprend un $, c’est-à-dire a$.class
    • Le nom de la classe est entièrement constitué de caractères minuscules, à savoir a.class
  • Déclarations pour les bibliothèques non référencées : Identifiez la bibliothèque non référencée et ajoutez ces dépendances au projet de liaison .NET pour Android avec une liaison appropriée à partir de NuGet ou en utilisant une Action de génération AndroidLibrary.

Remarque

La décomposition d’une bibliothèque Java peut être interdite ou soumise à des restrictions légales en fonction des lois locales ou de la licence sous laquelle la bibliothèque Java a été publiée. Si nécessaire, inscrivez les services d’un professionnel juridique avant de tenter de décompiler une bibliothèque Java et d’inspecter le code source.

Inspecter api.xml

Dans le cadre de la création d’un projet de liaison, .NET pour Android génère un nom de fichier XML obj/Debug/api.xml :

Api.xml généré sous obj/Debug

Ce fichier fournit la liste de toutes les API Java que .NET pour Android essaie de lier. Le contenu de ce fichier peut aider à identifier les types ou méthodes manquants, les liaisons dupliquées. Bien que l’inspection de ce fichier soit fastidieuse et chronophage, elle peut fournir des indices sur ce qui pourrait causer des problèmes de liaison. Par exemple, api.xml peut révéler qu’une propriété retourne un type inapproprié ou qu’il existe deux types qui partagent le même nom managé.

Problèmes connus

Cette section répertorie quelques-uns des messages d’erreur courants ou symptômes que j’ai rencontrés lors de la tentative de liaison d’une bibliothèque Android.

Problème : types C# manquants dans la sortie générée.

La liaison .dll génère mais manque certains types Java, ou la source C# générée ne génère pas en raison d’une erreur indiquant qu’il manque des types.

Causes possibles :

Cette erreur peut se produire en raison de plusieurs raisons, comme indiqué ci-dessous :

  • La bibliothèque liée peut référencer une deuxième bibliothèque Java. Si l’API publique de la bibliothèque liée utilise des types de la deuxième bibliothèque, vous devez également référencer une liaison managée pour la deuxième bibliothèque.

  • Java autorise la dérivation d’une classe publique à partir d’une classe non publique, mais elle n’est pas prise en charge dans .NET. Étant donné que le générateur de liaisons ne génère pas de liaisons pour les classes non publiques, les classes dérivées telles que celles-ci ne peuvent pas être générées correctement. Pour résoudre ce problème, supprimez l’entrée de métadonnées pour ces classes dérivées à l’aide du nœud de suppression dans Metadata.xml, ou corrigez les métadonnées qui rendent la classe non publique publique. Bien que la dernière solution crée la liaison afin que la source C# génère, la classe non publique ne doit pas être utilisée.

    Par exemple :

    <attr path="/api/package[@name='com.some.package']/class[@name='SomeClass']"
        name="visibility">public</attr>
    
  • Les outils qui obfusent les bibliothèques Java peuvent interférer avec le générateur de liaisons .NET pour Android et sa capacité à générer des classes wrapper C#. L’extrait de code suivant montre comment mettre à jour Metadata.xml pour rendre un nom de classe lisible :

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

Problème : la source C# générée ne génère pas en raison d’une incompatibilité de type de paramètre

La source C# générée ne se compile pas. Les types de paramètres de la méthode substituée ne correspondent pas.

Causes possibles :

.NET pour Android inclut un large éventail de champs Java mappés à des énumérations dans les liaisons C#. Celles-ci peuvent entraîner des incompatibilités de type dans les liaisons générées. Pour résoudre ce problème, les signatures de méthode créées à partir du générateur de liaison doivent être modifiées pour utiliser les énumérations. Pour plus d’informations, consultez Création d’énumérations.

Problème : Types EventArgs personnalisés dupliqués

La génération échoue en raison de doublons de types personnalisés EventArgs. Une erreur semblable à celle-ci se produit :

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

Causes possibles :

Cela est dû au fait qu’il existe un conflit entre les types d’événements qui proviennent de plusieurs types d’interface « écouteur » qui partagent des méthodes ayant des noms identiques. Par exemple, s’il existe deux interfaces Java comme indiqué dans l’exemple ci-dessous, le générateur crée à la fois MediationBannerListener et MediationInterstitialListener, ce qui génère l’erreur.

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

Cela est par conception afin que les noms longs sur les types d’arguments d’événement soient évités. Pour éviter ces conflits, certaines transformations de métadonnées sont requises. Modifiez transformations\Metadata.xml et ajoutez un attribut sur l’une argsType des interfaces (ou sur la méthode d’interface) :

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

Problème : la classe n’implémente pas la méthode d’interface

Un message d’erreur est généré indiquant qu’une classe générée n’implémente pas une méthode requise pour une interface que la classe générée implémente. Toutefois, en examinant le code généré, vous pouvez voir que la méthode est implémentée.

Voici un exemple d’erreur :

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'

Causes possibles :

Il s’agit d’un problème qui se produit avec les méthodes Java ayant des types de retour covariants. Dans cet exemple, la méthode Oauth.Signpost.Http.IHttpRequest.UnWrap() doit retourner Java.Lang.Object. Toutefois, la méthode Oauth.Signpost.Basic.HttpURLConnectionRequestAdapter.UnWrap() a un type de retour de HttpURLConnection. Il existe deux façons de résoudre ce problème :

  • Ajoutez une déclaration de classe partielle pour HttpURLConnectionRequestAdapter et implémentez IHttpRequest.Unwrap()explicitement :

    namespace Oauth.Signpost.Basic {
        partial class HttpURLConnectionRequestAdapter {
            Java.Lang.Object OauthSignpost.Http.IHttpRequest.Unwrap() {
                return Unwrap();
            }
        }
    }
    
  • Supprimez la covariance du code C# généré. Cela implique l’ajout de la transformation suivante à Transforms\Metadata.xml, ce qui va faire en sorte que le code C# généré ait un type de retour de Java.Lang.Object.

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

Problème : Collisions de noms sur les classes internes / propriétés

Visibilité en conflit sur les objets hérités.

En Java, il n’est pas nécessaire qu’une classe dérivée ait la même visibilité que son parent. Java va juste corriger cela pour vous. En C#, cela doit donc être explicite, vous devez vous assurer que toutes les classes de la hiérarchie ont la visibilité appropriée. L’exemple suivant montre comment remplacer un nom de package Java par com.evernote.android.jobEvernote.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>

Problème : une bibliothèque .so requise par la liaison ne se charge pas

Certains projets de liaison peuvent également dépendre des fonctionnalités d’une bibliothèque .so . Il est possible que .NET pour Android ne charge pas automatiquement la bibliothèque .so . Lorsque le code Java encapsulé s’exécute, .NET pour Android ne parvient pas à effectuer l’appel JNI et le message d'erreur java.lang.UnsatisfiedLinkError : méthode native introuvable : apparaît dans le logcat de l’application.

Le correctif de ce problème consiste à charger manuellement la bibliothèque .so avec un appel à Java.Lang.JavaSystem.LoadLibrary. Par exemple, en supposant qu'un projet .NET pour Android ait une librairie partagée libpocketsphinx_jni.so incluse dans le projet de liaison avec une action de génération EmbeddedNativeLibrary, l'extrait de code suivant (exécuté avant d'utiliser la librairie partagée) chargera la librairie .so :

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