Architecture

Приложения Xamarin.Android выполняются в среде выполнения Mono. Эта среда выполнения выполняется параллельно с виртуальной машиной среды выполнения Android (ART). Обе среды выполнения выполняются поверх ядра Linux и предоставляют различные API-интерфейсы пользовательского кода, который позволяет разработчикам получать доступ к базовой системе. Среда выполнения Mono написана на языке C.

Вы можете использовать system, System.IO, System.Net и остальные библиотеки классов .NET для доступа к базовым операционным системам Linux.

В Android большинство системных объектов, таких как аудио, графика, OpenGL и телефония, недоступны напрямую для собственных приложений, они предоставляются только через API Java среды выполнения Android, которые находятся в одном из пространств имен Java.* или в пространствах имен Android.* . Архитектура примерно такая:

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

Разработчики Xamarin.Android получают доступ к различным функциям операционной системы, вызывая API .NET, которые они знают (для низкоуровневого доступа) или используя классы, предоставляемые в пространствах имен Android, которые предоставляют мост к API Java, предоставляемым средой выполнения Android.

Дополнительные сведения о том, как классы Android взаимодействуют с классами среды выполнения Android, см. в документе конструктора API.

Пакеты приложений

Пакеты приложений Android — это zip-контейнеры с расширением .apk файла. Пакеты приложений Xamarin.Android имеют ту же структуру и макет, что и обычные пакеты Android с следующими дополнениями:

  • Сборки приложений (содержащие IL) хранятся без сжатия в папке сборок. Во время запуска процесса в выпуске выполняется сборка .apk mmap() в процессе, а сборки загружаются из памяти. Это позволяет ускорить запуск приложения, так как сборки не нужно извлекать до выполнения.

  • Примечание. Сведения о расположении сборки, такие как Assembly.Location и Assembly.CodeBase, нельзя полагаться на сборки выпуска. Они не существуют как отдельные записи файловой системы, и они не имеют удобного расположения.

  • Собственные библиотеки, содержащие среду выполнения Mono, присутствуют в .apk . Приложение Xamarin.Android должно содержать собственные библиотеки для нужных и целевых архитектур Android, например armeabi , armeabi-v7a , x86 . Приложения Xamarin.Android не могут работать на платформе, если они не содержат соответствующие библиотеки среды выполнения.

Приложения Xamarin.Android также содержат вызываемые оболочки Android, чтобы разрешить Android вызывать управляемый код.

Вызываемые программы-оболочки Android

  • Вызываемые оболочки Android — это мост JNI , который используется в любой момент, когда среда выполнения Android должна вызывать управляемый код. Вызываемые оболочки Android — это способ переопределения виртуальных методов и реализации интерфейсов Java. Дополнительные сведения см. в документации по интеграции Java.

Управляемые вызываемые оболочки

Управляемые вызываемые оболочки — это мост JNI, который используется в любое время, когда управляемый код должен вызывать код Android и обеспечивать поддержку переопределения виртуальных методов и реализации интерфейсов Java. Все пространства имен Android.* и связанные пространства имен являются управляемыми вызываемыми оболочками, созданными с помощью привязки .jar. Управляемые вызываемые оболочки отвечают за преобразование между управляемыми и android-типами и вызовами базовых методов платформы Android через JNI.

Каждая созданная вызываемая оболочка содержит глобальную ссылку Java, доступную через свойство Android.Runtime.IJavaObject.Handle . Глобальные ссылки используются для сопоставления между экземплярами Java и управляемыми экземплярами. Глобальные ссылки — это ограниченный ресурс: эмуляторы позволяют одновременно существовать только 2000 глобальных ссылок, в то время как большинство аппаратных ресурсов позволяет существовать более 52 000 глобальных ссылок.

Чтобы отслеживать создание и уничтожение глобальных ссылок, можно задать для свойства системы debug.mono.log значение gref.

Глобальные ссылки можно явно освободить, вызвав Java.Lang.Object.Dispose() в управляемой вызываемой оболочке. Это приведет к удалению сопоставления между экземпляром Java и управляемым экземпляром и позволяет собирать экземпляр Java. Если экземпляр Java повторно обращается из управляемого кода, для него будет создана новая вызываемая оболочка.

