Architecture

Las aplicaciones de Xamarin.Android se ejecutan en el entorno de ejecución mono. Este entorno de ejecución se ejecuta en paralelo con la máquina virtual android Runtime (ART). Ambos entornos en tiempo de ejecución se ejecutan sobre el kernel de Linux y exponen varias API al código de usuario que permite a los desarrolladores acceder al sistema subyacente. El entorno de ejecución mono está escrito en el lenguaje C.

Puede usar system,System.IO, System.Net y el resto de las bibliotecas de clases de .NET para acceder a las instalaciones del sistema operativo Linux subyacentes.

En Android, la mayoría de las instalaciones del sistema como Audio, Gráficos, OpenGL y Telefonía no están disponibles directamente para las aplicaciones nativas, solo se exponen a través de las API de Java de Android Runtime que residen en uno de los espacios de nombres Java.* o android.* . La arquitectura es aproximadamente similar a la siguiente:

Diagrama de Mono y ART encima del kernel y debajo de enlaces de .NET/Java +

Los desarrolladores de Xamarin.Android acceden a las distintas características del sistema operativo llamando a las API de .NET que conocen (para el acceso de bajo nivel) o mediante las clases expuestas en los espacios de nombres de Android que proporcionan un puente a las API de Java expuestas por Android Runtime.

Para obtener más información sobre cómo las clases de Android se comunican con las clases de Android Runtime, consulte el documento diseño de API .

Paquetes de aplicación

Los paquetes de aplicaciones android son contenedores ZIP con una extensión de archivo .apk . Los paquetes de aplicaciones de Xamarin.Android tienen la misma estructura y diseño que los paquetes android normales, con las siguientes adiciones:

  • Los ensamblados de aplicación (que contienen IL) se almacenan sin comprimir dentro de la carpeta de ensamblados . Durante el inicio del proceso en Release, el archivo .apk es mmap() en el proceso y los ensamblados se cargan desde la memoria. Esto permite el inicio de la aplicación más rápido, ya que no es necesario extraer ensamblados antes de la ejecución.

  • Nota: La información de ubicación del ensamblado, como Assembly.Location y Assembly.CodeBase, no se puede confiar en las compilaciones de versión. No existen como entradas distintas del sistema de archivos y no tienen ninguna ubicación utilizable.

  • Las bibliotecas nativas que contienen el entorno de ejecución mono están presentes en el archivo .apk . Una aplicación de Xamarin.Android debe contener bibliotecas nativas para las arquitecturas Android deseadas o dirigidas, por ejemplo, armeabi , armeabi-v7a , x86 . Las aplicaciones de Xamarin.Android no se pueden ejecutar en una plataforma a menos que contenga las bibliotecas en tiempo de ejecución adecuadas.

Las aplicaciones de Xamarin.Android también contienen contenedores que se pueden llamar a Android para permitir que Android llame a código administrado.

Contenedores que se pueden llamar de Android

  • Los contenedores invocables de Android son un puente JNI que se usa en cualquier momento que el entorno de ejecución de Android necesita invocar código administrado. Los contenedores invocables de Android son cómo se pueden invalidar los métodos virtuales y se pueden implementar interfaces de Java. Para más información, consulte la documentación de introducción a la integración de Java.

Contenedores que se pueden llamar administrados

Los contenedores que se pueden llamar administrados son un puente JNI que se usa en cualquier momento que el código administrado necesita invocar código Android y proporcionar compatibilidad para invalidar métodos virtuales e implementar interfaces de Java. Todos los espacios de nombres Android.* y relacionados son contenedores que se pueden llamar administrados generados a través del enlace .jar. Los contenedores que se pueden llamar administrados son responsables de convertir entre tipos administrados y Android e invocar los métodos de la plataforma Android subyacentes a través de JNI.

Cada contenedor administrado al que se puede llamar contiene una referencia global de Java, a la que se puede acceder a través de la propiedad Android.Runtime.IJavaObject.Handle . Las referencias globales se usan para proporcionar la asignación entre instancias de Java e instancias administradas. Las referencias globales son un recurso limitado: los emuladores solo permiten que existan 2000 referencias globales a la vez, mientras que la mayoría del hardware permite que haya más de 52 000 referencias globales a la vez.

Para realizar un seguimiento de cuándo se crean y destruyen las referencias globales, puede establecer la propiedad del sistema debug.mono.log para que contenga gref.

