Udostępnij za pośrednictwem


Architektura

Aplikacje platformy Xamarin.Android działają w środowisku wykonywania mono. To środowisko wykonywania działa równolegle z maszyną wirtualną Środowiska uruchomieniowego systemu Android (ART). Oba środowiska uruchomieniowe działają na bazie jądra systemu Linux i uwidaczniają różne interfejsy API kodu użytkownika, który umożliwia deweloperom dostęp do podstawowego systemu. Środowisko uruchomieniowe Mono jest napisane w języku C.

Aby uzyskać dostęp do bazowych obiektów systemu operacyjnego Linux, można użyć systemu, System.IO, System.Net i pozostałych bibliotek klas platformy .NET.

W systemie Android większość urządzeń systemowych, takich jak Audio, Graphics, OpenGL i Telefonia, nie jest dostępna bezpośrednio dla aplikacji natywnych, są one udostępniane tylko za pośrednictwem interfejsów API Java środowiska uruchomieniowego systemu Android znajdujących się w jednej z przestrzeni nazw Java.* lub android.* przestrzeni nazw. Architektura wygląda mniej więcej tak:

Diagram mono i ART nad jądrem i poniżej powiązań .NET/Java +

Deweloperzy platformy Xamarin.Android uzyskują dostęp do różnych funkcji w systemie operacyjnym, wywołując interfejsy API platformy .NET, które znają (w przypadku dostępu niskiego poziomu) lub używając klas uwidocznionych w przestrzeniach nazw systemu Android, które zapewniają mostek do interfejsów API języka Java udostępnianych przez środowisko uruchomieniowe systemu Android.

Aby uzyskać więcej informacji na temat sposobu komunikowania się klas systemu Android z klasami środowiska Uruchomieniowego systemu Android, zobacz dokument Projekt interfejsu API.

Pakiety aplikacji

Pakiety aplikacji systemu Android to kontenery ZIP z rozszerzeniem .apk pliku. Pakiety aplikacji platformy Xamarin.Android mają taką samą strukturę i układ jak zwykłe pakiety systemu Android z następującymi dodatkami:

  • Zestawy aplikacji (zawierające IL) są przechowywane nieskompresowane w folderze zestawów . Podczas uruchamiania procesu w wersji kompilacje .apk jest mmap() w procesie, a zestawy są ładowane z pamięci. Pozwala to na szybsze uruchamianie aplikacji, ponieważ zestawy nie muszą być wyodrębniane przed wykonaniem.

  • Uwaga: informacje o lokalizacji zestawu, takie jak Assembly.Location i Assembly.CodeBase, nie mogą być oparte na kompilacjach wydania. Nie istnieją one jako odrębne wpisy systemu plików i nie mają dostępnej lokalizacji.

  • Biblioteki natywne zawierające środowisko uruchomieniowe Mono znajdują się w .apk . Aplikacja Xamarin.Android musi zawierać biblioteki natywne dla żądanych/docelowych architektur systemu Android, np. armeabi , armeabi-v7a , x86 . Aplikacje platformy Xamarin.Android nie mogą działać na platformie, chyba że zawierają odpowiednie biblioteki środowiska uruchomieniowego.

Aplikacje platformy Xamarin.Android zawierają również wywołania otoki z możliwością wywołania systemu Android do kodu zarządzanego.

Wywoływane otoki systemu Android

  • Wywoływane otoki systemu Android to mostek JNI , który jest używany w dowolnym momencie, gdy środowisko uruchomieniowe systemu Android musi wywołać zarządzany kod. Otoki z możliwością wywoływania systemu Android to sposób, w jaki można zastąpić metody wirtualne, a interfejsy Języka Java można zaimplementować. Aby uzyskać więcej informacji, zobacz dokumentację z omówieniem integracji języka Java.

Zarządzane otoki z możliwością wywołania

