Architecture
Las aplicaciones 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 en tiempo de ejecución de Android (ART). Ambos entornos de 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 se escribe 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 en tiempo de ejecución de Android que residen en uno de los espacios de nombres Java.* o Android.*. La arquitectura es relativamente similar a lo siguiente:
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 Android que proporcionan un puente a las API de Java que expone el tiempo de ejecución de Android.
Para obtener más información sobre cómo las clases de Android se comunican con las clases en tiempo de ejecución de Android, 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 Xamarin.Android tienen la misma estructura y diseño que los paquetes de Android normales, con las siguientes adiciones:
Los ensamblados de aplicación (que contienen IL) se almacenan sin comprimir dentro de la carpeta Ensamblados. Durante el inicio del proceso en compilaciones de versión, .apk se procesa mediante mmap() y los ensamblados se cargan desde la memoria. Esto permite un inicio de la aplicación más rápido, ya que no es necesario extraer ensamblados antes de la ejecución.
Nota: Información de ubicación del ensamblado como Assembly.Location y Assembly.CodeBaseno es fiable en 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 el entorno de ejecución Mono están presentes en .apk. Una aplicación Xamarin.Android debe contener bibliotecas nativas para las arquitecturas de Android deseadas o de destino, por ejemplo , armeabi, armeabi-v7a y x86. Las aplicaciones Xamarin.Android no se pueden ejecutar en una plataforma a menos que contenga las bibliotecas en tiempo de ejecución adecuadas.
Las aplicaciones Xamarin.Android también incluyen contenedores que se pueden llamar de Android para permitir que Android llame al código administrado.
Contenedores que se pueden llamar de Android
- Los contenedores que se pueden llamar de Android son un puente de JNI que se usan cada vez que el tiempo de ejecución de Android necesita invocar código administrado. Los contenedores que se pueden llamar de Android son la forma en que se pueden invalidar los métodos virtuales y se pueden implementar las interfaces de Java. Consulte la documentación 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 de JNI que se usan cada vez que el código administrado necesita invocar código de Android y proporcionar compatibilidad con la invalidación de métodos virtuales y la implementación de interfaces de Java. Todos los espacios de nombres Android.* y relacionados son contenedores que se pueden llamar administrados generados a través de un enlace .jar. Los contenedores que se pueden llamar administrados son responsables de la conversión entre los tipos administrados y de Android, así como de la invocación de los métodos de la plataforma Android subyacentes a través de JNI.
Cada contenedor que se puede llamar administrado creado incluye 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 las instancias de Java y las instancias administradas. Las referencias globales son un recurso limitado: los emuladores solo permiten la existencia de 2000 referencias globales a la vez, mientras que la mayoría del hardware permite la existencia de 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 mediante una llamada a Java.Lang.Object.Dispose() en el contenedor 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 a partir de código administrado, se creará para ella un nuevo contenedor que se puede llamar administrado.
Se deben tomar precauciones a la hora de eliminar contenedores que se pueden llamar administrados si la instancia se puede compartir accidentalmente entre los subprocesos, ya que eliminar la instancia afectará a las referencias de cualquier otro subproceso. Para la máxima seguridad, solo Dispose()
las instancias que se han asignado a través new
de 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 puede llamar administrado
Es en las subclases de contenedor que se puede llamar administrado donde puede residir toda la lógica específica de la aplicación "interesante". Entre estas se incluyen las subclases Android.App.Activity personalizadas (como el tipo Activity1 en la plantilla de proyecto predeterminada). (Con, de forma específica, las 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 puede llamar administrado también incluyen una referencia global, a la que se puede acceder a través de la propiedad Java.Lang.Object.Handle. Al igual que ocurre 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 dichas instancias, ya que la eliminación mediante Dispose() 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
Al crearse un contenedor que se puede llamar de Android (ACW) a partir de Java, el constructor de ACW provocará la invocación del constructor de C# correspondiente. Por ejemplo el ACW para MainActivity incluirá un constructor predeterminado que invocará al constructor predeterminado de MainActivity (esto se hace a través de la llamada a TypeManager.Activate() dentro de los constructores de ACW).
Hay otra signatura de constructor de consecuencia: el constructor (IntPtr, JniHandleOwnership). Se invoca al constructor (IntPtr, JniHandleOwnership) siempre que un objeto Java se expone al código administrado y es necesario construir un contenedor que se puede llamar administrado para administrar el identificador de JNI. Normalmente se realiza de forma automática.
Hay dos escenarios en los que el constructor (IntPtr, JniHandleOwnership) debe proporcionarse manualmente en una subclase de contenedor que se puede llamar administrado:
Se crea una subclase de Android.App.Application. La aplicación es especial, el constructor de la aplicación predeterminado nunca se invocará y el constructor (IntPtr, JniHandleOwnership) debe proporcionarse en su lugar.
Invocación del método virtual de un constructor de clase base.
Tenga en cuenta que (2) es una abstracción con fugas. En Java, como en C#, las llamadas a métodos virtuales de 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 tanto, si un tipo LogTextBox (1) creara una subclase de TextView, (2) invalidara TextView.DefaultMovementMethod y (3) activara una instancia de esa clase a través de XML, la propiedad DefaultMovementMethod invalidada se invocaría antes de que el constructor de ACW tuviera la oportunidad de ejecutarse, y sucedería antes de que el constructor de C# tuviera la oportunidad de ejecutarse.
Esto se admite mediante la creación de una instancia 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, mediante la invocación del constructor LogTextBox(Context, IAttributeSet, int) en la misma instancia al ejecutarse el constructor de ACW.
Orden de eventos
El XML de diseño se carga en ContentView.
Android crea una instancia del gráfico de objetos de diseño y crea una instancia de monodroid.apidemo.LogTextBox, el ACW para LogTextBox.
El constructor monodroid.apidemo.LogTextBox ejecuta el constructor android.widget.TextView.
El constructor TextView invoca monodroid.apidemo.LogTextBox.getDefaultMovementMethod().
monodroid.apidemo.LogTextBox.getDefaultMovementMethod() invoca LogTextBox.n_getDefaultMovementMethod(), que invoca TextView.n_GetDefaultMovementMethod() , que invoca Java.Lang.Object.GetObject<TextView> (identificador, JniHandleOwnership.DoNotTransfer).
Java.Lang.Object.GetObject<TextView>() comprueba si ya hay una instancia de C# correspondiente para el identificador. En caso afirmativo, se devolverá. En este escenario, no hay ninguna, por lo que Object.GetObject<T>() debe crear una.
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.
TextView.n_GetDefaultMovementMethod() invoca al captador de propiedad LogTextBox.DefaultMovementMethod.
El control vuelve al constructor android.widget.TextView, que finaliza la ejecución.
Se ejecuta el constructor monodroid.apidemo.LogTextBox, invocando TypeManager.Activate().
El constructor LogTextBox(Context, IAttributeSet, int) se ejecuta en la misma instancia creada en (7) .
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 de JNI y la instancia de C# correspondiente. Java.Lang.Object.Dispose() interrumpe esta asignación. Si un identificador de JNI escribe 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á al constructor (IntPtr, JniHandleOwnership). Si el constructor no existe, se producirá una excepción.
Por ejemplo, dada la siguiente subclase de contenedor que se puede llamar administrado:
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, la eliminamos mediante Dispose() y hacemos 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 desaparecerá:
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, parecerá que la instancia "pierde" todos los datos de instancia, ya que se trata de una nueva instancia (tenga en cuenta que el valor es null).
I/mono-stdout( 2993): [Managed: Value=]
Solo elimine mediante Dispose() las subclases de contenedor que se puede llamar administrado cuando sepa que el objeto Java no se usará más o la subclase no contenga datos de instancia y se haya proporcionado un constructor (IntPtr, JniHandleOwnership).
Inicio de aplicaciones
Cuando se inicia una actividad, un servicio, etc., 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á uno nuevo, se leerá AndroidManifest.xml y se cargará y se cargará y creará una instancia del tipo especificado en el atributo /manifest/application/@android:name. A continuación, se creará una instancia de todos los tipos especificados por los valores de atributo /manifest/application/provider/@android:name y se invocará su método ContentProvider.attachInfo%28). Xamarin.Android enlaza con esto agregando un mono. MonoRuntimeProvider ContentProvider para AndroidManifest.xml durante el proceso de compilación. El método mono.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 cual los tipos que crean una subclase de Android.App.Application deben proporcionar un constructor (IntPtr, JniHandleOwnership), ya que la instancia de aplicación se crea antes de que Mono pueda inicializarse).
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. En el caso de las actividades, 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 de Java, de ahí los contenedores que se pueden llamar de Android). A continuación, se crea una instancia del mismo. La creación de una instancia de contenedor que se puede llamar de Android desencadenará la creación de una instancia del tipo de C# correspondiente. A continuación, Android invocará Activity.onCreate(Bundle) , que dará lugar a la invocación del Activity.OnCreate(Bundle) correspondiente y estará listo para las carreras.