Las referencias globales se pueden liberar explícitamente llamando a Java.Lang.Object.Dispose() en el contenedor al que se puede llamar. Esto quitará la asignación entre la instancia de Java y la instancia administrada y permitirá que se recopile la instancia de Java. Si se vuelve a acceder a la instancia de Java desde código administrado, se creará un nuevo contenedor al que se puede llamar.

Se debe tener cuidado al eliminar contenedores que se pueden llamar administrados si la instancia se puede compartir accidentalmente entre subprocesos, ya que la eliminación de la instancia afectará a las referencias de cualquier otro subproceso. Para mayor seguridad, solo Dispose() las instancias que se han asignado a través newde o desde métodos que sabe siempre asignan nuevas instancias y no instancias almacenadas en caché que pueden provocar el uso compartido accidental de instancias entre subprocesos.

Subclases de contenedor que se pueden llamar administradas

Las subclases de contenedor que se pueden llamar administradas son donde puede residir toda la lógica específica de la aplicación "interesante". Entre ellas se incluyen subclases personalizadas Android.App.Activity (como el tipo Activity1 en la plantilla de proyecto predeterminada). (En concreto, estas son subclases Java.Lang.Object que no contienen un atributo personalizado RegisterAttribute o RegisterAttribute.DoNotGenerateAcw es false, que es el valor predeterminado).

Al igual que los contenedores que se pueden llamar administrados, las subclases contenedoras administradas que se pueden llamar también contienen una referencia global, accesible a través de la propiedad Java.Lang.Object.Handle . Al igual que con los contenedores que se pueden llamar administrados, las referencias globales se pueden liberar explícitamente mediante una llamada a Java.Lang.Object.Dispose(). A diferencia de los contenedores que se pueden llamar administrados, se debe tener mucho cuidado antes de eliminar estas instancias, ya que Dispose()-ing de la instancia interrumpirá la asignación entre la instancia de Java (una instancia de un contenedor invocable de Android) y la instancia administrada.

Activación de Java

Cuando se crea un contenedor que se puede llamar de Android (ACW) a partir de Java, el constructor de ACW hará que se invoque el constructor de C# correspondiente. Por ejemplo, el ACW para MainActivity contendrá un constructor predeterminado que invocará el constructor predeterminado de MainActivity. (Esto se realiza a través de la llamada TypeManager.Activate() dentro de los constructores de ACW).

Hay otra firma de constructor de consecuencia: el constructor (IntPtr, JniHandleOwnership). El constructor (IntPtr, JniHandleOwnership) se invoca cada vez que un objeto java se expone al código administrado y se debe construir un contenedor que se puede llamar administrado para administrar el identificador JNI. Esto suele hacerse automáticamente.

Hay dos escenarios en los que el constructor (IntPtr, JniHandleOwnership) debe proporcionarse manualmente en una subclase contenedora administrada:

  1. Android.App.Application está subclase. La aplicación es especial; El constructor de Applicaton predeterminado nunca se invocará y, en su lugar, se debe proporcionar el constructor (IntPtr, JniHandleOwnership).

  2. Invocación de método virtual desde un constructor de clase base.

Tenga en cuenta que (2) es una abstracción filtrada. En Java, como en C#, las llamadas a métodos virtuales desde un constructor siempre invocan la implementación del método más derivado. Por ejemplo, el constructor TextView(Context, AttributeSet, int) invoca el método virtual TextView.getDefaultMovementMethod(), que se enlaza como la propiedad TextView.DefaultMovementMethod. Por lo tanto, si un tipo LogTextBox fuera a (1) subclase TextView, (2) invalide TextView.DefaultMovementMethod y (3) active una instancia de esa clase a través de XML, la propiedad DefaultMovementMethod invalidada se invocaría antes de que el constructor acW tuviera la oportunidad de ejecutarse y se produciría antes de que el constructor de C# tuviera la oportunidad de ejecutarse.

Esto se admite mediante la creación de instancias de LogTextBox a través del constructor LogTextView(IntPtr, JniHandleOwnership) cuando la instancia de LOGTextBox de ACW entra primero en código administrado y, a continuación, invoca el constructor LogTextBox(Context, IAttributeSet, int)en la misma instancia cuando se ejecuta el constructor ACW.