Zarządzane otoki z możliwością wywołania to mostek JNI, który jest używany w dowolnym momencie, gdy zarządzany kod musi wywołać kod systemu Android i zapewnić obsługę zastępowania metod wirtualnych i implementowania interfejsów Java. Całe przestrzenie nazw Android.* i powiązane przestrzenie nazw są zarządzanymi otokami wywoływanymi wygenerowanymi za pośrednictwem powiązania .jar. Zarządzane otoki z możliwością wywołania są odpowiedzialne za konwertowanie między typami zarządzanymi i androidami oraz wywoływanie podstawowych metod platformy Android za pośrednictwem interfejsu JNI.

Każda utworzona zarządzana otoka wywołująca zawiera globalne odwołanie java, które jest dostępne za pośrednictwem właściwości Android.Runtime.IJavaObject.Handle . Odwołania globalne służą do zapewniania mapowania między wystąpieniami Języka Java i wystąpieniami zarządzanymi. Odwołania globalne są ograniczonym zasobem: emulatory umożliwiają jednocześnie istnienie tylko 2000 odwołań globalnych, podczas gdy większość sprzętu zezwala na istnienie ponad 52 000 odwołań globalnych naraz.

Aby śledzić, kiedy odwołania globalne są tworzone i niszczone, można ustawić właściwość systemu debug.mono.log na wartość gref.

Odwołania globalne można jawnie zwolnić przez wywołanie metody Java.Lang.Object.Dispose() w zarządzanej otoce z możliwością wywołania. Spowoduje to usunięcie mapowania między wystąpieniem Java a wystąpieniem zarządzanym i zezwoleniem na zbieranie wystąpienia Java. Jeśli wystąpienie języka Java jest ponownie dostępne z poziomu kodu zarządzanego, zostanie utworzona nowa otoka z możliwością wywołania zarządzanego.

Należy zachować ostrożność w przypadku bycia zarządzanymi otokami z możliwością wywołania, jeśli wystąpienie może być przypadkowo współużytkowane między wątkami, ponieważ usunięcie wystąpienia będzie miało wpływ na odwołania z innych wątków. W przypadku maksymalnego bezpieczeństwa tylko Dispose() wystąpienia, które zostały przydzielone za pośrednictwem new metod lub z metod, które są zawsze przydzielane nowe wystąpienia, a nie wystąpienia buforowane, które mogą spowodować przypadkowe udostępnianie wystąpień między wątkami.

Zarządzane podklasy otoki wywołującej

Zarządzane podklasy otoki wywoływanej są tam, gdzie może istnieć cała "interesująca" logika specyficzna dla aplikacji. Należą do nich niestandardowe podklasy Android.App.Activity (takie jak typ Activity1 w domyślnym szablonie projektu). (W szczególności są to dowolne Podklasy Java.Lang.Object, które nie zawierają atrybutu niestandardowego RegisterAttribute lub RegisterAttribute.DoNotGenerateAcw jest fałszywe, co jest wartością domyślną).

Podobnie jak zarządzane otoki wywoływane, zarządzane podklasy otoki wywołujące zawierają również globalne odwołanie dostępne za pośrednictwem właściwości Java.Lang.Object.Handle . Podobnie jak w przypadku zarządzanych otoek z możliwością wywoływania można jawnie zwolnić odwołania globalne przez wywołanie metody Java.Lang.Object.Dispose(). W przeciwieństwie do zarządzanych otoek z możliwością wywołania należy zachować szczególną ostrożność przed usunięciem takich wystąpień, ponieważ usuwanie()-ing wystąpienia wystąpienia spowoduje przerwanie mapowania między wystąpieniem języka Java (wystąpieniem otoki z możliwością wywołania systemu Android) i wystąpieniem zarządzanym.

Aktywacja w języku Java