При удалении управляемых вызываемых оболочков необходимо выполнять осторожность, если экземпляр может быть непреднамеренно совместно предоставлен между потоками, так как удаление экземпляра будет влиять на ссылки со всех других потоков. Для максимальной безопасности только Dispose() экземпляров, которые были выделены через new методы, которые всегда выделяют новые экземпляры, а не кэшированные экземпляры, которые могут привести к случайному совместному использованию экземпляров между потоками.

Подклассы управляемого вызываемого оболочки

Управляемые вызываемые подклассы оболочки находятся в том месте, где может существовать любая логика для конкретного приложения. К ним относятся пользовательские подклассы Android.App.Activity (например, тип Activity1 в шаблоне проекта по умолчанию). (В частности, это все Подклассы Java.Lang.Object, которые не содержат настраиваемый атрибут RegisterAttribute или RegisterAttribute.DoNotGenerateAcw, является false, который является значением по умолчанию.)

Как и управляемые вызываемые оболочки, управляемые подклассы-оболочки также содержат глобальную ссылку, доступную через свойство Java.Lang.Object.Handle . Как и в управляемых вызываемых оболочках, глобальные ссылки можно освободить явным образом, вызвав Java.Lang.Object.Dispose(). В отличие от управляемых вызываемых оболочки, перед удалением таких экземпляров следует тщательно следить за тем, как dispose()-ing экземпляра прерывает сопоставление между экземпляром Java (экземпляр вызываемой оболочкой Android) и управляемым экземпляром.

Активация Java

При создании вызываемого оболочки Android (ACW) из Java конструктор ACW приведет к вызову соответствующего конструктора C#. Например, ACW для MainActivity будет содержать конструктор по умолчанию, который вызовет конструктор MainActivity по умолчанию. (Это делается с помощью Вызов TypeManager.Activate() в конструкторах ACW.)

Существует еще одна сигнатура конструктора последствий: конструктор (IntPtr, JniHandleOwnership). Конструктор (IntPtr, JniHandleOwnership) вызывается всякий раз, когда объект Java предоставляется управляемому коду, а управляемому вызываемому оболочке необходимо создать для управления дескриптором JNI. Обычно это выполняется автоматически.

Существует два сценария, в которых конструктор (IntPtr, JniHandleOwnership) должен быть предоставлен вручную в подклассе управляемого вызываемого оболочки:

  1. Android.App.Application является подклассом. Приложение является специальным; конструктор Applicationaton по умолчанию никогда не будет вызываться, а конструктор (IntPtr, JniHandleOwnership) должен быть предоставлен.

  2. Вызов виртуального метода из конструктора базового класса.

Обратите внимание, что (2) является утечкой абстракции. В Java, как и в C#, вызовы виртуальных методов из конструктора всегда вызывают самую производную реализацию метода. Например, конструктор TextView(Context, AttributeSet, int) вызывает виртуальный метод TextView.getDefaultMovementMethod(), привязанный к свойству TextView.DefaultMovementMethod. Таким образом, если тип LogTextBox должен был (1) подкласс TextView, (2) переопределить TextView.DefaultMovementMethod и (3) активировать экземпляр этого класса с помощью XML, переопределенное свойство DefaultMovementMethod будет вызываться до выполнения конструктора ACW, и это произойдет до выполнения конструктора C#.

Это поддерживается путем создания экземпляра LogTextBox с помощью конструктора LogTextView(IntPtr, JniHandleOwnership), когда экземпляр ACW LogTextBox сначала вводит управляемый код, а затем вызывает конструктор LogTextBox(Context, IAttributeSet, int) в том же экземпляре, когда выполняется конструктор ACW.

