Wrapper chiamabili Android per Xamarin.Android

Gli ACL (Callable Wrapper) Android sono necessari ogni volta che il runtime Android richiama codice gestito. Questi wrapper sono necessari perché non è possibile registrare classi con ART (runtime Android) in fase di esecuzione. (In particolare, il La funzione JNI DefineClass() non è supportata dal runtime Android.} I wrapper chiamabili Android costituiscono quindi la mancanza di supporto per la registrazione dei tipi di runtime.

Ogni volta cheoverridden il codice Android deve eseguire un virtual metodo di interfaccia o implementato nel codice gestito, Xamarin.Android deve fornire un proxy Java in modo che questo metodo venga inviato al tipo gestito appropriato. Questi tipi di proxy Java sono codice Java con la "stessa" classe di base e l'elenco di interfacce Java del tipo gestito, implementando gli stessi costruttori e dichiarando eventuali metodi di interfaccia e classe base sottoposti a override.

I wrapper chiamabili Android vengono generati dal programma monodroid.exe durante il processo di compilazione: vengono generati per tutti i tipi che (direttamente o indirettamente) ereditano Java.Lang.Object.

Denominazione del wrapper chiamabile Android

I nomi dei pacchetti per Android Callable Wrapper si basano sul md5SUM del nome completo dell'assembly del tipo esportato. Questa tecnica di denominazione consente di rendere disponibile lo stesso nome di tipo completo da assembly diversi senza introdurre un errore di creazione del pacchetto.

A causa di questo schema di denominazione MD5SUM, non è possibile accedere direttamente ai tipi in base al nome. Ad esempio, il comando seguente adb non funzionerà perché il nome my.ActivityType del tipo non viene generato per impostazione predefinita:

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

Se si tenta di fare riferimento a un tipo in base al nome, è anche possibile che vengano visualizzati errori simili al seguente:

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

Se è necessario l'accesso ai tipi in base al nome, è possibile dichiarare un nome per tale tipo in una dichiarazione di attributo. Ad esempio, di seguito è riportato il codice che dichiara un'attività con il nome My.ActivityTypecompleto :

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

La ActivityAttribute.Name proprietà può essere impostata per dichiarare in modo esplicito il nome di questa attività:

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

Dopo aver aggiunto questa impostazione di proprietà, my.ActivityType è possibile accedere in base al nome dal codice esterno e dagli adb script. L'attributo Name può essere impostato per molti tipi diversi, tra cui Activity, ApplicationService, BroadcastReceiver, e ContentProvider:

La denominazione ACW basata su MD5SUM è stata introdotta in Xamarin.Android 5.0. Per altre informazioni sulla denominazione degli attributi, vedere RegisterAttribute.

Implementazione di interfacce

In alcuni casi potrebbe essere necessario implementare un'interfaccia Android, ad esempio Android.Content.IComponentCallbacks. Poiché tutte le classi e l'interfaccia Android estendono l'interfaccia Android.Runtime.IJavaObject , sorge la domanda: come implementare IJavaObject?

La domanda è stata risolta in precedenza: il motivo per cui tutti i tipi Android devono implementare IJavaObject è in modo che Xamarin.Android abbia un wrapper chiamabile Android da fornire ad Android, ad esempio un proxy Java per il tipo specificato. Poiché monodroid.exe cerca Java.Lang.Object solo sottoclassi e Java.Lang.Object implementa IJavaObject, la risposta è ovvia: sottoclasse 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...
    }
}

Dettagli dell'implementazione

La parte restante di questa pagina fornisce dettagli di implementazione soggetti a modifiche senza preavviso (e viene presentato qui solo perché gli sviluppatori saranno curiosi di cosa sta succedendo).

Ad esempio, data l'origine C# seguente:

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);
        }
    }
}

Il programma mandroid.exe genererà il wrapper chiamabile Android seguente:

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);
}

Si noti che la classe base viene mantenuta e native vengono fornite dichiarazioni di metodo per ogni metodo sottoposto a override all'interno del codice gestito.