Po utworzeniu otoki z możliwością wywołania systemu Android (ACW) na podstawie języka Java konstruktor ACW spowoduje wywołanie odpowiedniego konstruktora języka C#. Na przykład acW for MainActivity będzie zawierać konstruktor domyślny, który wywoła domyślny konstruktor MainActivity. (Odbywa się to za pośrednictwem Wywołanie TypeManager.Activate() w konstruktorach ACW).

Istnieje jeszcze jeden podpis konstruktora konsekwencji: konstruktor (IntPtr, JniHandleOwnership). Konstruktor JniHandleOwnership (IntPtr, JniHandleOwnership) jest wywoływany za każdym razem, gdy obiekt Java jest uwidoczniony w kodzie zarządzanym, a zarządzać dojściem JNI musi zostać skonstruowany zarządzany obiekt Callable Wrapper. Zwykle odbywa się to automatycznie.

Istnieją dwa scenariusze, w których konstruktor (IntPtr, JniHandleOwnership) musi być ręcznie podany w podklasie Zarządzanej otoki wywołującej:

  1. Android.App.Application jest podklasowana. Aplikacja jest specjalna. Zamiast tego należy podać domyślny konstruktor Applicaton, a zamiast tego należy podać konstruktor (IntPtr, JniHandleOwnership).

  2. Wywołanie metody wirtualnej z konstruktora klasy bazowej.

Należy pamiętać, że (2) to nieszczelna abstrakcja. W języku Java, podobnie jak w języku C#, wywołania metod wirtualnych z konstruktora zawsze wywołują najbardziej pochodną implementację metody. Na przykład konstruktor TextView(Context, AttributeSet, int) wywołuje metodę wirtualną TextView.getDefaultMovementMethod(), która jest powiązana z właściwością TextView.DefaultMovementMethod. W związku z tym, jeśli typ LogTextBox to (1) podklasa TextView, (2) przesłanianie kontrolki TextView.DefaultMovementMethod, a (3) aktywowanie wystąpienia tej klasy za pomocą kodu XML, przesłonięta właściwość DefaultMovementMethod zostanie wywołana, zanim konstruktor ACW miał szansę wykonać.

Jest to obsługiwane przez utworzenie wystąpienia klasy LogTextBox za pomocą konstruktora LogTextView(IntPtr, JniHandleOwnership), gdy wystąpienie ACW LogTextBox najpierw wprowadza kod zarządzany, a następnie wywoływanie konstruktora LogTextBox(Context, IAttributeSet, int) w tym samym wystąpieniu, gdy konstruktor ACW jest wykonywany.

Kolejność zdarzeń:

  1. Kod XML układu jest ładowany do elementu ContentView.

  2. System Android tworzy wystąpienie grafu obiektów Layout i tworzy wystąpienie elementu monodroid.apidemo.LogTextBox , acW dla logTextBox .

  3. Konstruktor monodroid.apidemo.LogTextBox wykonuje konstruktor android.widget.TextView .

  4. Konstruktor TextView wywołuje element monodroid.apidemo.LogTextBox.getDefaultMovementMethod().

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() wywołuje metodę LogTextBox.n_getDefaultMovementMethod(), która wywołuje TextView.n_GetDefaultMovementMethod(), która wywołuje element Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer).

  6. Java.Lang.Object.GetObject<TextView>() sprawdza, czy istnieje już odpowiednie wystąpienie języka C# do obsługi . Jeśli tak jest, zostanie zwrócony. W tym scenariuszu nie ma, więc obiekt Object.GetObject<T>() musi go utworzyć.

  7. Object.GetObject<T>() szuka konstruktora LogTextBox(IntPtr, JniHandleOwneship), wywołuje go, tworzy mapowanie między uchwytem a utworzonym wystąpieniem i zwraca utworzone wystąpienie.

  8. TextView.n_GetDefaultMovementMethod() wywołuje metodę getter właściwości LogTextBox.DefaultMovementMethod .

  9. Kontrolka powraca do konstruktora android.widget.TextView , który kończy wykonywanie.

  10. Zostanie wykonany konstruktor monodroid.apidemo.LogTextBox, wywołując funkcję TypeManager.Activate().

  11. Konstruktor LogTextBox(Context, IAttributeSet, int) jest wykonywany na tym samym wystąpieniu utworzonym w (7) .

  12. Jeśli nie można odnaleźć konstruktora (IntPtr, JniHandleOwnership), zostanie zgłoszony wyjątek System.MissingMethodException](xref:System.MissingMethodException).

