Las mejores prácticas descritas aquí sirven como guía para desarrollar interfaces AIDL de manera efectiva y prestando atención a la flexibilidad de la interfaz, particularmente cuando se usa AIDL para definir una API o interactuar con superficies API.
AIDL se puede utilizar para definir una API cuando las aplicaciones necesitan interactuar entre sí en un proceso en segundo plano o necesitan interactuar con el sistema. Para obtener más información sobre el desarrollo de interfaces de programación en aplicaciones con AIDL, consulte Lenguaje de definición de interfaz de Android (AIDL) . Para ver ejemplos de AIDL en la práctica, consulte AIDL para HAL y AIDL estable .
Versionado
Cada instantánea compatible con versiones anteriores de una API AIDL corresponde a una versión. Para tomar una instantánea, ejecute m <module-name>-freeze-api
. Cada vez que se lanza un cliente o servidor de la API (por ejemplo, en un tren principal), es necesario tomar una instantánea y crear una nueva versión. Para las API de sistema a proveedor, esto debería suceder con la revisión anual de la plataforma.
Para obtener más detalles e información sobre el tipo de cambios que se permiten, consulte Versiones de interfaces .
Directrices de diseño de API
General
1. Documenta todo
- Documente cada método en cuanto a su semántica, argumentos, uso de excepciones integradas, excepciones específicas del servicio y valor de retorno.
- Documente cada interfaz por su semántica.
- Documente el significado semántico de enumeraciones y constantes.
- Documente todo lo que pueda no estar claro para un implementador.
- Proporcione ejemplos cuando sea relevante.
2. Carcasa
Utilice mayúsculas de camello superior para tipos y mayúsculas de camello inferior para métodos, campos y argumentos. Por ejemplo, MyParcelable
para un tipo parcelable y anArgument
para un argumento. Para acrónimos, considere el acrónimo como una palabra ( NFC
-> Nfc
).
[-Wconst-name] Los valores y constantes de enumeración deben ser ENUM_VALUE
y CONSTANT_NAME
Interfaces
1. Nombrar
[-Winterface-name] El nombre de una interfaz debe comenzar con I
gusta IFoo
.
2. Evite la interfaz grande con "objetos" basados en ID
Prefiere subinterfaces cuando hay muchas llamadas relacionadas con una API específica. Esto proporciona los siguientes beneficios: - Hace que el código cliente/servidor sea más fácil de entender - Simplifica el ciclo de vida de los objetos - Aprovecha que los carpetas son imposibles de falsificar.
No recomendado: una interfaz única y grande con objetos basados en ID.
interface IManager {
int getFooId();
void beginFoo(int id); // clients in other processes can guess an ID
void opFoo(int id);
void recycleFoo(int id); // ownership not handled by type
}
Recomendado: subinterfaces individuales
interface IManager {
IFoo getFoo();
}
interface IFoo {
void begin(); // clients in other processes can't guess a binder
void op();
}
3. No mezcle métodos unidireccionales con métodos bidireccionales
[-Wmixed-oneway] No mezcle métodos unidireccionales con métodos no unidireccionales, ya que complica la comprensión del modelo de subprocesamiento para clientes y servidores. Específicamente, al leer el código del cliente de una interfaz en particular, debe buscar cada método si ese método se bloqueará o no.
4. Evite devolver códigos de estado
Los métodos deben evitar códigos de estado como valores de retorno, ya que todos los métodos AIDL tienen un código de retorno de estado implícito. Consulte ServiceSpecificException
o EX_SERVICE_SPECIFIC
. Por convención, estos valores se definen como constantes en una interfaz AIDL. Hay información más detallada en la sección Manejo de errores de AIDL Backends .
5. Matrices como parámetros de salida considerados dañinos
[-Wout-array] Los métodos que tienen parámetros de salida de matriz, como void foo(out String[] ret)
suelen ser malos porque el tamaño de la matriz de salida debe ser declarado y asignado por el cliente en Java, por lo que el tamaño de la salida de la matriz no puede ser elegido por el servidor. Este comportamiento indeseable se debe a cómo funcionan las matrices en Java (no se pueden reasignar). En su lugar, prefiera API como String[] foo()
.
6. Evite los parámetros de entrada y salida
[-Winout-parameter] Esto puede confundir a los clientes porque incluso los parámetros in
parecen parámetros out
.
7. Evite los parámetros out/inout @nullable que no sean de matriz
[-Wout-nullable] Dado que el backend de Java no maneja la anotación @nullable
mientras que otros backends sí lo hacen, out/inout @nullable T
puede generar un comportamiento inconsistente entre los backends. Por ejemplo, los backends que no son Java pueden establecer un parámetro @nullable
en nulo (en C++, configurándolo como std::nullopt
), pero el cliente Java no puede leerlo como nulo.
Parcelables estructurados
1. Cuándo utilizar
Utilice parcelas estructuradas donde tenga varios tipos de datos para enviar.
O bien, cuando actualmente tiene un único tipo de datos pero cree que necesitará ampliarlo en el futuro. Por ejemplo, no utilice String username
. Utilice un paquete extensible, como el siguiente:
parcelable User {
String username;
}
Para que, en el futuro, puedas ampliarlo, de la siguiente manera:
parcelable User {
String username;
int id;
}
2. Proporcionar valores predeterminados explícitamente
[-Wexplicit-default, -Wenum-explicit-default] Proporciona valores predeterminados explícitos para los campos.
Parcelables no estructurados
1. Cuándo utilizar
Los parcelables no estructurados están actualmente disponibles en Java con @JavaOnlyStableParcelable
y en el backend de NDK con @NdkOnlyStableParcelable
. Por lo general, se trata de parcelas antiguas y existentes que no se pueden estructurar fácilmente.
Constantes y enumeraciones
1. Bitfields debería usar campos constantes
Los campos de bits deben usar campos constantes (por ejemplo, const int FOO = 3;
en una interfaz).
2. Las enumeraciones deben ser conjuntos cerrados.
Las enumeraciones deben ser conjuntos cerrados. Nota: solo el propietario de la interfaz puede agregar elementos de enumeración. Si los proveedores u OEM necesitan ampliar estos campos, se necesita un mecanismo alternativo. Siempre que sea posible, se debe preferir la funcionalidad del proveedor ascendente. Sin embargo, en algunos casos, se pueden permitir valores de proveedores personalizados (aunque los proveedores deberían tener un mecanismo para versionar esto, tal vez el propio AIDL, no deberían poder entrar en conflicto entre sí y estos valores no deberían ser expuestos a aplicaciones de terceros).
3. Evite valores como "NUM_ELEMENTS"
Dado que las enumeraciones están versionadas, se deben evitar los valores que indican cuántos valores están presentes. En C++, esto se puede solucionar con enum_range<>
. Para Rust, use enum_values()
. En Java, todavía no hay solución.
No recomendado: usar valores numerados
@Backing(type="int")
enum FruitType {
APPLE = 0,
BANANA = 1,
MANGO = 2,
NUM_TYPES, // BAD
}
4. Evite prefijos y sufijos redundantes
[-Wredundant-name] Evite prefijos y sufijos redundantes o repetitivos en constantes y enumeradores.
No recomendado: usar un prefijo redundante
enum MyStatus {
STATUS_GOOD,
STATUS_BAD // BAD
}
Recomendado: nombrar directamente la enumeración
enum MyStatus {
GOOD,
BAD
}
Descriptor de archivo
[-Wfile-descriptor] Se desaconseja el uso de FileDescriptor
como argumento o valor de retorno de un método de interfaz AIDL. Especialmente, cuando AIDL se implementa en Java, esto podría causar fugas en el descriptor de archivo a menos que se maneje con cuidado. Básicamente, si acepta un FileDescriptor
, deberá cerrarlo manualmente cuando ya no se utilice.
Para los servidores nativos, está seguro porque FileDescriptor
se asigna a unique_fd
, que se puede cerrar automáticamente. Pero independientemente del idioma de backend que utilice, es aconsejable NO utilizar FileDescriptor
en absoluto porque esto limitará su libertad para cambiar el idioma de backend en el futuro.
En su lugar, utilice ParcelFileDescriptor
, que se puede cerrar automáticamente.
Unidades variables
Asegúrese de que las unidades variables estén incluidas en el nombre para que sus unidades estén bien definidas y comprendidas sin necesidad de consultar documentación.
Ejemplos
long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good
double energy; // Bad
double energyMilliJoules; // Good
int frequency; // Bad
int frequencyHz; // Good
Las marcas de tiempo deben indicar su referencia.
Las marcas de tiempo (¡de hecho, todas las unidades!) deben indicar claramente sus unidades y puntos de referencia.
Ejemplos
/**
* Time since device boot in milliseconds
*/
long timestampMs;
/**
* UTC time received from the NTP server in units of milliseconds
* since January 1, 1970
*/
long utcTimeMs;