Architettura

Le applicazioni Xamarin.Android vengono eseguite nell'ambiente di esecuzione mono. Questo ambiente di esecuzione viene eseguito side-by-side con la macchina virtuale Android Runtime (ART). Entrambi gli ambienti di runtime vengono eseguiti sul kernel Linux ed espongono varie API al codice utente che consente agli sviluppatori di accedere al sistema sottostante. Il runtime di Mono viene scritto nel linguaggio C.

È possibile usare system, System.IO, System.Net e il resto delle librerie di classi .NET per accedere alle funzionalità del sistema operativo Linux sottostanti.

In Android, la maggior parte delle strutture di sistema come Audio, Grafica, OpenGL e Telefonia non sono disponibili direttamente per le applicazioni native, vengono esposte solo tramite le API Java di Android Runtime che risiedono in uno degli spazi dei nomi Java.* o Android.* . L'architettura è approssimativamente simile alla seguente:

Diagram of Mono and ART above the kernel and below .NET/Java + bindings

Gli sviluppatori Xamarin.Android accedono alle varie funzionalità del sistema operativo chiamando in API .NET che conoscono (per l'accesso di basso livello) o usando le classi esposte negli spazi dei nomi Android che forniscono un bridge alle API Java esposte dal runtime Android.

Per altre informazioni su come le classi Android comunicano con le classi di Runtime Android, vedere il documento Progettazione API.

Pacchetti dell'applicazione

I pacchetti dell'applicazione Android sono contenitori ZIP con un'estensione di file .apk . I pacchetti di applicazioni Xamarin.Android hanno la stessa struttura e layout dei normali pacchetti Android, con le aggiunte seguenti:

  • Gli assembly dell'applicazione (che contengono IL) vengono archiviati non compressi all'interno della cartella degli assembly . Durante l'avvio del processo in Release build il .apk è mmap() ed ed i processi e gli assembly vengono caricati dalla memoria. Ciò consente un avvio più rapido dell'app, perché gli assembly non devono essere estratti prima dell'esecuzione.

  • Nota: le informazioni sul percorso dell'assembly, ad esempio Assembly.Location e Assembly.CodeBase, non possono essere basate sulle build di rilascio. Non esistono come voci distinte del file system e non hanno una posizione utilizzabile.

  • Le librerie native contenenti il runtime Mono sono presenti all'interno del .apk . Un'applicazione Xamarin.Android deve contenere librerie native per le architetture Android desiderate/mirate, ad esempio armeabi , armeabi-v7a , x86 . Le applicazioni Xamarin.Android non possono essere eseguite in una piattaforma a meno che non contenga le librerie di runtime appropriate.

Le applicazioni Xamarin.Android contengono anche wrapper chiamabili Android per consentire ad Android di chiamare il codice gestito.

Android Callable Wrapper

  • I wrapper chiamabili Android sono un bridge JNI che viene usato ogni volta che il runtime Android deve richiamare il codice gestito. I wrapper chiamabili Android sono il modo in cui è possibile eseguire l'override dei metodi virtuali e implementare le interfacce Java. Per altre informazioni, vedere la documentazione panoramica sull'integrazione java.

Wrapper chiamabili gestiti

I wrapper chiamabili gestiti sono un bridge JNI usato in qualsiasi momento per richiamare il codice Android e fornire supporto per l'override dei metodi virtuali e l'implementazione di interfacce Java. L'intero oggetto Android.* e gli spazi dei nomi correlati sono wrapper chiamabili gestiti generati tramite .jar binding. I wrapper chiamabili gestiti sono responsabili della conversione tra tipi gestiti e Android e richiamano i metodi della piattaforma Android sottostanti tramite JNI.

Ogni wrapper chiamabile gestito creato contiene un riferimento globale Java, accessibile tramite la proprietà Android.Runtime.IJavaObject.Handle . I riferimenti globali vengono usati per fornire il mapping tra istanze Java e istanze gestite. I riferimenti globali sono una risorsa limitata: gli emulatori consentono di esistere solo 2000 riferimenti globali alla volta, mentre la maggior parte dell'hardware consente l'esistenza di oltre 52.000 riferimenti globali alla volta.

Per tenere traccia della creazione e dell'eliminazione definitiva dei riferimenti globali, è possibile impostare la proprietà di sistema debug.mono.log in modo che contenga gref.

I riferimenti globali possono essere liberati in modo esplicito chiamando Java.Lang.Object.Dispose() nel wrapper chiamabile gestito. Verrà rimosso il mapping tra l'istanza Java e l'istanza gestita e verrà consentito la raccolta dell'istanza Java. Se l'istanza Java viene nuovamente accessibile dal codice gestito, verrà creato un nuovo wrapper chiamabile gestito.

È necessario prestare attenzione quando si eliminano i wrapper chiamabili gestiti se l'istanza può essere condivisa inavvertitamente tra thread, in quanto l'eliminazione dell'istanza influirà sui riferimenti da qualsiasi altro thread. Per garantire la massima sicurezza, solo Dispose() le istanze allocate tramitenew o da metodi che si conoscono allocano sempre nuove istanze e non istanze memorizzate nella cache che possono causare la condivisione accidentale di istanze tra thread.

Sottoclassi wrapper chiamabili gestite

Le sottoclassi wrapper chiamabili gestite sono in cui può essere attiva tutta la logica specifica dell'applicazione "interessante". Queste includono sottoclassi Android.App.Activity personalizzate, ad esempio il tipo Activity1 nel modello di progetto predefinito. (In particolare, questi sono tutti Le sottoclassi Java.Lang.Object che non contengono un attributo personalizzato RegisterAttribute o RegisterAttribute.DoNotGenerateAcw sono false, ovvero l'impostazione predefinita.

Analogamente ai wrapper chiamabili gestiti, le sottoclassi wrapper chiamabili gestite contengono anche un riferimento globale, accessibile tramite la proprietà Java.Lang.Object.Handle . Come per i wrapper chiamabili gestiti, i riferimenti globali possono essere liberati in modo esplicito chiamando Java.Lang.Object.Dispose(). A differenza dei wrapper chiamabili gestiti, è consigliabile prestare molta attenzione prima di eliminare tali istanze, perché Dispose()-ing dell'istanza interromperà il mapping tra l'istanza Java (un'istanza di android Callable Wrapper) e l'istanza gestita.

Attivazione Java

Quando viene creato un oggetto AcW (Callable Wrapper ) Android da Java, il costruttore ACW causerà il richiamo del costruttore C# corrispondente. Ad esempio, acw per MainActivity conterrà un costruttore predefinito che richiamerà il costruttore predefinito di MainActivity. (Questa operazione viene eseguita tramite il Chiamare TypeManager.Activate() all'interno dei costruttori ACW.

Esiste un'altra firma del costruttore di conseguenza: il costruttore (IntPtr, JniHandleOwnership). Il costruttore (IntPtr, JniHandleOwnership) viene richiamato ogni volta che un oggetto Java viene esposto al codice gestito e deve essere costruito un wrapper chiamabile gestito per gestire l'handle JNI. Questa operazione viene in genere eseguita automaticamente.

Esistono due scenari in cui il costruttore (IntPtr, JniHandleOwnership) deve essere fornito manualmente in una sottoclasse Wrapper chiamabile gestita:

  1. Android.App.Application è sottoclassato. L'applicazione è speciale. Il costruttore Applicaton predefinito non verrà mai richiamato e deve essere fornito il costruttore (IntPtr, JniHandleOwnership).

  2. Chiamata al metodo virtuale da un costruttore della classe base.

Si noti che (2) è un'astrazione persa. In Java, come in C#, le chiamate ai metodi virtuali da un costruttore richiamano sempre l'implementazione del metodo più derivata. Ad esempio, il costruttore TextView(Context, AttributeSet, int) richiama il metodo virtuale TextView.getDefaultMovementMethod(), associato come proprietà TextView.DefaultMovementMethod. Pertanto, se un tipo LogTextBox doveva (1) sottoclasse TextView, (2) eseguire l'override di TextView.DefaultMovementMethod e (3) attivare un'istanza di tale classe tramite XML, la proprietà DefaultMovementMethod sottoposta a override verrebbe richiamata prima che il costruttore ACW avesse la possibilità di eseguire e si verificava prima che il costruttore C# avesse la possibilità di eseguire.

Questa operazione è supportata creando un'istanza di LogTextBox tramite il costruttore LogTextView(IntPtr, JniHandleOwnership) quando l'istanza di ACW LogTextBox immette prima il codice gestito e quindi richiama il costruttore LogTextBox(Context, IAttributeSet, int) nella stessa istanza quando viene eseguito il costruttore ACW.

Ordine degli eventi:

  1. Il codice XML di layout viene caricato in un controllo ContentView.

  2. Android crea un'istanza dell'oggetto grafico dell'oggetto Layout e crea un'istanza di monodroid.apidemo.LogTextBox , acW per LogTextBox .

  3. Il costruttore monodroid.apidemo.LogTextBox esegue il costruttore android.widget.TextView .

  4. Il costruttore TextView richiama monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() richiama LogTextBox.n_getDefaultMovementMethod() , che richiama TextView.n_GetDefaultMovementMethod() , che richiama Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer).

  6. Java.Lang.Object.GetObject<TextView>() verifica se esiste già un'istanza C# corrispondente per l'handle . In caso affermativo, viene restituito. In questo scenario non esiste, quindi Object.GetObject<T>() deve crearne uno.

  7. Object.GetObject<T>() cerca il costruttore LogTextBox(IntPtr, JniHandleOwneship), lo richiama, crea un mapping tra handle e l'istanza creata e restituisce l'istanza creata.

  8. TextView.n_GetDefaultMovementMethod() richiama il getter della proprietà LogTextBox.DefaultMovementMethod .

  9. Il controllo torna al costruttore android.widget.TextView , che termina l'esecuzione.

  10. Il costruttore monodroid.apidemo.LogTextBox viene eseguito, richiamando TypeManager.Activate().

  11. Il costruttore LogTextBox(Context, IAttributeSet, int) viene eseguito nella stessa istanza creata in (7).

  12. Se non è possibile trovare il costruttore (IntPtr, JniHandleOwnership), verrà generata un'eccezione System.MissingMethodException](xref:System.MissingMethodException).

Chiamate Dispose() premature

Esiste un mapping tra un handle JNI e l'istanza C# corrispondente. Java.Lang.Object.Dispose() interrompe questo mapping. Se un handle JNI immette codice gestito dopo che il mapping è stato interrotto, l'attivazione java e il costruttore (IntPtr, JniHandleOwnership) verrà controllato e richiamato. Se il costruttore non esiste, verrà generata un'eccezione.

Ad esempio, data la sottoclasse Wraper chiamabile gestita seguente:

class ManagedValue : Java.Lang.Object {

    public string Value {get; private set;}

    public ManagedValue (string value)
    {
        Value = value;
    }

    public override string ToString ()
    {
        return string.Format ("[Managed: Value={0}]", Value);
    }
}

Se si crea un'istanza, Dispose() e si determina la ricreazione del wrapper chiamabile gestito:

var list = new JavaList<IJavaObject>();
list.Add (new ManagedValue ("value"));
list [0].Dispose ();
Console.WriteLine (list [0].ToString ());

Il programma morirà:

E/mono    ( 2906): Unhandled Exception: System.NotSupportedException: Unable to activate instance of type Scratch.PrematureDispose.ManagedValue from native handle 4051c8c8 --->
System.MissingMethodException: No constructor found for Scratch.PrematureDispose.ManagedValue::.ctor(System.IntPtr, Android.Runtime.JniHandleOwnership)
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateProxy (System.Type type, IntPtr handle, JniHandleOwnership transfer) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   --- End of inner exception stack trace ---
E/mono    ( 2906):   at Java.Interop.TypeManager.CreateInstance (IntPtr handle, JniHandleOwnership transfer, System.Type targetType) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object.GetObject (IntPtr handle, JniHandleOwnership transfer, System.Type type) [0x00000] in <filename unknown>:0
E/mono    ( 2906):   at Java.Lang.Object._GetObject[IJavaObject] (IntPtr handle, JniHandleOwnership transfer) [0x00000

Se la sottoclasse contiene un costruttore (IntPtr, JniHandleOwnership), verrà creata una nuova istanza del tipo. Di conseguenza, l'istanza apparirà "persa" tutti i dati dell'istanza, perché si tratta di una nuova istanza. Si noti che il valore è null.

I/mono-stdout( 2993): [Managed: Value=]

Solo Dispose() delle sottoclassi wrapper chiamabili gestite quando si sa che l'oggetto Java non verrà più usato o la sottoclasse non contiene più dati di istanza e viene fornito un costruttore (IntPtr, JniHandleOwnership).

Avvio dell'applicazione

Quando viene avviata un'attività, un servizio e così via, Android verificherà prima di tutto se è già in esecuzione un processo per ospitare l'attività/servizio/ecc. Se non esiste alcun processo di questo tipo, verrà creato un nuovo processo, il AndroidManifest.xml viene letto e il tipo specificato nell'attributo /manifest/application/@android:name viene caricato e creato un'istanza. Tutti i tipi specificati dal metodo /manifest/application/provider/@android:name vengono quindi create istanze di tutti i tipi specificati dal metodo /manifest/application/provider/@android:name e hanno richiamato il metodo ContentProvider.attachInfo%28 . Xamarin.Android si collega a questa funzionalità aggiungendo un mono. MonoRuntimeProviderContentProvider per AndroidManifest.xml durante il processo di compilazione. Mono . Il metodo MonoRuntimeProvider.attachInfo() è responsabile del caricamento del runtime Mono nel processo. Eventuali tentativi di utilizzo di Mono prima di questo punto avranno esito negativo. ( Nota: ecco perché i tipi che sottoclasse Android.App.Application devono fornire un costruttore (IntPtr, JniHandleOwnership), perché l'istanza dell'applicazione viene creata prima che Mono possa essere inizializzata.

Al termine dell'inizializzazione del processo, AndroidManifest.xml viene consultata per trovare il nome della classe dell'attività/servizio/ecc. da avviare. Ad esempio, l'attributo /manifest/application/activity/@android:name viene usato per determinare il nome di un'attività da caricare. Per Le attività, questo tipo deve ereditare android.app.Activity. Il tipo specificato viene caricato tramite Class.forName() (che richiede che il tipo sia un tipo Java, quindi i wrapper chiamabili Android), quindi ne viene creata un'istanza. La creazione di un'istanza di Android Callable Wrapper attiverà la creazione di un'istanza del tipo C# corrispondente. Android richiamerà quindi Activity.onCreate(Bundle), che causerà il richiamo dell'oggetto Activity.OnCreate(Bundle) corrispondente e le gare non verranno più eseguite.