Architektura

Aplikace Xamarin.Android běží v prostředí mono spouštění. Toto spouštěcí prostředí běží souběžně s virtuálním počítačem Android Runtime (ART). Obě běhová prostředí běží nad jádrem Linuxu a zveřejňují různá rozhraní API pro uživatelský kód, která vývojářům umožňuje přístup k základnímu systému. Modul runtime Mono je napsaný v jazyce C.

Pro přístup k základním zařízením operačního systému Linux můžete používat systém, System.IO, System.Net a zbývající knihovny tříd .NET.

Na Androidu není většina systémových zařízení, jako je Zvuk, Grafika, OpenGL a Telefonie, k dispozici přímo nativním aplikacím, jsou k dispozici pouze prostřednictvím rozhraní Java API modulu Android Runtime, která se nacházejí v jednom z oborů názvů Java.* nebo obory názvů Android.* . Architektura se přibližně podobá této:

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

Vývojáři Xamarin.Android přistupují k různým funkcím v operačním systému buď voláním rozhraní .NET API, která znají (pro přístup na nízké úrovni), nebo pomocí tříd vystavených v oborech názvů Androidu, které poskytují most k rozhraním JAVA API, která jsou vystavená modulem Android Runtime.

Další informace o tom, jak třídy Androidu komunikují s třídami modulu Android Runtime, najdete v dokumentu návrhu rozhraní API.

Balíčky aplikací

Balíčky aplikací pro Android jsou kontejnery ZIP s příponou .apk souboru. Balíčky aplikací Xamarin.Android mají stejnou strukturu a rozložení jako běžné balíčky pro Android s následujícími doplňky:

  • Sestavení aplikace (obsahující IL) jsou uložena nekomprimovaná ve složce sestavení . Při spuštění procesu ve vydané verzi sestavení .apk je mmap() ed do procesu a sestavení jsou načtena z paměti. To umožňuje rychlejší spuštění aplikace, protože sestavení není nutné před spuštěním extrahovat.

  • Poznámka: Informace o umístění sestavení, jako je Assembly.Location a Assembly.CodeBase, nelze v buildech vydané verze spoléhat . Neexistují jako samostatné položky systému souborů a nemají použitelné umístění.

  • Nativní knihovny obsahující modul runtime Mono se nacházejí v .apk . Aplikace Xamarin.Android musí obsahovat nativní knihovny pro požadované/cílové architektury Androidu, například armeabi , armeabi-v7a , x86 . Aplikace Xamarin.Android nelze spustit na platformě, pokud neobsahují příslušné knihovny modulu runtime.

Aplikace Xamarin.Android také obsahují obálky volatelné pro Android, aby android mohl volat do spravovaného kódu.

Obálky Androidu s možností volání

  • Obálky s možností volání pro Android jsou most JNI , který se používá pokaždé, když modul Android Runtime potřebuje vyvolat spravovaný kód. Obálky s možností volání pro Android jsou způsob, jakým lze přepsat virtuální metody a implementovat rozhraní Java. Další informace najdete v dokumentaci k přehledu integrace Javy.

Spravované obálky s možností volání

Spravované obálky s možností volání jsou most JNI, který se používá vždy, když spravovaný kód potřebuje vyvolat kód Androidu a poskytovat podporu pro přepsání virtuálních metod a implementaci rozhraní Java. Celý android.* a související obory názvů jsou spravované obálky volatelné generované prostřednictvím .jar vazby. Spravované obálky s možností volání zodpovídají za převod mezi spravovanými typy a typy Androidu a vyvoláním základních metod platformy Android prostřednictvím JNI.

Každý vytvořený spravovaný volatelný obálka obsahuje globální odkaz Java, který je přístupný prostřednictvím Android.Runtime.IJavaObject.Handle vlastnost. Globální odkazy se používají k zajištění mapování mezi instancemi Java a spravovanými instancemi. Globální odkazy jsou omezené prostředky: emulátory umožňují najednou existovat pouze 2000 globálních odkazů, zatímco většina hardwaru umožňuje najednou existovat více než 52 000 globálních odkazů.

Pokud chcete sledovat, kdy se vytvoří a zničí globální odkazy, můžete nastavit vlastnost systému debug.mono.log tak, aby obsahovala gref.

Globální odkazy lze explicitně uvolnit voláním Java.Lang.Object.Dispose() na spravované obálky s možností volání. Tím se odebere mapování mezi instancí Javy a spravovanou instancí a umožníte shromažďování instance Javy. Pokud je instance Javy znovu přístupná ze spravovaného kódu, vytvoří se pro ni nová spravovaná obálka s možností volání.

