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 funciones 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 en tiempo de ejecución de Android que residen en uno de los espacios de nombres .* de Javao en los espacios de nombres de Android.*. La arquitectura es aproximadamente como esta:

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

Los desarrolladores de Xamarin.Android acceden a las distintas características del sistema operativo mediante una llamada 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 más información sobre cómo se comunican las clases de Android 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 aplicación 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 assemblies. Durante el inicio del proceso en la versión, el archivo .apk se convierte en mmap() en el proceso y los ensamblados se cargan desde la memoria. Esto permite un inicio más rápido de la aplicación, ya que no es necesario extraer los ensamblados antes de la ejecución.

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

  • Las bibliotecas nativas que contienen 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 de Android deseadas o de destino, 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 que se pueden llamar de Android son un puente JNI que se usa cada vez que el entorno de ejecución de Android necesita invocar código administrado. Los contenedores a los que se puede llamar de Android son la forma en que se pueden invalidar los métodos virtuales y cómo se pueden implementar interfaces de Java. Consulte el documento Información general sobre la integración de Java para obtener más información.

Contenedores que se pueden llamar administrados

Los contenedores que se pueden llamar administrados son un puente JNI que se usa cada vez que el código administrado necesita invocar código Android y proporcionar compatibilidad para reemplazar métodos virtuales e implementar interfaces de Java. Todo android.* y los espacios de nombres 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 los tipos administrados y android e invocar los métodos subyacentes de la plataforma Android 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 existan más de 52 000 referencias globales a la vez.

Para realizar un seguimiento de cuándo se crean y destruyen 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 administrado. Esto quitará la asignación entre la instancia de Java y la instancia administrada y permitirá recopilar la instancia de Java. Si se vuelve a acceder a la instancia de Java desde código administrado, se creará un nuevo contenedor que se puede llamar administrado para ella.

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

Subclases de contenedor que se pueden llamar administradas

Las subclases de contenedor a las que se puede llamar administradas son donde puede haber toda la lógica específica de la aplicación "interesante". Estas incluyen subclases personalizadas de Android.App.Activity (como el tipo Activity1 en la plantilla de proyecto predeterminada). (En concreto, se trata de 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 de contenedor que se pueden llamar administradas 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 llamando a Java.Lang.Object.Dispose(). A diferencia de los contenedores que se pueden llamar administrados, se debe tener mucho cuidado antes de eliminar dichas instancias, ya que Dispose()-ing de la instancia interrumpirá la asignación entre la instancia de Java (una instancia de un contenedor que se puede llamar de Android) y la instancia administrada.

Activación de Java

Cuando se crea un contenedor ccable de Android (ACW) desde Java, el constructor 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 a TypeManager.Activate() dentro de los constructores ACW).

Hay otra firma de constructor de consecuencia: el constructor (IntPtr, JniHandleOwnership). El constructor (IntPtr, JniHandleOwnership) se invoca siempre que un objeto java se expone al código administrado y se debe construir un contenedor invocable administrado para administrar el identificador de JNI. Normalmente, esto se hace automáticamente.

Hay dos escenarios en los que el constructor (IntPtr, JniHandleOwnership) debe proporcionarse manualmente en una subclase Contenedor invocable administrado:

  1. Android.App.Application tiene subclases. La aplicación es especial; Nunca se invocará el constructor applicaton predeterminado 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 está enlazado como la propiedad TextView.DefaultMovementMethod. Por lo tanto, si un tipo LogTextBox fuera a (1) subclase TextView, (2) invalidar TextView.DefaultMovementMethody (3) activar 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 escribe primero código administrado y, a continuación, invoca el constructorLogTextBox(Context, IAttributeSet, int) en la misma instancia cuando se ejecuta el constructor ACW.

Orden de los eventos:

  1. El XML de diseño se carga en un objeto ContentView.

  2. Android crea una instancia del gráfico de objetos Layout y crea una instancia de monodroid.apidemo.LogTextBox , el 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 a Java.Lang.Object.GetObject TextView (handle, JniHandleOwnership.DoNotTransfer).

  6. Java.Lang.Object.GetObject TextView > () comprueba si ya hay una instancia de C# correspondiente para > . 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 > lo invoca, crea una asignación entre el identificador y la instancia creada y devuelve la instancia creada.

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

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

  10. Se ejecuta el constructor monodroid.apidemo.LogTextBox, que invoca a 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 produce 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 escribe código administrado después de que se haya roto la asignación, parece la activación de Java y el constructor (IntPtr, JniHandleOwnership) se comprobará e invocará. Si el constructor no existe, se producirá una excepción.

Por ejemplo, dada la siguiente subclase 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);
    }
}

Si creamos una instancia, Dispose() de ella, y provocamos 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 fallecerá:

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 parecerá "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 Dispose() de subclases de contenedor invocables administradas cuando sabe 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 se inicia una actividad, un servicio, etc., Android comprobará primero si ya hay un proceso en ejecución para hospedar la actividad, el servicio, etc. Si no existe este proceso, se creará un nuevo proceso, se leerá el AndroidManifest.xml y se cargará y se creará una instancia del tipo especificado en el atributo /manifest/application/@android:name. A continuación, se crea una instancia 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 conecta a esto agregando un mono. ContentProvider de MonoRuntimeProvider AndroidManifest.xml durante el proceso de compilación. Mono. El método MonoRuntimeProvider.attachInfo() es responsable de cargar el entorno de ejecución Mono en el proceso. Los intentos de usar Mono antes de este punto producirán un error. ( Nota: Este es el motivo por el que los tipos de subclase Android.App.Application necesitan proporcionar un constructor (IntPtr, JniHandleOwnership),ya que la instancia de Application se crea antes de que se pueda inicializar Mono).

Una vez completada la inicialización del proceso, se consulta para encontrar el nombre de clase de AndroidManifest.xml 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 tanto, los contenedores que se pueden llamar de Android) y, a continuación, se crea una instancia de . La creación de una instancia de contenedor que se puede llamar de Android desencadenará la creación de una instancia del tipo C# correspondiente. A continuación, Android invocará Activity.onCreate(Bundle), lo que hará que se invoque el elemento Activity.OnCreate(Bundle) correspondiente y estará fuera de las carreras.