Orden de eventos:

  1. El XML de diseño se carga en una clase ContentView.

  2. Android crea una instancia del gráfico de objetos Layout y crea una instancia de monodroid.apidemo.LogTextBox , acW para LogTextBox .

  3. El constructor monodroid.apidemo.LogTextBox ejecuta el constructor android.widget.TextView .

  4. El constructor TextView invoca monodroid.apidemo.LogTextBox.getDefaultMovementMethod() .

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

  6. Java.Lang.Object.GetObject<TextView>() comprueba si ya hay una instancia de C# correspondiente para controlar . Si lo hay, se devuelve. En este escenario, no existe, por lo que Object.GetObject<T>() debe crear uno.

  7. Object.GetObject<T>() busca el constructor LogTextBox(IntPtr, JniHandleOwneship), lo invoca, crea una asignación entre el identificador y la instancia creada y devuelve la instancia creada.

  8. TextView.n_GetDefaultMovementMethod() invoca el captador de la propiedad LogTextBox.DefaultMovementMethod .

  9. El control vuelve al constructor android.widget.TextView , que finaliza la ejecución.

  10. El constructor monodroid.apidemo.LogTextBox se ejecuta, invocando TypeManager.Activate() .

  11. El constructor LogTextBox(Context, IAttributeSet, int) se ejecuta en la misma instancia creada en (7).

  12. Si no se encuentra el constructor (IntPtr, JniHandleOwnership), se producirá una excepción System.MissingMethodException](xref:System.MissingMethodException).

Llamadas a Dispose() prematuras

Hay una asignación entre un identificador JNI y la instancia de C# correspondiente. Java.Lang.Object.Dispose() interrumpe esta asignación. Si un identificador JNI entra en código administrado después de que se haya interrumpido la asignación, se parecerá a la activación de Java y se comprobará e invocará el constructor (IntPtr, JniHandleOwnership ). Si el constructor no existe, se producirá una excepción.

Por ejemplo, dada la siguiente subclase De wraper con llamada administrada:

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

Si creamos una instancia, Dispose() de ella y hace que se vuelva a crear el contenedor que se puede llamar administrado:

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

El programa 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

Si la subclase contiene un constructor (IntPtr, JniHandleOwnership), se creará una nueva instancia del tipo. Como resultado, la instancia aparecerá como "perder" todos los datos de instancia, ya que es una nueva instancia. (Tenga en cuenta que el valor es null).

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

Solo se han proporcionado subclases de contenedor que se pueden llamar administradas cuando sepa que el objeto Java ya no se usará, o la subclase no contiene datos de instancia y se ha proporcionado un constructor (IntPtr, JniHandleOwnership).

Inicio de la aplicación

Cuando una actividad, un servicio, etc. se inicia, Android comprobará primero si ya hay un proceso en ejecución para hospedar la actividad o el servicio, etc. Si no existe este proceso, se creará un nuevo proceso, se leerá el AndroidManifest.xml y se cargará la instancia del tipo especificado en el atributo /manifest/application/@android:name . A continuación, se crean instancias de todos los tipos especificados por los valores de atributo /manifest/application/provider/@android:name y se invoca su método ContentProvider.attachInfo%28). Xamarin.Android se enlaza a esto agregando un mono. MonoRuntimeProvider ContentProvider para AndroidManifest.xml durante el proceso de compilación. El mono. El método MonoRuntimeProvider.attachInfo() es responsable de cargar el entorno de ejecución mono en el proceso. Se producirá un error en los intentos de usar Mono antes de este punto. ( Nota: Este es el motivo por el que los tipos que subclase Android.App.Application necesitan proporcionar un constructor (IntPtr, JniHandleOwnership), ya que la instancia de aplicación se crea antes de que Se pueda inicializar Mono).

Una vez completada la inicialización del proceso, AndroidManifest.xml se consulta para buscar el nombre de clase de la actividad/servicio/etc. que se va a iniciar. Por ejemplo, el atributo /manifest/application/activity/@android:name se usa para determinar el nombre de una actividad que se va a cargar. Para Activities, este tipo debe heredar android.app.Activity. El tipo especificado se carga a través de Class.forName() (que requiere que el tipo sea un tipo Java, por lo que se crean instancias de los contenedores invocables de Android). La creación de una instancia de contenedor invocable de Android desencadenará la creación de una instancia del tipo de C# correspondiente. A continuación, Android invocará Activity.onCreate(Bundle) , que hará que se invoque la actividad correspondiente.OnCreate(Bundle) y esté fuera de las carreras.