Principios de diseño de la API de Xamarin.Android
Además de las bibliotecas de clases base principales que forman parte de Mono, Xamarin.Android incluye enlaces para varias API de Android que permiten a los desarrolladores crear aplicaciones nativas de Android con Mono.
En el núcleo de Xamarin.Android hay un motor de interoperabilidad que une el mundo de C# con el mundo de Java y proporciona a los desarrolladores acceso a las API de Java desde C# u otros lenguajes .NET.
Principios de diseño
Estos son algunos de nuestros principios de diseño para el enlace de Xamarin.Android
Cumpla las directrices de .NET Framework diseño de .
Permitir a los desarrolladores crear subclases de clases de Java.
La subclase debe funcionar con construcciones estándar de C#.
Derive de una clase existente.
Llame al constructor base para encadenar.
La invalidación de métodos debe realizarse con el sistema de invalidación de C#.
Facilitar las tareas comunes de Java y las tareas de Java difíciles.
Exponer las propiedades de JavaBean como propiedades de C#.
Exponga una API fuertemente tipada:
Aumentar la seguridad de tipos.
Minimice los errores en tiempo de ejecución.
Obtenga IntelliSense del IDE en los tipos de valor devueltos.
Permite la documentación emergente del IDE.
Aliente la exploración en el IDE de las API:
Use alternativas de marco para minimizar la exposición de la biblioteca de clases de Java.
Exponga delegados de C# (lambdas, métodos anónimos y System.Delegate) en lugar de interfaces de método único cuando sea adecuado y aplicable.
Proporcione un mecanismo para llamar a bibliotecas de Java arbitrarias ( Android.Runtime.JNIEnv).
Ensamblados
Xamarin.Android incluye una serie de ensamblados que constituyen monomobile profile. La página Ensamblados tiene más información.
Los enlaces a la plataforma Android se incluyen en el Mono.Android.dll ensamblado. Este ensamblado contiene el enlace completo para consumir las API de Android y comunicarse con la máquina virtual en tiempo de ejecución de Android.
Diseño de enlace
Colecciones
Las API de Android usan ampliamente las colecciones java.util para proporcionar listas, conjuntos y mapas. Exponemos estos elementos mediante las interfaces System.Collections.Generic en nuestro enlace. Las asignaciones fundamentales son:
java.util.Set E > se asigna al tipo de >, clase auxiliar <.
java.util.List E > se asigna al tipo de >, clase auxiliar <.
java.util.Map K,V > se asigna al tipo de >, clase auxiliar <.
java.util.Collection E > se asigna al tipo de >, clase auxiliar <.
Hemos proporcionado clases auxiliares para facilitar una serialización sin copia más rápida de estos tipos. Cuando sea posible, se recomienda usar estas colecciones proporcionadas en lugar de la implementación proporcionada por el marco, como List<T> o Dictionary<TKey, TValue> . Las implementaciones de Android.Runtime usan una colección nativa de Java internamente y, por lo tanto, no requieren la copia hacia y desde una colección nativa al pasar a un miembro de la API de Android.
Puede pasar cualquier implementación de interfaz a un método de Android que acepte esa interfaz, por ejemplo, pasar un al List<int> constructor List<int> Sinembargo, para todas las implementaciones excepto para las implementaciones de Android.Runtime, esto implica copiar la lista de la máquina virtual Mono en la máquina virtual en tiempo de ejecución de Android. Si la lista se cambia más adelante en el entorno de ejecución de Android (por ejemplo, mediante la invocación de ArrayAdapter T > . Método Add(T), esos cambios > visibles en el código administrado. Si se JavaList<int> usara un , esos cambios serían visibles.
Las implementaciones de interfaz de colecciones que no son una de las clases auxiliares enumeradas anteriormente solo serializan [In]:
// This fails:
var badSource = new List<int> { 1, 2, 3 };
var badAdapter = new ArrayAdapter<int>(context, textViewResourceId, badSource);
badAdapter.Add (4);
if (badSource.Count != 4) // true
throw new InvalidOperationException ("this is thrown");
// this works:
var goodSource = new JavaList<int> { 1, 2, 3 };
var goodAdapter = new ArrayAdapter<int> (context, textViewResourceId, goodSource);
goodAdapter.Add (4);
if (goodSource.Count != 4) // false
throw new InvalidOperationException ("should not be reached.");
Propiedades
Los métodos de Java se transforman en propiedades, cuando corresponda:
El par de métodos
T getFoo()javavoid setFoo(T)y se transforman en la propiedadFoo. Ejemplo: Activity.Intent.El método Java
getFoo()se transforma en la propiedad Foo de solo lectura. Ejemplo: Context.PackageName.No se generan propiedades de solo establecimiento.
Las propiedades no se generan si el tipo de propiedad sería una matriz.
Eventos y agentes de escucha
Las API de Android se basa en Java y sus componentes siguen el patrón de Java para enlazar agentes de escucha de eventos. Este patrón tiende a ser complicado, ya que requiere que el usuario cree una clase anónima y declare los métodos para invalidar, por ejemplo, así es como se harían las cosas en Android con Java:
final android.widget.Button button = new android.widget.Button(context);
button.setText(this.count + " clicks!");
button.setOnClickListener (new View.OnClickListener() {
public void onClick (View v) {
button.setText(++this.count + " clicks!");
}
});
El código equivalente en C# mediante eventos sería:
var button = new Android.Widget.Button (context) {
Text = string.Format ("{0} clicks!", this.count),
};
button.Click += (sender, e) => {
button.Text = string.Format ("{0} clicks!", ++this.count);
};
Tenga en cuenta que ambos mecanismos anteriores están disponibles con Xamarin.Android. Puede implementar una interfaz de agente de escucha y adjuntarla con View.SetOnClickListener, o bien puede adjuntar un delegado creado a través de cualquiera de los paradigmas de C# habituales al evento Click.
Cuando el método de devolución de llamada del agente de escucha tiene un valor devuelto void, creamos elementos de API basados en un delegado > TEventArgs eventHandler. Generamos un evento como el ejemplo anterior para estos tipos de agente de escucha. Sin embargo, si la devolución de llamada del agente de escucha devuelve un valor booleano distinto de void y no booleano, no se usan eventos ni EventHandlers. En su lugar, generamos un delegado específico para la firma de la devolución de llamada y agregamos propiedades en lugar de eventos. El motivo es tratar el orden de invocación de delegado y el control de devolución. Este enfoque refleja lo que se hace con la API de Xamarin.iOS.
Los eventos o propiedades de C# solo se generan automáticamente si el método de registro de eventos de Android:
Tiene un
setprefijo, por ejemplo,setTiene un
voidtipo de valor devuelto.Acepta solo un parámetro, el tipo de parámetro es una interfaz, la interfaz solo tiene un método y el nombre de la interfaz termina en , por
Listenerejemplo,Listener.
Además, si el método de interfaz listener tiene un tipo de valor devuelto booleano en lugar de void,la subclase EventArgs generada contendrá una propiedad Handled. El valor de la propiedad Handled se usa como valor devuelto para el método Listener y el valor predeterminado es .
Por ejemplo, el método Android View.setOnKeyListener() acepta la interfaz View.OnKeyListener y el método View.OnKeyListener.onKey(View, int, KeyEvent) tiene un tipo de valor devuelto booleano. Xamarin.Android genera un evento View.KeyPress correspondiente, que es un objeto EventHandler View.KeyEventArgs. A su vez, la clase KeyEventArgs tiene una propiedad View.KeyEventArgs.Handled, que se usa como valor devuelto para el método View.OnKeyListener.onKey().
Pretendemos agregar sobrecargas para otros métodos y ctors para exponer la conexión basada en delegados. Además, los agentes de escucha con varias devoluciones de llamada requieren una inspección adicional para determinar si la implementación de devoluciones de llamada individuales es razonable, por lo que las estamos convirtiendo a medida que se identifican. Si no hay ningún evento correspondiente, los agentes de escucha deben usarse en C#, pero preste atención a los que crea que podrían tener un uso delegado. También hemos realizado algunas conversiones de interfaces sin el sufijo "Listener" cuando estaba claro que se beneficiarían de una alternativa de delegado.
Todas las interfaces de agentes de escucha implementan elAndroid.Runtime.IJavaObject interfaz , debido a los detalles de implementación del enlace, por lo que las clases de agente de escucha deben implementar esta interfaz. Para ello, implemente la interfaz del agente de escucha en una subclase de Java.Lang.Object o cualquier otro objeto Java encapsulado, como una actividad de Android.
Runnables
Java usa la interfaz java.lang.Runnable para proporcionar un mecanismo de delegación. La clase java.lang.Thread es un consumidor importante de esta interfaz. Android también ha empleado la interfaz en la API. Activity.runOnUiThread()y View.post() son ejemplos importantes.
La Runnable interfaz contiene un único método void, Runnable Por lo tanto, se presta a enlazar en C# como delegado System.Action. Hemos proporcionado sobrecargas en el enlace que aceptan un parámetro para todos los miembros de API que consumen un en la API nativa, por ActionRunnableActionRunnable
Hemos dejado las sobrecargas IRunnable en su lugar en lugar de reemplazarlas, ya que varios tipos implementan la interfaz y, por lo tanto, se pueden pasar como runnables directamente.
Clases internas
Java tiene dos tipos diferentes de clases anidadas:clases anidadas estáticas y clases no estáticas.
Las clases anidadas estáticas de Java son idénticas a los tipos anidados de C#.
Las clases anidadas no estáticas, también denominadas clases internas,son significativamente diferentes. Contienen una referencia implícita a una instancia de su tipo de contenedor y no pueden contener miembros estáticos (entre otras diferencias fuera del ámbito de esta información general).
En lo que respecta al enlace y al uso de C#, las clases anidadas estáticas se tratan como tipos anidados normales. Mientras tanto, las clases internas tienen dos diferencias significativas:
La referencia implícita al tipo que lo contiene debe proporcionarse explícitamente como parámetro de constructor.
Al heredar de una clase interna, la clase interna debe estar anidada dentro de un tipo que herede del tipo que lo contiene de la clase interna base, y el tipo derivado debe proporcionar un constructor del mismo tipo que el tipo que contiene C#.
Por ejemplo, considere la clase interna Android.Service.Wallpaper.WallpaperService.Engine. Puesto que es una clase interna, el constructor WallpaperService.Engine() toma una referencia a una instancia de WallpaperService (comparar y contrastar con el constructor WallpaperService.Engine() de Java, que no toma ningún parámetro).
Una derivación de ejemplo de una clase interna es CubeWallpaper.CubeEngine:
class CubeWallpaper : WallpaperService {
public override WallpaperService.Engine OnCreateEngine ()
{
return new CubeEngine (this);
}
class CubeEngine : WallpaperService.Engine {
public CubeEngine (CubeWallpaper s)
: base (s)
{
}
}
}
Observe cómo está anidado dentro de , hereda de la clase que contiene de y tiene un constructor que toma el tipo declarante ( en este caso ), todo como se especificó CubeWallpaper.CubeEngineCubeWallpaperCubeWallpaperWallpaperService.EngineCubeWallpaper.CubeEngineCubeWallpaper anteriormente.
Interfaces
Las interfaces de Java pueden contener tres conjuntos de miembros, dos de los cuales causan problemas de C#:
Métodos
Tipos
Campos
Las interfaces de Java se traducen en dos tipos:
Interfaz (opcional) que contiene declaraciones de método. Esta interfaz tiene el mismo nombre que la interfaz de Java, salvo que también tiene un prefijo 'I'.
Clase estática (opcional) que contiene los campos declarados dentro de la interfaz de Java.
Los tipos anidados se "reubican" para que sean elementos del mismo nivel de la interfaz de encier en lugar de tipos anidados, con el nombre de interfaz de encier como prefijo.
Por ejemplo, considere la interfaz android.os.Parcelable. La interfaz Descomposiciones contiene métodos, tipos anidados y constantes. Los métodos de interfaz Desatensable se colocan en la interfaz Android.OS.IParcelable. Las constantes de la interfaz Descomportable se colocan en el tipo Android.OS.ParcelableConsts. Los tipos anidados android.os.Parcelable.ClassLoaderCreator T > y > no están enlazados actualmente debido a limitaciones en la compatibilidad con genéricos; si se admiten, estarán presentes como interfaces < y > Por ejemplo, la interfaz anidada android.os.IBinder.DeathRecipient se enlaza como la interfaz Android.OS.IBinderDeathRecipient.
Nota:
A partir de Xamarin.Android 1.9, las constantes de interfaz de Java se duplican en un esfuerzo por simplificar la porción de código Java. Esto ayuda a mejorar la porción de código Java que se basa en constantes de interfaz del proveedor de Android.
Además de los tipos anteriores, hay cuatro cambios posteriores:
Se genera un tipo con el mismo nombre que la interfaz de Java para contener constantes.
Los tipos que contienen constantes de interfaz también contienen todas las constantes que proceden de interfaces java implementadas.
Todas las clases que implementan una interfaz de Java que contiene constantes obtienen un nuevo tipo anidado InterfaceConsts que contiene constantes de todas las interfaces implementadas.
El tipo Consts ahora está obsoleto.
Para la interfaz android.os.Parcelable, esto significa que ahora habrá un tipo Android.OS.Parcelable que contendrá las constantes. Por ejemplo, la constante Parcelable.CONTENTS_FILE_DESCRIPTOR se enlazará como constante Descriptor Desaceptable.ContentsFileDescriptor, en lugar de como constante Descriptor Descriptor DeSaldableConsts.ContentsFileDescriptor.
Para las interfaces que contienen constantes que implementan otras interfaces que contienen todavía más constantes, ahora se genera la unión de todas las constantes. Por ejemplo, la interfaz android.provider.MediaStore.Video.VideoColumns implementa la interfaz android.provider.MediaStore.MediaColumns. Sin embargo, antes de la 1.9, el tipo Android.Provider.MediaStore.Video.VideoColumnsConsts no tiene ninguna manera de acceder a las constantes declaradas en Android.Provider.MediaStore.MediaColumnsConsts. Como resultado, la expresión de Java MediaStore.Video.VideoColumns.TITLE debe enlazarse a la expresión de C# MediaStore.Video.MediaColumnsConsts.Title, que es difícil de detectar sin leer mucha documentación de Java. En la versión 1.9, la expresión de C# equivalente será MediaStore.Video.VideoColumns.Title.
Además, considere el tipo android.os.Bundle, que implementa la interfaz Java Bundleable. Puesto que implementa la interfaz , todas las constantes de esa interfaz son accesibles "a través" del tipo bundle, por ejemplo, Bundle.CONTENTS_FILE_DESCRIPTOR es una expresión de Java perfectamente válida. Anteriormente, para portabilidad de esta expresión a C#, tendría que ver todas las interfaces que se implementan para ver de qué tipo CONTENTS_FILE_DESCRIPTOR la expresión. A partir de Xamarin.Android 1.9, las clases que implementan interfaces de Java que contienen constantes tendrán un tipo InterfaceConsts anidado, que contendrá todas las constantes de interfaz heredadas. Esto permitirá traducir Bundle.CONTENTS_FILE_DESCRIPTOR a Bundle.InterfaceConsts.ContentsFileDescriptor.
Por último, los tipos con un sufijo Consts como Android.OS.ParcelableConsts ahora están obsoletos, aparte de los tipos anidados InterfaceConsts recién introducidos. Se quitarán en Xamarin.Android 3.0.
Recursos
Las imágenes, las descripciones de diseño, los blobs binarios y los diccionarios de cadenas se pueden incluir en la aplicación como archivos de recursos. Varias API de Android están diseñadas para funcionar en los identificadores de recursos en lugar de tratar directamente con imágenes, cadenas o blobs binarios.
Por ejemplo, una aplicación android de ejemplo que contiene un diseño de interfaz de usuario ( ), una cadena de tabla de internacionalización ( ) y algunos iconos ( ) conservaría sus recursos en el directorio "Resources" de la main.axmlstrings.xmldrawable-*/icon.png aplicación:
Resources/
drawable-hdpi/
icon.png
drawable-ldpi/
icon.png
drawable-mdpi/
icon.png
layout/
main.axml
values/
strings.xml
Las API nativas de Android no funcionan directamente con nombres de archivo, sino que funcionan en identificadores de recursos. Al compilar una aplicación Android que usa recursos, el sistema de compilación empaquetará los recursos para la distribución y generará una clase denominada que contiene los tokens para cada uno de Resource los recursos incluidos. Por ejemplo, para el diseño de recursos anterior, esto es lo que la clase de R expondría:
public class Resource {
public class Drawable {
public const int icon = 0x123;
}
public class Layout {
public const int main = 0x456;
}
public class String {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
A continuación, usaría para hacer referencia al archivo o para hacer referencia al archivo , o para hacer referencia a Resource.Drawable.icon la primera cadena del archivo de diccionario drawable/icon.pngResource.Layout.mainlayout/main.xmlResource.String.first_stringvalues/strings.xml .
Constantes y enumeraciones
Las API nativas de Android tienen muchos métodos que toman o devuelven un valor int que se debe asignar a un campo constante para determinar lo que significa int. Para usar estos métodos, el usuario debe consultar la documentación para ver qué constantes son los valores adecuados, lo que es menor que lo ideal.
Por ejemplo, considere Activity.requestWindowFeature(int featureID).
En estos casos, nos esforzamos por agrupar las constantes relacionadas en una enumeración de .NET y reasignar el método para tomar la enumeración en su lugar. Al hacerlo, podemos ofrecer la selección de IntelliSense de los valores potenciales.
El ejemplo anterior se convierte en: Activity.RequestWindowFeature(WindowFeatures featureId).
Tenga en cuenta que se trata de un proceso muy manual para averiguar qué constantes pertenecen juntas y qué API consumen estas constantes. Envíe errores para las constantes usadas en la API que se expresarían mejor como una enumeración.