Порядок событий:

  1. XML макета загружается в ContentView.

  2. Android создает экземпляр графа объектов Layout и создает экземпляр monodroid.apidemo.LogTextBox , ACW для LogTextBox.

  3. Конструктор monodroid.apidemo.LogTextBox выполняет конструктор android.widget.TextView .

  4. Конструктор TextView вызывает monodroid.apidemo.LogTextBox.getDefaultMovementMethod().

  5. monodroid.apidemo.LogTextBox.getDefaultMovementMethod() вызывает LogTextBox.n_getDefaultMovementMethod(), который вызывает TextView.n_GetDefaultMovementMethod(), который вызывает Java.Lang.Object.GetObject<TextView> (handle, JniHandleOwnership.DoNotTransfer).

  6. Проверка Java.Lang.Object.GetObject<TextView>() для просмотра наличия соответствующего экземпляра C# для дескриптора. Если есть, возвращается. В этом сценарии нет, поэтому объект.GetObject<T>() должен создать его.

  7. Object.GetObject<T>() ищет конструктор LogTextBox(IntPtr, JniHandleOwneship), вызывает его, создает сопоставление между дескриптором и созданным экземпляром и возвращает созданный экземпляр.

  8. TextView.n_GetDefaultMovementMethod() вызывает метод получения свойства LogTextBox.DefaultMovementMethod .

  9. Элемент управления возвращается в конструктор android.widget.TextView , который завершает выполнение.

  10. Конструктор monodroid.apidemo.LogTextBox выполняется, вызывая TypeManager.Activate().

  11. Конструктор LogTextBox(Context, IAttributeSet, int) выполняется в том же экземпляре, созданном в (7).

  12. Если конструктор (IntPtr, JniHandleOwnership) не найден, будет создан метод System.MissingMethodException](xref:System.MissingMethodException).

Преждевременные вызовы Dispose()

Существует сопоставление между дескриптором JNI и соответствующим экземпляром C#. Java.Lang.Object.Dispose() нарушает это сопоставление. Если дескриптор JNI вводит управляемый код после разрыва сопоставления, он выглядит как активация Java, а конструктор (IntPtr, JniHandleOwnership) будет проверка для и вызывается. Если конструктор не существует, создается исключение.

Например, учитывая следующий подкласс управляемого вызываемого оболочки:

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

Если мы создадим экземпляр, dispose() и создадим вызываемую оболочку:

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

Программа умрет:

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

Если подкласс содержит конструктор (IntPtr, JniHandleOwnership),будет создан новый экземпляр типа. В результате экземпляр будет "потерять" все данные экземпляра, так как это новый экземпляр. (Обратите внимание, что значение равно null.)

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

Только dispose() управляемых вызываемых подклассов оболочки, если вы знаете, что объект Java больше не будет использоваться, или подкласс не содержит данных экземпляра, а конструктор (IntPtr, JniHandleOwnership) предоставлен.

Запуск приложения

При запуске действия, службы и т. д. Android сначала проверка, чтобы узнать, существует ли процесс, выполняющийся для размещения действия или службы и т. д. Если такой процесс не существует, создается новый процесс, AndroidManifest.xml считывается, а тип, указанный в атрибуте /manifest/application/@android:name, загружается и создается экземпляр. Далее создаются экземпляры всех типов, указанных в атрибутах /manifest/application/application/provider/@android:name , и вызывается метод ContentProvider.attachInfo%28 . Xamarin.Android перехватчики в это путем добавления моно. MonoRuntimeProvider ContentProvider для AndroidManifest.xml во время процесса сборки. Моно. Метод MonoRuntimeProvider.attachInfo() отвечает за загрузку среды выполнения Mono в процесс. Все попытки использовать Mono до этой точки завершаются ошибкой. ( Примечание. Поэтому типы, которые подкласс Android.App.Application должны предоставить конструктор (IntPtr, JniHandleOwnership), так как экземпляр приложения создается перед инициализацией Mono.)

После завершения AndroidManifest.xml инициализации процесса необходимо найти имя класса действия или службы и т. д. для запуска. Например, атрибут /manifest/application/activity/@android:name используется для определения имени загружаемого действия. Для действий этот тип должен наследовать android.app.Activity. Указанный тип загружается через Class.forName() (который требует, чтобы тип был типом Java, поэтому вызываемые оболочки Android), затем создается экземпляр. Создание вызываемого экземпляра оболочки Android активирует создание экземпляра соответствующего типа C#. Затем Android вызовет Activity.onCreate(Bundle), что приведет к вызову соответствующего действия.OnCreate(Bundle), и вы отключаетсяе к гонкам.