Při odstraňování obálky s možností spravovaného volání je potřeba věnovat pozornost, pokud může být instance neúmyslně sdílena mezi vlákny, protože vyřazení instance bude mít vliv na odkazy z jiných vláken. Pro zajištění maximální bezpečnosti pouze Dispose() instancí, které byly přiděleny prostřednictvím newmetod nebo z metod, které vždy přidělují nové instance, a ne instance uložené v mezipaměti, které můžou způsobit náhodné sdílení instancí mezi vlákny.

Podtřídy spravované obálky s možností volání

Spravovaná podtřídy obálky s možností volání jsou místo, kde může být živá všechna "zajímavá" logika specifická pro aplikaci. Patří mezi ně vlastní podtřídy Android.App.Activity (například typ Activity1 ve výchozí šabloně projektu). (Konkrétně se jedná o všechny Podtřídy Java.Lang.Object, které neobsahují vlastní atribut RegisterAttribute nebo RegisterAttribute.DoNotGenerateAcw je false, což je výchozí hodnota.)

Podobně jako spravované obálky s možností volání obsahují spravované obálkové podtřídy volajícího také globální odkaz přístupný prostřednictvím java.Lang.Object.Handle vlastnost. Stejně jako u spravovaných obálkových volání lze globální odkazy explicitně uvolnit voláním Java.Lang.Object.Dispose(). Na rozdíl od spravovaných obálkových volání je potřeba před odstraněním takových instancí věnovat velkou pozornost , protože Dispose()-ing instance přeruší mapování mezi instancí Javy (instance volatelné obálky Androidu) a spravovanou instancí.

Aktivace v Javě

Když se vytvoří obálka ACW (Callable Wrapper ) pro Android z Javy, konstruktor ACW způsobí vyvolání odpovídajícího konstruktoru jazyka C#. Například ACW pro MainActivity bude obsahovat výchozí konstruktor, který vyvolá MainActivity výchozí konstruktor. (To se provádí prostřednictvím Volání TypeManager.Activate() v rámci konstruktorů ACW.)

Existuje jeden další konstruktor podpis výsledku: konstruktor (IntPtr, JniHandleOwnership). Konstruktor (IntPtr, JniHandleOwnership) je vyvolán vždy, když je objekt Java vystavený spravovanému kódu a musí být vytvořen spravovaný callable Wrapper pro správu popisovače JNI. Obvykle se to provádí automaticky.

Existují dva scénáře, ve kterých musí být konstruktor (IntPtr, JniHandleOwnership) ručně zadaný u podtřídy Managed Callable Wrapper:

  1. Android.App.Application je podtříděná. Aplikace je zvláštní; výchozí konstruktor Applicaton nebude nikdy vyvolán a konstruktor (IntPtr, JniHandleOwnership) musí být uveden.

  2. Vyvolání virtuální metody z konstruktoru základní třídy

Všimněte si, že (2) je nevracená abstrakce. V Javě, stejně jako v jazyce C#, volání virtuálních metod z konstruktoru vždy vyvolat nejvíce odvozené implementace metody. Například TextView(Context, AttributeSet, int) konstruktor vyvolá virtuální metodu TextView.getDefaultMovementMethod(), která je vázána jako TextView.DefaultMovementMethod vlastnost. Pokud by tedy typ LogTextBox měl (1) podtřídu TextView, (2) přepsat TextView.DefaultMovementMethod a (3) aktivovat instanci této třídy prostřednictvím XML, přepsaná DefaultMovementMethod vlastnost by byla vyvolána předtím, než konstruktor ACW měl šanci spustit, a to by došlo dříve, než konstruktor jazyka C# měl šanci spustit.

To je podporováno vytvořením instance LogTextBox prostřednictvím LogTextView(IntPtr, JniHandleOwnership) konstruktor, když ACW LogTextBox instance poprvé zadá spravovaný kód a potom vyvolá LogTextBox(Context, IAttributeSet, int) konstruktoru ve stejné instanci při spuštění konstruktoru ACW.

