Guía de estilo AIDL

Las mejores prácticas descritas aquí sirven como guía para desarrollar interfaces AIDL de manera efectiva y con atención a la flexibilidad de la interfaz, particularmente cuando AIDL se usa para definir una API o interactuar con superficies de API.

AIDL se puede usar 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), debe 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 por su semántica, argumentos, uso de excepciones integradas, excepciones específicas del servicio y valor de retorno.
  • Documente cada interfaz por su semántica.
  • Documentar el significado semántico de enumeraciones y constantes.
  • Documente todo lo que no esté claro para un implementador.
  • Proporcione ejemplos donde sea relevante.

2. Carcasa

Use mayúsculas y minúsculas en camello para tipos y mayúsculas y minúsculas en camello para métodos, campos y argumentos. Por ejemplo, MyParcelable para un tipo parcelable y anArgument para un argumento. Para los 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. Denominación

[-Winterface-name] El nombre de una interfaz debe comenzar con I like IFoo .

2. Evite una gran interfaz con "objetos" basados ​​en id

Prefiere las 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 - Hace que el ciclo de vida de los objetos sea más simple - Aprovecha las ventajas de que los enlaces no se pueden falsificar.

No recomendado: una única interfaz 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 los 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. Arreglos 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 matriz de salida no puede ser elegido por el servidor. Este comportamiento no deseado ocurre debido 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. Evitar out/inout @nullable non-array parámetros

[-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 provocar un comportamiento incoherente en los backends. Por ejemplo, los backends que no son de Java pueden establecer un parámetro de salida @nullable en nulo (en C++, establecerlo como std::nullopt ) pero el cliente de Java no puede leerlo como nulo.

Parcelables estructurados

1. Cuándo usar

Use parcelables estructurados donde tenga múltiples tipos de datos para enviar.

O bien, cuando actualmente tiene un solo tipo de datos pero espera que necesite ampliarlo en el futuro. Por ejemplo, no use 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. Proporcione valores predeterminados explícitamente

[-Wexplicit-default, -Wenum-explicit-default] Proporcione valores predeterminados explícitos para los campos.

Parcelables no estructurados

1. Cuándo usar

Los parcelables no estructurados están actualmente disponibles en Java con @JavaOnlyStableParcelable y en el backend de NDK con @NdkOnlyStableParcelable . Por lo general, estos son parcelables antiguos y existentes que no se pueden estructurar fácilmente.

Constantes y enumeraciones

1. Los campos de bits deben 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 o los 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 deben tener un mecanismo establecido para versionar esto, tal vez el mismo AIDL, no deberían poder entrar en conflicto entre sí, y estos valores no deberían ser expuesto 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: uso de valores numerados

@Backing(type="int")
enum FruitType {
    APPLE = 0,
    BANANA = 1,
    MANGO = 2,
    NUM_TYPES, // BAD
}

4. Evita prefijos y sufijos redundantes

[-Wredundant-name] Evite prefijos y sufijos redundantes o repetitivos en constantes y enumeradores.

No recomendado: uso de 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 encarecidamente el uso de FileDescriptor como argumento o el valor de retorno de un método de interfaz AIDL. Especialmente, cuando el AIDL se implementa en Java, esto podría causar una fuga del descriptor de archivo a menos que se maneje con cuidado. Básicamente, si acepta un FileDescriptor , debe cerrarlo manualmente cuando ya no se use.

Para backends nativos, está seguro porque FileDescriptor se asigna a unique_fd , que se puede cerrar automáticamente. Pero independientemente del idioma de backend que usaría, es aconsejable NO usar FileDescriptor en absoluto porque esto limitará su libertad para cambiar el idioma de backend en el futuro.

En su lugar, use 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 entendidas sin necesidad de documentación de referencia.

Ejemplos

long duration; // Bad
long durationNsec; // Good
long durationNanos; // Also good

double energy; // Bad
double energyMilliJoules; // Good

int frequency; // Bad
int frequencyHz; // Good

Los sellos 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;