Wrappers Android Callable pour Xamarin.Android

Les wrappers callables Android (ACW) sont requis chaque fois que le runtime Android appelle du code managé. Ces wrappers sont obligatoires, car il n’existe aucun moyen d’inscrire des classes avec ART (le runtime Android) au moment de l’exécution. (Plus précisément, la fonction JNI DefineClass() n’est pas prise en charge par le runtime Android.} Les wrappers android callables permettent donc de compenser l’absence de prise en charge de l’inscription de type runtime.

À chaque fois Le code Android doit exécuter une virtual méthode d’interface ou qui est overridden ou implémentée dans le code managé, Xamarin.Android doit fournir un proxy Java afin que cette méthode soit distribuée au type managé approprié. Ces types de proxy Java sont du code Java qui a la « même » classe de base et la même liste d’interface Java que le type managé, implémentant les mêmes constructeurs et déclarant toutes les méthodes d’interface et de classe de base remplacées.

Les wrappers callables Android sont générés par le programme monodroid.exe pendant le processus de génération : ils sont générés pour tous les types qui héritent (directement ou indirectement) de Java.Lang.Object.

Nommage du wrapper Android Callable

Les noms de package pour les wrappers Android Callable sont basés sur le MD5SUM du nom qualifié d’assembly du type exporté. Cette technique d’affectation de noms permet de rendre le même nom de type complet disponible par différents assemblys sans introduire d’erreur d’empaquetage.

En raison de ce schéma de nommage MD5SUM, vous ne pouvez pas accéder directement à vos types par nom. Par exemple, la commande suivante adb ne fonctionne pas, car le nom my.ActivityType de type n’est pas généré par défaut :

adb shell am start -n My.Package.Name/my.ActivityType

En outre, des erreurs comme celles-ci peuvent s’afficher si vous tentez de référencer un type par son nom :

java.lang.ClassNotFoundException: Didn't find class "com.company.app.MainActivity"
on path: DexPathList[[zip file "/data/app/com.company.App-1.apk"] ...

Si vous avez besoin d’accéder aux types par nom, vous pouvez déclarer un nom pour ce type dans une déclaration d’attribut. Par exemple, voici le code qui déclare une activité avec le nom My.ActivityTypecomplet :

namespace My {
    [Activity]
    public partial class ActivityType : Activity {
        /* ... */
    }
}

La ActivityAttribute.Name propriété peut être définie pour déclarer explicitement le nom de cette activité :

namespace My {
    [Activity(Name="my.ActivityType")]
    public partial class ActivityType : Activity {
        /* ... */
    }
}

Une fois ce paramètre de propriété ajouté, my.ActivityType est accessible par nom à partir de code externe et de adb scripts. L’attribut Name peut être défini pour de nombreux types différents, notamment Activity, Application, Service, BroadcastReceiveret ContentProvider:

Le nommage ACW basé sur MD5SUM a été introduit dans Xamarin.Android 5.0. Pour plus d’informations sur l’attribution de noms d’attributs, consultez RegisterAttribute.

Implémentation des interfaces

Il peut arriver que vous deviez implémenter une interface Android, telle que Android.Content.IComponentCallbacks. Étant donné que toutes les classes et l’interface Android étendent l’interface Android.Runtime.IJavaObject , la question se pose : comment implémenter IJavaObject?

La question a été répondue ci-dessus : la raison pour laquelle tous les types Android doivent implémenter IJavaObject est pour que Xamarin.Android dispose d’un wrapper appelant Android à fournir à Android, c’est-à-dire un proxy Java pour le type donné. Étant donné quemonodroid.exe recherche Java.Lang.Object uniquement les sous-classes et Java.Lang.Object implémente IJavaObject, la réponse est évidente : sous-classe Java.Lang.Object:

class MyComponentCallbacks : Java.Lang.Object, Android.Content.IComponentCallbacks {

    public void OnConfigurationChanged (Android.Content.Res.Configuration newConfig)
    {
        // implementation goes here...
    } 

    public void OnLowMemory ()
    {
        // implementation goes here...
    }
}

Informations d’implémentation

Le reste de cette page fournit des détails d’implémentation susceptibles d’être modifiés sans préavis (et est présenté ici uniquement parce que les développeurs seront curieux de savoir ce qui se passe).

Par exemple, étant donné la source C# suivante :

using System;
using Android.App;
using Android.OS;

namespace Mono.Samples.HelloWorld
{
    public class HelloAndroid : Activity
    {
        protected override void OnCreate (Bundle savedInstanceState)
        {
            base.OnCreate (savedInstanceState);
            SetContentView (R.layout.main);
        }
    }
}

Le programme mandroid.exe génère le wrapper Android Callable suivant :

package mono.samples.helloWorld;

public class HelloAndroid
    extends android.app.Activity
{
    static final String __md_methods;
    static {
        __md_methods = "n_onCreate:(Landroid/os/Bundle;)V:GetOnCreate_Landroid_os_Bundle_Handler\n" + "";
        mono.android.Runtime.register (
            "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, 
            Culture=neutral, PublicKeyToken=null", HelloAndroid.class, __md_methods);
    }

    public HelloAndroid ()
    {
        super ();
        if (getClass () == HelloAndroid.class)
            mono.android.TypeManager.Activate (
                "Mono.Samples.HelloWorld.HelloAndroid, HelloWorld, Version=1.0.0.0, 
                Culture=neutral, PublicKeyToken=null", "", this, new java.lang.Object[] {  });
    }

    @Override
    public void onCreate (android.os.Bundle p0)
    {
        n_onCreate (p0);
    }

    private native void n_onCreate (android.os.Bundle p0);
}

Notez que la classe de base est conservée et native que des déclarations de méthode sont fournies pour chaque méthode remplacée dans le code managé.