Guía de estilo de AIDL

Las prácticas recomendadas que se describen aquí sirven como guía para desarrollar interfaces de AIDL de manera eficaz y con atención a la flexibilidad de la interfaz, en particular, cuando se usa AIDL para definir una API o interactuar con las plataformas de API.

Se puede usar AIDL para definir una API cuando las apps necesitan interactuar entre sí en un proceso en segundo plano o con el sistema. Para obtener más información sobre el desarrollo de interfaces de programación en apps con AIDL, consulta Lenguaje de definición de la interfaz de Android (AIDL). Para ver ejemplos de AIDL en la práctica, consulta AIDL para HAL y AIDL estable.

Control de versiones

Cada instantánea retrocompatible de una API de AIDL corresponde a una versión. Para tomar una instantánea, ejecuta m <module-name>-freeze-api. Cada vez que se lanza un cliente o un servidor de la API (por ejemplo, en un tren de Mainline), debes tomar una instantánea y crear una versión nueva. En el caso de las APIs de sistema a proveedor, esto debería ocurrir con la revisión anual de la plataforma.

Para obtener más detalles y más información sobre el tipo de cambios que se permiten, consulta Interfaces de control de versiones.

Lineamientos de diseño de APIs

General

1. Documenta todo

  • Documenta cada método para su semántica, argumentos, uso de excepciones integradas, excepciones específicas del servicio y valor que se muestra.
  • Documenta cada interfaz para su semántica.
  • Documenta el significado semántico de las enums y las constantes.
  • Documenta todo lo que pueda ser poco claro para un implementador.
  • Proporciona ejemplos cuando sea relevante.

2. Carcasa

Usa mayúsculas para los tipos y minúsculas para los métodos, los campos y los argumentos. Por ejemplo, MyParcelable para un tipo parcelable y anArgument para un argumento. En el caso de las siglas, considera que el acrónimo es una palabra (NFC -> Nfc).

[-Wconst-name] Los valores y constantes de enum deben ser ENUM_VALUE y CONSTANT_NAME.

Interfaces

1. Nombre

[-Winterface-name] El nombre de la interfaz debe comenzar con I, como IFoo.

2. Evita una interfaz grande con "objetos" basados en IDs.

Prefiere las subinterfaces cuando haya muchas llamadas relacionadas con una API específica. Esto proporciona los siguientes beneficios:

  • Facilita la comprensión del código del cliente o del servidor.
  • Simplifica el ciclo de vida de los objetos.
  • Aprovecha que los binders no se pueden falsificar.

No se recomienda: Una sola interfaz grande con objetos basados en IDs

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: Interfaces individuales

interface IManager {
    IFoo getFoo();
}

interface IFoo {
    void begin(); // clients in other processes can't guess a binder
    void op();
}

3. No mezcles métodos unidireccionales con bidireccionales.

[-Wmixed-oneway] No mezcles métodos unidireccionales con métodos no unidireccionales, ya que esto complica la comprensión del modelo de subprocesos para los clientes y los servidores. Específicamente, cuando lees el código del cliente de una interfaz en particular, debes buscar cada método para saber si se bloqueará o no.

4. Evita mostrar códigos de estado

Los métodos deben evitar los códigos de estado como valores que se muestran, ya que todos los métodos de AIDL tienen un código de estado implícito. Consulta ServiceSpecificException o EX_SERVICE_SPECIFIC. Por convención, estos valores se definen como constantes en una interfaz AIDL. Puedes encontrar información más detallada en la sección de administración de errores de los backends de AIDL.

5. Los arrays como parámetros de salida se consideran dañinos

[-Wout-array] Los métodos que tienen parámetros de salida de array, como void foo(out String[] ret), suelen ser malos porque el cliente debe declarar y asignar el tamaño del array de salida en Java, por lo que el servidor no puede elegir el tamaño del array de salida. Este comportamiento no deseado ocurre debido a la forma en que funcionan los arrays en Java (no se pueden reasignar). En su lugar, prefiere APIs como String[] foo().

6. Evita los parámetros de entrada y salida

[-Winout-parameter] Esto puede confundir a los clientes porque incluso los parámetros in se parecen a los parámetros out.

7. Evita los parámetros no array @nullable de entrada y salida

[-Wout-nullable] Como el backend de Java no controla la anotación @nullable, mientras que otros backends sí lo hacen, out/inout @nullable T puede generar un comportamiento incoherente entre los backends. Por ejemplo, los backends que no son de Java pueden establecer un parámetro @nullable de salida como nulo (en C++, se establece como std::nullopt), pero el cliente de Java no puede leerlo como nulo.

Elementos parcelables estructurados

1. Cuándo debe utilizarse

Usa elementos parcelables estructurados cuando tengas varios tipos de datos para enviar.

O bien, cuando tienes un solo tipo de datos, pero esperas necesitar ampliarlo en el futuro. Por ejemplo, no uses String username. Usa un elemento parcelable extensible, como el siguiente:

parcelable User {
    String username;
}

Para que, en el futuro, puedas ampliarlo, haz lo siguiente:

parcelable User {
    String username;
    int id;
}

2. Proporciona valores predeterminados de forma explícita

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

Elementos parcelables no estructurados

1. Cuándo debe utilizarse

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

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. Los enums deben ser conjuntos cerrados.

Los enums deben ser conjuntos cerrados. Nota: Solo el propietario de la interfaz puede agregar elementos de enumeración. Si los proveedores o OEMs necesitan extender estos campos, se necesita un mecanismo alternativo. Siempre que sea posible, se debe preferir la funcionalidad de proveedores de upstream. Sin embargo, en algunos casos, es posible que se permitan valores de proveedores personalizados (aunque los proveedores deben tener un mecanismo para crear versiones de este, quizás el propio AIDL, no deben poder entrar en conflicto entre sí y estos valores no deben exponerse a apps de terceros).

3. Evita valores como "NUM_ELEMENTS".

Dado que las enums tienen control de versión, se deben evitar los valores que indican cuántos valores están presentes. En C++, se puede solucionar con enum_range<>. Para Rust, usa enum_values(). En Java, aún no hay una solución.

No se recomienda: Usar valores numerados

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

4. Evita los prefijos y sufijos redundantes

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

No se recomienda: Usar un prefijo redundante

enum MyStatus {
    STATUS_GOOD,
    STATUS_BAD // BAD
}

Recomendado: Asigna un nombre directamente a la enumeración

enum MyStatus {
    GOOD,
    BAD
}

FileDescriptor

[-Wfile-descriptor] No se recomienda el uso de FileDescriptor como argumento o el valor que se muestra de un método de interfaz de AIDL. En especial, cuando el AIDL se implementa en Java, esto puede provocar una filtración del descriptor de archivo, a menos que se maneje con cuidado. Básicamente, si aceptas un FileDescriptor, debes cerrarlo de forma manual cuando ya no se use.

En el caso de los backends nativos, estás protegido porque FileDescriptor se asigna a unique_fd, que se puede cerrar automáticamente. Sin embargo, independientemente del lenguaje de backend que uses, es recomendable NO usar FileDescriptor, ya que esto limitará tu libertad para cambiar el lenguaje de backend en el futuro.

En su lugar, usa ParcelFileDescriptor, que se puede cerrar automáticamente.

Unidades variables

Asegúrate de que las unidades variables se incluyan en el nombre para que sus unidades estén bien definidas y se comprendan sin necesidad de consultar la 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;