Przedwczesne wywołania dispose()

Istnieje mapowanie między dojściem JNI a odpowiednim wystąpieniem języka C#. Java.Lang.Object.Dispose() przerywa to mapowanie. Jeśli dojście JNI wprowadza kod zarządzany po przerwaniu mapowania, wygląda na to, że aktywacja Języka Java, a konstruktor (IntPtr, JniHandleOwnership) zostanie sprawdzony i wywołany. Jeśli konstruktor nie istnieje, zostanie zgłoszony wyjątek.

Na przykład biorąc podklasę Managed Callable Wraper:

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

Jeśli utworzymy wystąpienie, dispose() i spowodujemy ponowne utworzenie zarządzanej otoki z możliwością wywołania:

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

Program umrze:

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

Jeśli podklasa zawiera konstruktor (IntPtr, JniHandleOwnership), zostanie utworzone nowe wystąpienie typu. W związku z tym wystąpienie będzie wyglądać na "utratę" wszystkich danych wystąpienia, ponieważ jest to nowe wystąpienie. (Pamiętaj, że wartość ma wartość null).

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

Tylko dispose() zarządzanych podklas otoki wywoływanej, gdy wiesz, że obiekt Java nie będzie już używany lub podklasa nie zawiera danych wystąpienia i (IntPtr, JniHandleOwnership) konstruktor został udostępniony.

Uruchamianie aplikacji

Po uruchomieniu działania, usługi itp. system Android najpierw sprawdzi, czy istnieje już proces uruchomiony w celu hostowania działania/usługi/itp. Jeśli taki proces nie istnieje, zostanie utworzony nowy proces, AndroidManifest.xml zostanie odczytany, a typ określony w atrybucie /manifest/application/@android:name zostanie załadowany i utworzony. Następnie wszystkie typy określone przez metodę /manifest/application/provider/@android:name są tworzone i mają wywołaną metodę ContentProvider.attachInfo%28). Środowisko Xamarin.Android jest w tym elementem zaczepianym przez dodanie mono. MonoRuntimeProvider ContentProvider AndroidManifest.xml podczas procesu kompilacji. Mono . Metoda MonoRuntimeProvider.attachInfo() jest odpowiedzialna za ładowanie środowiska uruchomieniowego Mono do procesu. Wszelkie próby użycia platformy Mono przed tym punktem zakończy się niepowodzeniem. ( Uwaga: dlatego typy, które podklasy Android.App.Application muszą podać konstruktor (IntPtr, JniHandleOwnership), ponieważ wystąpienie aplikacji jest tworzone przed zainicjowaniem mono).

Po zakończeniu inicjowania procesu należy się skonsultować, AndroidManifest.xml aby znaleźć nazwę klasy działania/usługi/itp. do uruchomienia. Na przykład atrybut /manifest/application/activity/@android:name służy do określenia nazwy działania do załadowania. W przypadku działań ten typ musi dziedziczyć android.app.Activity. Określony typ jest ładowany za pośrednictwem klasy.forName() (co wymaga, aby typ był typem Języka Java, stąd android Callable Wrappers), a następnie tworzył wystąpienie. Utworzenie wystąpienia otoki wywołującej systemu Android spowoduje wyzwolenie utworzenia wystąpienia odpowiadającego mu typu C#. Następnie system Android wywoła element Activity.onCreate(Bundle), co spowoduje wywołanie odpowiedniego elementu Activity.OnCreate(Bundle) i nastąpi wyłączenie do wyścigów.