Pořadí událostí:

  1. Xml rozložení se načte do ContentView.

  2. Android vytvoří instanci grafu objektu Layout a vytvoří instanci monodroid.apidemo.LogTextBox , ACW pro LogTextBox .

  3. Monodroid.apidemo.LogTextBox konstruktor spustí android.widget.TextView konstruktor.

  4. Konstruktor TextView vyvolá monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() vyvolá LogTextBox.n_getDefaultMovementMethod(), který vyvolá TextView.n_GetDefaultMovementMethod(), který vyvolá Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer).

  6. Java.Lang.Object.GetObject<TextView>() kontroluje, jestli již existuje odpovídající instance jazyka C# pro popisovač . Pokud tam je, vrátí se. V tomto scénáři neexistuje, takže Objekt.GetObject<T>() musí vytvořit.

  7. Object.GetObject<T>() vyhledá konstruktor LogTextBox(IntPtr, JniHandleOwneship ), vyvolá jej, vytvoří mapování mezi popisovačem a vytvořenou instancí a vrátí vytvořenou instanci.

  8. TextView.n_GetDefaultMovementMethod() vyvolá getter vlastnosti LogTextBox.DefaultMovementMethod .

  9. Ovládací prvek se vrátí do konstruktoru android.widget.TextView , který dokončí provádění.

  10. Monodroid.apidemo.LogTextBox konstruktor spustí vyvolání TypeManager.Activate() .

  11. Konstruktor LogTextBox(Context, IAttributeSet, int) se spustí ve stejné instanci vytvořené v (7) .

  12. Pokud nelze najít konstruktor (IntPtr, JniHandleOwnership), vyvolá se výjimka System.MissingMethodException](xref:System.MissingMethodException).

Předčasné volání Dispose()

Mezi popisovačem JNI a odpovídající instancí jazyka C# existuje mapování. Java.Lang.Object.Dispose() přeruší toto mapování. Pokud obslužná rutina JNI zadá spravovaný kód po přerušení mapování, bude vypadat jako Aktivace Java a konstruktor (IntPtr, JniHandleOwnership) bude zkontrolován a vyvolán. Pokud konstruktor neexistuje, vyvolá se výjimka.

Například vzhledem k následující podtřídě spravovaného zalamovacího zalamovacího objektu s možností volání:

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

Pokud vytvoříme instanci, Dispose() z ní a způsobíme opětovné vytvoření spravované obálky s možností volání:

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

Program zemře:

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

Pokud podtřída obsahuje konstruktor (IntPtr, JniHandleOwnership), bude vytvořena nová instance typu. V důsledku toho se instance zobrazí jako "ztratit" všechna data instance, protože se jedná o novou instanci. (Všimněte si, že hodnota je null.)

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

Pouze Dispose() spravovaných volatelných obálkových podtříd, pokud víte, že objekt Java již nebude použit, nebo podtřída neobsahuje žádná data instance a konstruktor (IntPtr, JniHandleOwnership) byl poskytnut.

Spuštění aplikace

Když se spustí aktivita, služba atd., Android nejprve zkontroluje, jestli už existuje proces, který je spuštěný pro hostování aktivity, služby atd. Pokud takový proces neexistuje, vytvoří se nový proces, AndroidManifest.xml přečte a typ zadaný v atributu /manifest/application/@android:name se načte a vytvoří instance. Dále se vytvoří instance všech typů určených parametrem /manifest/application/provider/@android:name a vyvolá se jejich metoda ContentProvider.attachInfo%28 ). Xamarin.Android se k tomu připojí přidáním mono. MonoRuntimeProvider ContentProvider AndroidManifest.xml během procesu sestavení. Mono . Metoda MonoRuntimeProvider.attachInfo() zodpovídá za načtení modulu runtime Mono do procesu. Všechny pokusy o použití mono před tímto bodem selžou. ( Poznámka: To je důvod, proč typy, které podtřídy Android.App.Application musí poskytnout konstruktor (IntPtr, JniHandleOwnership), protože instance aplikace je vytvořena před Mono lze inicializovat.)

Jakmile se inicializace procesu dokončí, AndroidManifest.xml projděte si název třídy aktivity, služby atd. ke spuštění. Například atribut /manifest/application/activity/@android:name se používá k určení názvu aktivity, která se má načíst. U aktivit musí tento typ dědit android.app.Activity. Zadaný typ se načte prostřednictvím třídy.forName() (což vyžaduje, aby typ byl typem Java, a proto se vytvoří instance obálky volatelné pro Android). Vytvoření instance obálky volatelné pro Android aktivuje vytvoření instance odpovídajícího typu jazyka C#. Android pak vyvolá Activity.onCreate(Bundle), což způsobí vyvolání odpovídající aktivity Activity.OnCreate(Bundle) a vy jste mimo závody.